【Spring】-循环依赖

要讲清楚三级缓存解决set循环依赖问题先要讲清除两个问题,一是spring-ioc
会存在哪几种循环依赖的情况以及哪种循环依赖是无解的,哪种是框架会帮我们处理的
,二是getBean()这个方法干了什么,以及里面用到的三级缓存是怎么优雅的解决循环依赖的

Spring三种循环依赖

引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错。下面说一下Spring是如果解决循环依赖的。

构造器参数循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyIn CreationException异常表示循环依赖。
ServiceA依赖了ServiceB,同时ServiceB依赖了ServiceA,并且都是构造器注入的方式(需要两者都是相互构造器注入)

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ServiceA {

private ServiceB serviceB;

public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}

}

public class ServiceB {

private ServiceA serviceA;

public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}

}

走一下:

1
2
3
4
5
6
7
8
9
10
11
12
@Import({ServiceA.class, ServiceB.class})
public class MainConfig {

}

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotation =
new AnnotationConfigApplicationContext(MainConfig.class);
}
}

果不其然:

1
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'com.springboot.demo.scan.component.ServiceA': Requested bean is currently in creation: Is there an unresolvable circular reference?

why?:
稍后再说吧。。先把另外两种情况再讲下

set单例循环注入

众所周知,如果两个相互依赖的bean有一方是set注入对方的,那么就会成功注入了
Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ServiceA {

@Resource
private ServiceB serviceB;

public ServiceB getServiceB() {
return serviceB;
}

public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}

public class ServiceB {

private ServiceA serviceA;

public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}

启动和配置bean的代码就不贴了,同上
走一把,你会发现程序正常运行,完全OJBK

set原型方式注入

原型注入的话,循环依赖是一定会报错的
Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Scope("prototype")
public class ServiceA {

@Resource
private ServiceB serviceB;

public ServiceB getServiceB() {
return serviceB;
}

public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}

public class ServiceB {

private ServiceA serviceA;

public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}

run一下:

1
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'com.springboot.demo.scan.component.ServiceB': Requested bean is currently in creation: Is there an unresolvable circular reference?

总结一下

1.相互都是构造器注入,Spring解决不了
2.set单例注入,Spring可以解决
3.set原型注入,Spring解决不了

Spring循环依赖的理论依据

Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者或属性是可以延后设置的。

Spring单例对象的初始化其实可以分为三步:(实例化、填充属性、初始化)

  • createBeanInstance, 实例化,实际上就是调用对应的构造方法构造对象,此时只是调用了构造方法。
  • populate:populateBean,填充属性,这步对spring xml中指定的property进行populate
  • initializeBean,调用spring xml中指定的init方法,或者AfterPropertiesSet方法

会发生循环依赖的步骤集中在第一步和第二步。
大致调用栈绘图如下:

Spring容器的’三级缓存’
在Spring容器的整个声明周期中,单例Bean有且仅有一个对象。这很容易让人想到可以用缓存来加速访问。
从源码中也可以看出Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至不惜使用了“三级缓存”,这也便是它设计的精妙之处~

介绍一下三级缓存:

  • singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
  • earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
  • singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖

获取单例Bean的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

简单解析:

  1. 从一级缓存singletonObjects中去获取。(如果获取到就直接return)
  2. 如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
  3. 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)
    加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

    为什么相互都是构造器注入方式解决不了

    大家从getBean()这个方法debug进去看,总的流程可以整理为下图(我抄过来的)

    依旧以上面A、B类使用属性field注入循环依赖的例子为例,对整个流程做文字步骤总结如下:
  4. 使用context.getBean(A.class),旨在获取容器内的单例A(若A不存在,就会走A这个Bean的创建流程),显然初次获取A是不存在的,因此走A的创建之路~
  5. 实例化A(注意此处仅仅是实例化),并将它放进缓存(此时A已经实例化完成,已经可以被引用了)
  6. 初始化A:@Autowired依赖注入B(此时需要去容器内获取B)
  7. 为了完成依赖注入B,会通过getBean(B)去容器内找B。但此时B在容器内不存在,就走向B的创建之路~
  8. 实例化B,并将其放入缓存。(此时B也能够被引用了)
  9. 初始化B,@Autowired依赖注入A(此时需要去容器内获取A)
  10. 此处重要:初始化B时会调用getBean(A)去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)能够正常返回
  11. B初始化成功(此时已经注入A成功了,已成功持有A的引用了),return(注意此处return相当于是返回最上面的getBean(B)这句代码,回到了初始化A的流程中~)。
    因为B实例已经成功返回了,因此最终A也初始化成功
  12. 到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B,完美~
  13. 站的角度高一点,宏观上看Spring处理循环依赖的整个流程就是如此。希望这个宏观层面的总结能更加有助于小伙伴们对Spring解决循环依赖的原理的了解,同时也顺便能解释为何构造器循环依赖就不好使的原因。

如果两个循环依赖的对象都是相互构造器注入,可想而知,在实例化A的时候,此时就需要入参为B的实例化对象的引用,但是此时B的实例化又依赖A的实例化对象引用,这样就GG了

为什么set单例的注入方式可以解决

请参照上图以及上面10个步骤,大家就明白了。

为什么原型的注入方式解决不了

请看源代码,从getBean()方法追进去,会发现一层这样的判断,如果是单例的才执行一系列操作,否则GG。直接第一步createBeanInstance()都不会进去,因为原型bean都是在需要使用的时候再创建一个出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

思考

但是其实这还存在一层思考,为什么要用三级缓存,按照这个思想,实际两级缓存就可以了吧。
这也是Spring设计的巧妙之处,和AOP相关,我们下次再讨论。

-------------本文结束感谢您的阅读-------------