0


spring如何解决循环依赖

本文将从spring如何解决循环依赖,以及那些情况下的循环依赖无法解决来分析,由于笔者水平有限,若有错误的地方望各位纠正并讨论。

一、循环引用示列

众所周知再spring中单列bean是支持循环依赖的,下面看代码:

A.class代码:

  1. @Component
  2. public class A {
  3. @Autowired
  4. private B b;
  5. public A(){
  6. System.out.println("init A");
  7. }
  8. }

A中注入了一个B;

B.class代码:

  1. @Component
  2. public class B {
  3. @Autowired
  4. private A a;
  5. public B() {
  6. System.out.println("init B");
  7. }
  8. }

B中注入了A;

ZKConfig.class配置类代码

  1. @Configuration
  2. @ComponentScan(value = "com.config")
  3. public class ZkConfig {
  4. public static void main(String... args) {
  5. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
  6. //往容器注册一个配置类
  7. ac.register(ZkConfig.class);
  8. ac.refresh();
  9. System.out.println(ac.getBean(A.class));
  10. System.out.println(ac.getBean(B.class));
  11. }
  12. }
  13. 打印结果:
  14. init A
  15. init B
  16. com.config.A@44a664f2
  17. com.config.B@7f9fcf7f

通过打印结果,可以发现循环引用是没问题的。但如果我们想关闭循环引用呢?

只需调用一下api进行设置就行:

  1. @Configuration
  2. @ComponentScan(value = "com.config")
  3. public class ZkConfig {
  4. public static void main(String... args) {
  5. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
  6. ac.register(ZkConfig.class);
  7. //关闭循环引用
  8. ac.setAllowCircularReferences(false);
  9. ac.refresh();
  10. System.out.println(ac.getBean(A.class));
  11. System.out.println(ac.getBean(B.class));
  12. }
  13. }

就会看到报错信息:

Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

这个错就是无法支持循环引用报的错,那spring是如何解决的呢?接着来分析。

二、spring的生命周期

  1. 要了解spring如何解决循环依赖的,那么必须要先了解bean如何被创建的即bean的生命周期,下面简单画一下spring的生命周期

1.实列化容器:创建一个spring ApplicationContext 即new AnnotationApplicationContext();创建applicationContext时父类会实列化一个DefaultListableFactory()。

2.调用ac.register()注册一个配置类,后续通过这个类找到我们所有需要注入到容器的bean,也可以通过ac.scanner()扫描包,原理都是一样

3.通过spring内置的beanFactory的后置处理期ConfigurationClassPostProcess解析配置类

4.将解析到的bean封装成beanDefinition放入beanfactory的beanDefinitionMap中用于后续bean的创建

5.遍历beanDefiniton,验证beanDefinition,比如判断这个bean是否为单列或者抽象以及懒加载,就是判断是否需要现在进行实列化

6.将需要进行实列化的bean,首先需推导出一个用来实列化的构造函数进行实列化

7.此时还是还是普通的对象,属性还为空,除非是通过构造函数进行依赖注入的bean,需要进行属性注入,大多数情况下,循环依赖就发生在这里,依赖注入主要是通过AutowireAnnotationBeanPostProcessor和CommonAnnotationBeanPostOrocessor进行的,依赖注入实际上还是通过getBean()从容器中获取一个bean出来,然后通过反射给属性赋值

7.若bean实现了Aware接口会进行回调

8.执行初始化方法如@PostConstruct和实现了isInitializingBean接口和通过xml注入的bean,设置了init-method属性的

9.执行bean后置处理期的after方法,若未出现循环依赖没有提前暴露bean,aop就会发生再这里

10.将bean放入单列池中

11.注册bean的销毁逻辑,销毁bean

大致了解了spring的生命周期后现在我们来讨论一下依赖注入:

下面先看一下方法栈的调用图

可以看到真正创建一个bean之前会先通过红色标记的#DefaultSingleBeanRegistry.getSingleton()先获取一次:

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. //从一级缓存中获取
  3. Object singletonObject = this.singletonObjects.get(beanName);
  4. //若一级缓存中没有,且当前bean正在创建中
  5. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  6. synchronized (this.singletonObjects) {
  7. //从三级缓存中获取
  8. singletonObject = this.earlySingletonObjects.get(beanName);
  9. /**
  10. * 三级缓存中没有,且允许循环依赖
  11. * 三级缓存作用:若涉及到三个及以上对象循环依赖,此时就可以直接从三级缓存中获取到值
  12. */
  13. if (singletonObject == null && allowEarlyReference) {
  14. //从从二级缓存中获取,这里获取到的是一个对象工厂
  15. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  16. if (singletonFactory != null) {
  17. /**
  18. * 从对象工厂中获取到一个半成品bean
  19. */
  20. singletonObject = singletonFactory.getObject();
  21. //放入到三级缓存
  22. this.earlySingletonObjects.put(beanName, singletonObject);
  23. //从二级缓存中移除
  24. this.singletonFactories.remove(beanName);
  25. }
  26. }
  27. }
  28. }
  29. return singletonObject;
  30. }

我们先看一下这里面涉及到的三个map:

通过代码层面我们可以看到,当从一级缓存获取获取bean实列后进行if判断

  1. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
  1. bean还未创建完成singletonObject一定为空,但这isSingletonCurrentlyInCreation(beanName)是是如何判断的呢?点进去可以看到从判断是否再一个set集合中存在

singletonsCurrentlyInCreation.contains(beanName);
通过集合名字可以很清楚的了解到这个集合存储得是正在创建的bean的名字,那么是从那里put进去的呢?

是在它的重载方法#DefaultSingleBeanRegistry.getSingleton(String beanName, ObjectFactory<?> singletonFactory)中的beforeSingletonCreation()实列化前会先将beanName放入集合中表示正在创建

  1. beforeSingletonCreation(beanName);
  2. protected void beforeSingletonCreation(String beanName) {
  3. if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
  4. throw new BeanCurrentlyInCreationException(beanName);
  5. }
  6. }

但是bean第一次创建这个肯定也为空 不会这个if.那接下来我们看一下二级缓存是在那里放入的

可以看到是在对象实列化后

  1. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
  2. isSingletonCurrentlyInCreation(beanName));
  3. if (earlySingletonExposure) {
  4. if (logger.isTraceEnabled()) {
  5. logger.trace("Eagerly caching bean '" + beanName +
  6. "' to allow for resolving potential circular references");
  7. }
  8. //添加二级缓存
  9. addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  10. }
  11. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  12. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  13. synchronized (this.singletonObjects) {
  14. if (!this.singletonObjects.containsKey(beanName)) {
  15. //添加二级缓存 将匿名对象singletonFactory加入,就是一系列BeanPostprocessor
  16. this.singletonFactories.put(beanName, singletonFactory);
  17. //从三级级缓存中移除
  18. this.earlySingletonObjects.remove(beanName);
  19. //添加到注册的单列集合中
  20. this.registeredSingletons.add(beanName);
  21. }
  22. }
  23. }

这里boolean判断时有一个属性allowCircularReferences ,这也在前面我们通过api将这个属性设置为false时循环依赖就会报错的原因,实列化前就将bean放入set集合中了,earlySingletonExposure 这个属性为true,objectFactory就会被添加到二级缓存中,下面我们我们看一看添加的到底是一个什么玩意。

  1. protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  2. Object exposedObject = bean;
  3. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
  4. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  5. if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
  6. SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
  7. exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
  8. }
  9. }
  10. }
  11. return exposedObject;
  12. }

代码执行到ObjectFactory.getObject()方法时就会调用getEarlyBeanRefrence(),这里主要就是执行实现了SmartInstantiationAwareBeanPostProcessor的后置处理期,aop就会再这里执行,具体aop怎么执行的就不再这里讨论了 aop的内容点有点多,后面可以单独出一篇文章整理。

至此二级缓存添加完毕,接下来看依赖注入,依赖注入就可以简单理解成调用getBean()从容器中获取,现在我们就来梳理循环引用时依赖注入是如何完成的。

结合上面的代码和图可以看出a注入b时,b进行创建,b再次注入a此时时调用getBean()方法,流程自然会进入前面画的方法栈的调用图的#DefaultSingleBeanRegistry.getSingleton(String beanName, boolean allowEarlyReference)方法。此时我们再来看这段代码

  1. //此时beanName=a,allowEarlyReference=true
  2. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  3. //从单列池中中无法获取到a
  4. Object singletonObject = this.singletonObjects.get(beanName);
  5. //此时a==null,但是此时a是正在创建中的所以 isSingletonCurrentlyInCreation(a)==true
  6. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  7. synchronized (this.singletonObjects) {
  8. //从三级缓存中获取,此时未放入三级缓存,singletonObject==null
  9. singletonObject = this.earlySingletonObjects.get(beanName);
  10. //从前面的结果可以看出,这里肯定会进
  11. if (singletonObject == null && allowEarlyReference) {
  12. /**从从二级缓存中获取,beanName==a
  13. *a再实列化后就会添加一个对象工厂singletonFactories中
  14. *所以这里会返回一个ObjectFactory对象,此时singletonFactory不等于空
  15. **/
  16. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  17. if (singletonFactory != null) {
  18. //调用getObject()方法,实则会调用到前面传入的匿名函数getEarlyBeanReference()方法中
  19. //但由于a并未存在aop的逻辑,所以这里会返回一个a对象,就是实列化后的对象a
  20. singletonObject = singletonFactory.getObject();
  21. //放入到三级缓存
  22. this.earlySingletonObjects.put(beanName, singletonObject);
  23. //从二级缓存中移除
  24. this.singletonFactories.remove(beanName);
  25. }
  26. }
  27. }
  28. }
  29. return singletonObject;
  30. }

这里就会返回一个半成品的对象a,b完成属性注入后紧接着做b的生命周期的其它事,完成后将b放入单列池中,只不过b中属性a还是一个半成品;

此时栈调用就会返回到 a 属性注入b那里,a的属性注入也完成了,a也做自己生命周期的其他事,完成后将a放入单列池中,此时a和b的bean创建都完成了,b中的a也就是一个具有完整生命周期的bean,因为b中的属性a的指针始终指向的这个对象a。

有的读者可能就会问那么这个三级缓存的意义再那里呢,似乎也没用到

当我们存在三个以及三个以上的bean循环引用时,这个三级缓存就有用了吧比如:

  1. public A{
  2. @Autowired
  3. private B b;
  4. }
  5. public B{
  6. @Autowired
  7. private A a;
  8. @Autowired
  9. private C c;
  10. }
  11. public C{
  12. @Autowired
  13. private A a;
  14. }
  15. B注入C时,C进行依赖注入时,a就可以直接从三级缓存中获取

当我们完成二级缓存的beanPostProcessor的方法调用时,放入三级缓存,这样其它bean依赖这个bean时就可以直接从三级缓存earlySingletonObjects中获取,没必要再执行二级缓存的逻辑了提高了获取的效率。

那么这个二级缓存的作用,为什么不把对象直接放在三级缓存,那个这个二级缓存就可以不用了?

我么可以想象一下如果a需要进行aop,二级缓存中直接存储的就是一个普通对象a,b对a注入完毕,b中的属性a指针指向的是一个普通的对象,
b的生命周期走完后,此时a进行aop,此时a就是一个代理对象,那么b指向的a就完全没意义了,所以这里需要一个工厂对象对a进行提前暴露,好让b中属性a指向的对象也是一个代理对象

至于为什么spring不直接先提前暴露,再放入二级缓存,这样就可以不用到三级缓存了,这点笔者就不是很清楚了 我猜测,可能作者认为循环依赖且依赖的bean需进行aop的情况比较少,作者还是希望,没有循环依赖的情况下,bean还是最后进行aop,不想提前暴露
这段提前暴露的逻辑也可能不止aop处理

什么情况下循环依赖会失败

1.通过构造函数注入,构造函数注入是不会走添加二级缓存那段逻辑,
原型bean注入同样也不走那段逻辑所以也无法支持循环依赖
2.当加了@Transaction和@Async的两个bean进行循环引用时,
也可能会出问题,当@Async的bean比@Transaction先进行创建时就会报错
@Async的代理是通过一个单独的后置处理期AsyncAnnotationBeanPostProcessor添加的切面,
不会走提前暴露就是getEarlyBeanRefrence()那段逻辑
而@Transaction是会走提前暴露的,这里就不详细展开了感兴趣的可以自行了解

标签: spring java

本文转载自: https://blog.csdn.net/weixin_43716742/article/details/124448697
版权归原作者 只会摸鱼的coder 所有, 如有侵权,请联系我们删除。

“spring如何解决循环依赖”的评论:

还没有评论