1.由共事抛的一个问题开始
最近项目组的一个共事遇到了一个问题,问我的意见,一下子引起的我的趣味,因为这个问题我也是第一次遇到。平时自认为对spring循环依赖问题还是比拟理解的,直到遇到这个和前面的几个问题后,从新刷新了我的意识。
咱们先看看过后出问题的代码片段:
@Servicepublicclass TestService1 { @Autowired private TestService2 testService2; @Async public void test1() { }}
@Servicepublicclass TestService2 { @Autowired private TestService1 testService1; public void test2() { }}
这两段代码中定义了两个Service类:TestService1和TestService2,在TestService1中注入了TestService2的实例,同时在TestService2中注入了TestService1的实例,这里形成了循环依赖。
只不过,这不是一般的循环依赖,因为TestService1的test1办法上加了一个@Async
注解。
大家猜猜程序启动后运行后果会怎么?
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
报错了。。。起因是呈现了循环依赖。
「不迷信呀,spring不是号称能解决循环依赖问题吗,怎么还会呈现?」
如果把下面的代码略微调整一下:
@Servicepublicclass TestService1 { @Autowired private TestService2 testService2; public void test1() { }}
把TestService1的test1办法上的@Async注解去掉,TestService1和TestService2都须要注入对方的实例,同样形成了循环依赖。
然而重新启动我的项目,发现它可能失常运行。这又是为什么?
带着这两个问题,让咱们一起开始spring循环依赖的探秘之旅。
2.什么是循环依赖?
循环依赖:说白是一个或多个对象实例之间存在间接或间接的依赖关系,这种依赖关系形成了形成一个环形调用。
第一种状况:本人依赖本人的间接依赖
第二种状况:两个对象之间的间接依赖
第三种状况:多个对象之间的间接依赖
后面两种状况的间接循环依赖比拟直观,十分好辨认,然而第三种间接循环依赖的状况有时候因为业务代码调用层级很深,不容易辨认进去。
3.循环依赖的N种场景
spring中呈现循环依赖次要有以下场景:
单例的setter注入
这种注入形式应该是spring用的最多的,代码如下:
@Servicepublicclass TestService1 { @Autowired private TestService2 testService2; public void test1() { }}
@Servicepublicclass TestService2 { @Autowired private TestService1 testService1; public void test2() { }}
这是一个经典的循环依赖,然而它能失常运行,得益于spring的外部机制,让咱们根本无法感知它有问题,因为spring默默帮咱们解决了。
spring外部有三级缓存:
- singletonObjects 一级缓存,用于保留实例化、注入、初始化实现的bean实例
- earlySingletonObjects 二级缓存,用于保留实例化实现的bean实例
- singletonFactories 三级缓存,用于保留bean创立工厂,以便于前面扩大有机会创立代理对象。
上面用一张图通知你,spring是如何解决循环依赖的:
图1
仔细的敌人可能会发现在这种场景中第二级缓存作用不大。
那么问题来了,为什么要用第二级缓存呢?
试想一下,如果呈现以下这种状况,咱们要如何解决?
@Servicepublicclass TestService1 { @Autowired private TestService2 testService2; @Autowired private TestService3 testService3; public void test1() { }}
@Servicepublicclass TestService2 { @Autowired private TestService1 testService1; public void test2() { }}
@Servicepublicclass TestService3 { @Autowired private TestService1 testService1; public void test3() { }}
TestService1依赖于TestService2和TestService3,而TestService2依赖于TestService1,同时TestService3也依赖于TestService1。
依照上图的流程能够把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。
假如不必第二级缓存,TestService1注入到TestService3的流程如图:
图2
TestService1注入到TestService3又须要从第三级缓存中获取实例,而第三级缓存里保留的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创立的实例对象每次可能都不一样的。
这样不是有问题?
为了解决这个问题,spring引入的第二级缓存。下面图1其实TestService1对象的实例曾经被增加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。
图3
还有个问题,第三级缓存中为什么要增加ObjectFactory对象,间接保留实例对象不行吗?
答:不行,因为如果你想对增加到三级缓存中的实例对象进行加强,间接用实例对象是行不通的。
针对这种场景spring是怎么做的呢?
答案就在AbstractAutowireCapableBeanFactory类doCreateBean办法的这段代码中:
它定义了一个匿名外部类,通过getEarlyBeanReference办法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。
多例的setter注入
这种注入办法偶尔会有,特地是在多线程的场景下,具体代码如下:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Servicepublicclass TestService1 { @Autowired private TestService2 testService2; public void test1() { }}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Servicepublicclass TestService2 { @Autowired private TestService1 testService1; public void test2() { }}
很多人说这种状况spring容器启动会报错,其实是不对的,我十分负责任的通知你程序可能失常启动。
为什么呢?
其实在AbstractApplicationContext类的refresh办法中通知了咱们答案,它会调用finishBeanFactoryInitialization办法,该办法的作用是为了spring容器启动的时候提前初始化一些bean。该办法的外部又调用了preInstantiateSingletons办法
标红的中央显著可能看出:非形象、单例 并且非懒加载的类能力被提前初始bean。
而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序可能失常启动。
如何让他提前初始化bean呢?
只须要再定义一个单例的类,在它外面注入TestService1
@Servicepublicclass TestService3 { @Autowired private TestService1 testService1;}
重新启动程序,执行后果:
Requested bean is currently in creation: Is there an unresolvable circular reference?
果然呈现了循环依赖。
留神:这种循环依赖问题是无奈解决的,因为它没有用缓存,每次都会生成一个新对象。
结构器注入
这种注入形式是spring4.x以上的版本中官网举荐的形式,具体如下代码:
@Servicepublicclass TestService1 { public TestService1(TestService2 testService2) { }}
@Servicepublicclass TestService2 { public TestService2(TestService1 testService1) { }}
运行后果:
Requested bean is currently in creation: Is there an unresolvable circular reference?
呈现了循环依赖,为什么呢?
从图中的流程看出结构器注入只是增加了三级缓存,并没有应用缓存,所以也无奈解决循环依赖问题。
单例的代理对象setter注入
这种注入形式其实也比拟罕用,比方平时应用:@Async
注解的场景,会通过AOP主动生成代理对象。
我那位共事的问题也是这种状况。
@Servicepublicclass TestService1 { @Autowired private TestService2 testService2; @Async public void test1() { }}
@Servicepublicclass TestService2 { @Autowired private TestService1 testService1; public void test2() { }}
从后面得悉程序启动会报错,呈现了循环依赖:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
为什么会循环依赖呢?
答案就在上面这张图中:
说白了,bean初始化实现之后,前面还有一步去查看:第二级缓存 和 原始对象 是否相等。因为它对后面流程来说无关紧要,所以后面的流程图中省略了,然而在这里是关键点,咱们重点说说:
那位共事的问题正好是走到这段代码,发现第二级缓存 和 原始对象不相等,所以抛出了循环依赖的异样。
如果这时候把TestService1改个名字,改成:TestService6,其余的都不变。
@Servicepublicclass TestService6 { @Autowired private TestService2 testService2; @Async public void test1() { }}
再重新启动一下程序,神奇般的好了。
what? 这又是为什么?
这就要从spring的bean加载程序说起了,默认状况下,spring是依照文件残缺门路递归查找的,按门路+文件名排序,排在后面的先加载。所以TestService1比TestService2先加载,而改了文件名称之后,TestService2比TestService6先加载。
为什么TestService2比TestService6先加载就没问题呢?
答案在上面这张图中:
这种状况testService6中其实第二级缓存是空的,不须要跟原始对象判断,所以不会抛出循环依赖。
DependsOn循环依赖
还有一种有些非凡的场景,比方咱们须要在实例化Bean A之前,先实例化Bean B,这个时候就能够应用@DependsOn注解。
@DependsOn(value = "testService2")@Servicepublicclass TestService1 { @Autowired private TestService2 testService2; public void test1() { }}
@DependsOn(value = "testService1")@Servicepublicclass TestService2 { @Autowired private TestService1 testService1; public void test2() { }}
程序启动之后,执行后果:
Circular depends-on relationship between 'testService2' and 'testService1'
这个例子中原本如果TestService1和TestService2都没有加@DependsOn注解是没问题的,反而加了这个注解会呈现循环依赖问题。
这又是为什么?
答案在AbstractBeanFactory类的doGetBean办法的这段代码中:
它会查看dependsOn的实例有没有循环依赖,如果有循环依赖则抛异样。
4.呈现循环依赖如何解决?
我的项目中如果呈现循环依赖问题,阐明是spring默认无奈解决的循环依赖,要看我的项目的打印日志,属于哪种循环依赖。目前蕴含上面几种状况:
生成代理对象产生的循环依赖
这类循环依赖问题解决办法很多,次要有:
- 应用@Lazy注解,提早加载
- 应用@DependsOn注解,指定加载先后关系
- 批改文件名称,扭转循环依赖类的加载程序
应用@DependsOn产生的循环依赖
这类循环依赖问题要找到@DependsOn注解循环依赖的中央,迫使它不循环依赖就能够解决问题。
多例循环依赖
这类循环依赖问题能够通过把bean改成单例的解决。
结构器循环依赖
这类循环依赖问题能够通过应用@Lazy注解解决。
当然最好的解决循环依赖问题最佳计划是从代码设计上躲避,然而简单的零碎中有可能没法防止。
最初说一句(求关注,别白嫖我)
如果这篇文章对您有所帮忙,或者有所启发的话,帮忙扫描下发二维码关注一下,您的反对是我保持写作最大的能源。
求一键三连:点赞、转发、在看。
在公众号中回复:面试、代码神器、开发手册、工夫治理有超赞的粉丝福利,另外回复:加群,能够跟很多BAT大厂的前辈交换和学习。
本文由博客群发一文多发等经营工具平台 OpenWrite 公布