【Spring IOC】【四】容器源码解析- 循环依赖
1 前言
获取Bean我们大概都了解过了,这篇我们着重讲一下在获取bean的时候,出现的一个循环依赖的问题,这面试可以说是一道必问的题了,看的时候多想多看,别钻死牛角尖,我看的时候我陷入了一个牛角尖,我就在纠结为什么要用三个缓存来解决循环依赖呢,其实一个缓存也可以解决丫,干嘛要用三个呢。我总是陷入这个问题,其实别纠结人家为什么要用三个,这是人家的一个设计,确实一个缓存可以解决,但是你站在框架的角度,你要把AOP融进来,还要考虑你开发框架完要给别人用,使用者要使用AOP的入口设计,框架的纯粹度和优雅性是不是,可能正是因为这样的考虑,人家这么设计的,我猜的哈,有理解错的,请指正哈。
看过Spring IOC这块的源码,熟悉一些类以及前后方法关系的,因为东西太多,我会直截取关键的地方来说,否则你会不清楚来龙去脉没思路的。
那我们进入正题,什么是循环依赖,跟死锁好像有点像,就是你中有我 我中有你,这种关系是间接的或者直接的,打个比方:
A类里需要注入B类,B类里需要注入A类, 这就是直接性的循环依赖
A类里需要注入B类,B类里需要注入C类 C类里又需要注入A类, 这就是间接性的循环依赖
不管是直接还是间接,都是互相依赖导致的恰恰闭环出现的循环依赖,那么Spring是怎么解决的呢,我们看看。
2 依赖关系分类
在Spring里表达依赖关系有几种方式,Spring并不是对所有的方式都支持解决的这个你要明白,那么它给解决哪些,不给解决哪些呢(不给解决就需要你自己解除掉这种关系)?
要进行多例或者单例分类,根据注入方式分类:
- depends-on 这种方式的都不支持,不管是单例的还是多例的,主要bean之间有depends-on导致的循环依赖,Spring都会直接抛异常 这个我们在getBean的时候说过。下边我再贴个图。
- 构造器方式注入 这种方式的都不支持 这个好理解,这种方式会导致对象都创不出来,自己想想是不是 对象都实例化不完
- setter方式注入 单例这种支持 但是多例的不支持,多例不会走三级缓存对象创建不出来
- 注解方式注入 单例支持,多例的不支持
3 代码准备(边看源码边调试)
我们边看源码边拿一个例子来说,这样方便理解,我的类关系是这样的:
// A注入B B注入A @Component public class A { @Autowired private B b; public void say() { System.out.println("a"); } } @Component public class B { @Autowired private A a; public void say() { System.out.println("b"); } }
另外,这两个类我加了个AOP也
<!--通知--> <bean id="advices" class="com.virtuous.demo.spring.LogAspect"/> <!--aop配置--> <aop:config proxy-target-class="true"> <!--切面--> <aop:aspect ref="advices"> <!--切点--> <aop:pointcut id="pointcur1" expression="execution( * com.virtuous.demo.spring.cycle.*.*(..))"/> <!--连接通知方法与切点--> <aop:before method="before" pointcut-ref="pointcur1"/> </aop:aspect> </aop:config>
测试类:
public class ApplicationContextTest { @Test public void testFactoryBean() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring/spring-alias.xml"); A a = applicationContext.getBean("a", A.class); B b = applicationContext.getBean("b", B.class); System.out.println(a); System.out.println(applicationContext.getBean("a")); System.out.println(b); System.out.println(applicationContext.getBean("b")); } }
4 源码分析
好了,到这里我们要开始分析Spring怎么解决循环依赖的,我们会从关键的几个点去看:
(1)首先进来的就是a,先要创建a的bean
(2)createBeanInstancece实例化a后,进行判断是否提前暴露a,将ObjectFactory(A)即把实例A包装成ObjectFactory放入第3级缓存
(3)populateBean(A)即给a的属性赋值的时候,发现需要依赖B,那么就会getBean(B)
(4)进行B的创建
(5)createBeanInstancece实例化b后,进行判断是否提前暴露b,将ObjectFactory(B)即把实例B包装成ObjectFactory放入第3级缓存
(6)populateBean(B)即给b的属性赋值的时候,发现需要依赖A,从第3级缓存中取出a,并调用getObject()方法
(7)AbstractAutoProxyCreator的getEarlyBeanReference方法会判断b是否需要代理,需要的话会返回代理对象,否则直接返回bean自己。
(7)b创建完毕,清空第二、三级缓存,放入完整B到第一级缓存
(8)回到a的创建a,也可以顺利初始化完毕,并清空自己的第二、三级缓存,把完整A放入第一级缓存中,完事。
有没有看晕的,看晕的话就debug多看几次,多想想,注意第三级缓存是一个函数接口,注意嵌套,这个需要多看几遍源码,很多常用的方法,看几次以后就熟悉了,下边再画个图辅助理解一下:
5 小结
累了,歇会儿这家伙看的人脑袋高速运转啊,若文章有错误不妥之处,欢迎大家指出来。好了,本篇文章到此结束,谢谢大家的阅读。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了