spring源码解析(五) 循环依赖

1.什么是循环依赖?

Bean A → Bean B → Bean A

在A对象生命周期中注入B,进入B生命周期找A,但A还不存在,继续进入A对象生命周期找B,这就是循环依赖。

2.循环依赖造成的结果

当Spring正在加载所有Bean时,Spring尝试以能正常创建Bean的顺序去创建Bean。这样会抛出异常。

┌─────┐
|  testA defined in class path resource [com/chuan/config/TestConfig.class]
↑     ↓
|  testB defined in class path resource [com/chuan/config/TestConfig.class]
└─────┘

3.逻辑分析

用代码看就是这样:但这只是普通的java代码,而不是spring的bean生命周期。

//实例化A
A a =new A();
//填充A属性,需要注入B,所以进入B的生命周期
B b =new B();
//b实例拿到了a的原始对象,注入进去
b.a=a;
//填充B属性
b.x=1;
//b生命周期结束,回到A的属性填充,填充A属性
a.b=b;
//填充A其他属性
a.x=1;

Bean的生成步骤如下(简化):

  1. 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
  2. 填充原始对象中的属性(依赖注入)
  3. 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
  4. 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可

暂时先一个demo分析一下怎么解决循环依赖:

demo1    A依赖于B,B依赖于A

A生命周期

1.实例化A(new A())--->a的原始对象--->存在一个map中

2.填充b属性-->从单例池里拿B对应的bean-->没拿到B--->创建B对应的Bean

3.填充其他属性

4.初始化后

5.添加到单例池

B生命周期

1.实例化B(new B())--->b的原始对象

2.填充a属性-->从单例池里拿a对应的bean--->找不到-->去这个map中找-->拿到a的原始对象

3.填充其他属性

4.初始化后

5.添加到单例池

这样来看两级循环就可以完成,一级缓存为单例池,二级缓存存生成的原始对象。

那为什么需要三级缓存呢?——只有两级缓存会存在代理对象和注入的对象不是同一个,这肯定是不合理的。

demo2

看一下demo这个流程:

A依赖于B、C

B依赖于A

C依赖于A

A生命周期

0.标记一下A正在进行创建

1.实例化A(new A())--->a的原始对象--->存入到第三级缓存

2.填充b属性-->从单例池里拿B对应的bean-->找不到-->创建B对应的Bean

3.填充c属性--->从单例池里拿C对应的bean-->找不到-->创建C对应的Bean

4.初始化后-》进行aop,从第二级缓存取出a代理对象

5.将代理对象添加到单例池(第一级缓存)

B生命周期

1.实例化B(new B())--->b的原始对象-->.........

2.填充a属性-->从单例池里拿a对应的bean--->找不到--》第二级缓存--->找不到---->发现a正在创建(也就是发现了需要循环依赖)---->第三级缓存---->执行lambda--->

进行aop--->获取a的代理对象---->存入到第二级缓存,并删除第三级缓存。

3.填充其他属性

4.初始化后

5.添加到单例池

C生命周期

1.实例化C(new C())--->c的原始对象-->.........

2.填充a属性-->从单例池里拿a对应的bean--->找不到--》第二级缓存--->获取到a代理对象(因为先执行了B,已经将第二级缓存缓存进去了)

3.填充其他属性

4.初始化后

5.添加到单例池

4.源码分析

在doCreateBean方法中,看这段代码:

    // 如果当前创建的是单例bean,并且允许循环依赖,并且还在创建过程中,那么则提早暴露
      boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
      if (earlySingletonExposure) {
         if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                  "' to allow for resolving potential circular references");
         }
         // 此时的bean还没有完成属性注入,是一个非常简单的对象
         // 构造一个对象工厂添加到singletonFactories中,这时只是存一下第三级缓存
         addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));  // AService
//       addEarlySingleton(beanName, bean);
      }

只是将第三级缓存存进了singletonFactories里,并没有执行,因为它也不知道会不会出现循环依赖。

向下继续看,执行完属性填充和初始化后,一直到这。

if (earlySingletonExposure) {
   // 在解决循环依赖时,当a的属性注入完了之后,从缓存中中得到a AOP之后的代理对象
   Object earlySingletonReference = getSingleton(beanName, false);  // earlySingletonObjects
   if (earlySingletonReference != null) {
      // 如果提前暴露的对象和经过了完整的生命周期后的对象相等,则把代理对象赋值给exposedObject
      // 最终会添加到singletonObjects中去
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
   }
      // 如果提前暴露的对象和经过了完整的生命周期后的对象不相等
      // allowRawInjectionDespiteWrapping表示在循环依赖时,只能
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
         String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
         if (!actualDependentBeans.isEmpty()) {
            // a的原始对象被注入给了其他bean,但是a最后被包装了
            // 也就是说其他bean没有用到a的最终版本
            throw new BeanCurrentlyInCreationException(beanName,
                  "Bean with name '" + beanName + "' has been injected into other beans [" +
                  StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                  "] 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.");
         }
      }
   }
}

进入getSingleton方法查看,如何进行获取三级缓存中的内容。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //从单例池(1级缓存)中拿bean
   Object singletonObject = this.singletonObjects.get(beanName);
    //没有从单例池获取到,并且这个单例正在创建中
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         // 从earlySingletonObjects(二级缓存)中获取
         singletonObject = this.earlySingletonObjects.get(beanName);
         //没有从二级缓存中获取到,并且这个对象存在早期引用
         if (singletonObject == null && allowEarlyReference) {
            // 从singletonFactories(三级缓存)中获取
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                //执行之前未执行的lambda表达式,获取到一个对象
               singletonObject = singletonFactory.getObject();  
               //将三级缓存的对象放入到二级缓存中,清除该beanName的三级缓存
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

5.特殊情况

有两个情况循环依赖是不能解决的:

1.两个都是原型bean也不能依赖

因为每次都是重新创建一个新的bean,所以出现循环 A->B->A->B->A

解决方法:将其中一个作用域改为单例

2.两个都使用构造方法注入也不行

因为第三级缓存是在实例化后进行的,所以在构造方法上注入,会导致三层缓存都找不到相应的bean,从而无限循环。

解决方法:在其中一个上加懒加载

6.总结

总结一下三级缓存:

  1. singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean
  2. earlySingletonObjects:缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的BeanPostProcessor等生命周期
  3. singletonFactories:缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值