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 公布