1 什么是循环依赖?
如下图所示:
BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类。这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖。同理,再如下图的情况:
上图中,BeanA类依赖了BeanB类,BeanB类依赖了BeanC类,BeanC类依赖了BeanA类,如此,也形成了一个依赖闭环。再比如:
上图中,自己引用了自己,自己和自己形成了依赖关系。同样也是一个依赖闭环。那么,如果出现此类循环依赖的情况,会出现什么问题呢?
2 循环依赖问题复现
2.1 定义依赖关系
我们继续扩展前面的内容,给ModifyService增加一个属性,代码如下:
1 | java复制代码 |
给QueryService增加一个属性,代码如下:
1 | java复制代码 |
如此,ModifyService依赖了QueryService,同时QueryService也依赖了ModifyService,形成了依赖闭环。那么这种情况下会出现什么问题呢?
2.2 问题复现
我们来运行调试一下之前的代码,在GPApplicationContext初始化后打上断点,我们来跟踪一下IoC容器里面的情况,如下图:
启动项目,我们发现只要是有循环依赖关系的属性并没有自动赋值,而没有循环依赖关系的属性均有自动赋值,如下图所示:
这种情况是怎么造成的呢?我们分析原因之后发现,因为,IoC容器对Bean的初始化是根据BeanDefinition循环迭代,有一定的顺序。这样,在执行依赖注入时,需要自动赋值的属性对应的对象有可能还没初始化,没有初始化也就没有对应的实例可以注入。于是,就出现我们看到的情况。
3 使用缓存解决循环依赖问题
3.1 定义缓存
具体代码如下:
1 | java复制代码 |
3.2 判断循环依赖
增加getSingleton()方法:
1 | java复制代码 |
3.3 添加缓存
修改getBean()方法,在getBean()方法中添加如下代码:
1 | java复制代码 |
3.4 添加依赖注入
修改populateBean()方法,代码如下:
1 | java复制代码 |
4 循环依赖对AOP创建代理对象的影响
4.1 循环依赖下的代理对象创建过程
我们都知道Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器来自动完成的。也就是说Spring最终给我们放进容器里面的是一个代理对象,而非原始对象。
这里我们结合循环依赖,再分析一下AOP代理对象的创建过程和最终放进容器内的动作,看如下代码:
1 | java复制代码 |
此Service类使用到了事务,所以最终会生成一个JDK动态代理对象Proxy。刚好它又存在自己引用自己的循环依赖的情况。跟进到Spring创建Bean的源码部分,来看doCreateBean()方法:
1 | java复制代码 |
以上代码分析的是代理对象有自己存在循环依赖的情况,Spring用三级缓存很巧妙的进行解决了这个问题。
4.2 非循环依赖下的代理对象创建过程
如果自己并不存在循环依赖的情况,Spring的处理过程就稍微不同,继续跟进源码:
1 | java复制代码 |
根据以上代码分析可知,只要用到代理,没有被循环引用的,最终存在Spring容器里缓存的仍旧是代理对象。如果我们关闭Spring容器的循环依赖,也就是把allowCircularReferences设值为false,那么会不会出现问题呢?先关闭循环依赖开关。
1 | java复制代码 |
关闭循环依赖后,上面代码中存在A、B循环依赖的情况,运行程序会出现如下异常:
1 | java复制代码 |
此处异常类型也是BeanCurrentlyInCreationException异常,但报错位置在DefaultSingletonBeanRegistry.beforeSingletonCreation
我们来分析一下,在实例化A后给其属性赋值时,Spring会去实例化B。B实例化完成后会继续给B属性赋值,由于我们关闭了循环依赖,所以不存在提前暴露引用。因此B无法直接拿到A的引用地址,只能又去创建A的实例。而此时我们知道A其实已经正在创建中了,不能再创建了。所有就出现了异常。对照演示代码,来分析一下程序运行过程:
1 | java复制代码 |
其大致运行步骤如下:
1 | java复制代码 |
由上面代码可知,即使关闭循环依赖开关,最终缓存到容器中的对象仍旧是代理对象,显然@Autowired给属性赋值的也一定是代理对象。
最后,以AbstractAutoProxyCreator为例看看自动代理创建器实现循环依赖代理对象的细节。
AbstractAutoProxyCreator是抽象类,它的三大实现子类InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator小伙伴们应该比较熟悉,该抽象类实现了创建代理的动作:
1 | java复制代码 |
根据以上分析可得知,自动代理创建器它保证了代理对象只会被创建一次,而且支持循环依赖的自动注入的依旧是代理对象。由上面分析得出结论,在Spring容器中,不论是否存在循环依赖的情况,甚至关闭Spring容器的循环依赖功能,它对Spring AOP代理的创建流程有影响,但对结果是无影响的。也就是说Spring很好地屏蔽了容器中对象的创建细节,让使用者完全无感知。
本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注『 Tom弹架构 』可获取更多技术干货!
本文转载自: 掘金