欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

7.Spring循环依赖-三级缓存

程序员文章站 2022-03-06 12:17:32
Spring循环依赖循环依赖是什么:伪代码:// A依赖了Bclass A{ public B b;}// B依赖了Aclass B{ public A a;}如果在Java中这样实现是没有问题的,但是在Spring中,这样就存在问题,因为Spring中的Bean对象并不是简单的new出来的,而是经过一系列的bean的生命周期创建出来的,所以在A还未创建时,B使用A时就会因为找不到A对象而报错,同样也可能在A创建后使用B的时候,B还没有创建的情况。简单来说一下...

Spring循环依赖

  1. 循环依赖是什么:

    伪代码:

    // A依赖了B
    class A{
        public B b;
    }
    
    // B依赖了A
    class B{
        public A a;
    }
    

    如果在Java中这样实现是没有问题的,但是在Spring中,这样就存在问题,因为Spring中的Bean对象并不是简单的new出来的,而是经过一系列的bean的生命周期创建出来的,所以在A还未创建时,B使用A时就会因为找不到A对象而报错,同样也可能在A创建后使用B的时候,B还没有创建的情况。

  2. 简单来说一下bean的生命周期:

    1. 扫描class文件创建对应BeanDefinition
    2. 根据BeanDefinition创建bean
    3. 首先根据class文件推断构造方法
    4. 确定构造方法后,反射得到一个对象bean对象(原始对象)
    5. 接着对bean对象的属性填充,以及其他属性的填充
    6. 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
    7. 将最终生成的对象放入单例池中,下次getBean中直接从单例池中拿

    可以看到一个Bean创建是经历了很多步骤的,比如Aware回调,初始化等。而上面的代码放在spring中就可以这样解释:

    ABean创建-》发现依赖BBean-》触发BBean生命周期创建-》BBean发现依赖ABean-》而ABean正在创建中;

    形成了循环依赖,导致ABean、BBean都无法创建。

    这就是spring中循环依赖的场景。

    spring中提供了解决循环依赖的机制-》三级缓存

1. 三级缓存演变思路

放几张周瑜大都督的图:

循环依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bkBtMrse-1609374445190)(F:\MyFile\学习笔记\Java学习笔记\6.Spring框架\5.Spring原理\5.图片素材\23.循环依赖.png)]

打破这个循环依赖,加入中间人(缓存)

1. 第一级缓存

  1. 单例池:singletonObjects, Map<beanName,Object>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uTsxEO2V-1609374445194)(F:\MyFile\学习笔记\Java学习笔记\6.Spring框架\5.Spring原理\5.图片素材\23.循环依赖2.png)]

A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时A的Bean依赖了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B的原始对象,然后把B的原始对象提早暴露出来放入缓存中,然后在对B的原始对象进行依赖注入A,此时能从缓存中拿到A的原始对象(虽然是A的原始对象,还不是最终的Bean),B的原始对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。

因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始对象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。

2. 第二级缓存earlySingletonObjects

用课程中的案例说明:

AService的生命周期
1. 实例化AService(new AService),AService原始对象-》将对象放入Map中,该对象为原始对象AService,各属性未赋值
2. 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池

BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》到Map中去找-》找到AService原始对象赋值给BService
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池

上述一级缓存会产生一个问题放入Map中的对象是一个原始对象,属性未赋值,那么此时我们是将原始对象放入单例池中,还是将AService代理对象放入单例池中呢?

应该放入代理对象,因为在AOP中生成的代理对象属性已经赋值,而不是空值,如果仍然放入原始对象,那么AOP的赋值有什么用嘞。于是问题就又产生了,在BService中的第二步就已经将AService的原始对象赋值给了BService,但是我们在AService中的第4步中将AService的代理对象放入了单例池中,上下不统一,而我们想要的效果是AService的代理对象放入单例池中,同时赋值给BService中的第二步

解决办法:将AOP提前到第一步

  1. 实例化AService(new AService),AService原始对象-》AOP-》AService代理对象-》放入Map中,AService代理对象
  2. 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
  3. 填充其他属性
  4. 初始化后(AOP)-》AService的代理对象(此时第四步就不需要了)
  5. 添加到单例池

第一步进行AOP的场景:AService产生循环依赖的时候

此时又带来一个问题:Spring如何判断AOP是否已经执行过?

在AService中是无法判断的,所以我们将判断放入BService的第2步中:

填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》AService正在创建中(就认为AService出现了循环依赖)-》到Map中去找-》找到AService原始对象赋值给BService。

于是我们可以在AService的第0步中,将正在创建的AService放入createingSet中,该集合中存放的就是正在创建的bean对象

于是BService的第二步变成了如下:

填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》createingSet-》表示AService出现了循环依赖-》AOP-》AService代理对象赋值给BService。

于是这里又产生了新的问题:

BService中产生的代理对象可以直接放入单例池中吗?是不可以的,因为在这里AService的代理对象的属性是不完整的,那么什么时候应该将BService中AService产生的代理对象放入单例池中呢?而且假设有CService依赖AService,导致AService还需要填充CService属性,于是产生了和BService一样的过程,这就导致了有创建了AService的代理对象

那怎么解决呢?缓存喽,即BService第二步不要直接将AService的代理对象放入单例池中,而是放入earlySingletonObjects的集合中,即二级缓存中

填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》createingSet-》表示AService出现了循环依赖-》AOP-》AService代理对象赋值给BService-》将AService的代理对象放入二级缓存earlySingletonObjects<AService:AService代理对象>

二级缓存就解决了这么多的问题。最终要的是保证了AService的代理对象的唯一。

整个二级缓存的过程如下

AService的生命周期
1. 实例化AService(new AService),AService原始对象-》将对象放入Map中,该对象为原始对象AService,各属性未赋值
2. 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
4.5. 从二级缓存中取出AService的代理对象
5. 添加到单例池(从二级缓存中取出对象放入一级缓存)

BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》二级缓存-》找不到-》createing-》代表AService出现了循环依赖-》AOP-》AService代理对象赋值给BService-》将代理对象放入二级缓存<AService:AService代理对象>
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池

经过上面两个缓存还存在的问题就是:在AService的第四步中,怎么判断AService已经进行过AOP了呢?另一个问题:在BService第2步中AOP要拿到AService的原始对象进行代理,那么是如何拿到的呢?

3. singletonFactories:

这就用到了三级缓存singletonFactories:

BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》二级缓存-》找不到-》createing-》代表AService出现了循环依赖-》第三级缓存-》AService原始对象-》AOP-》AService代理对象赋值给BService-》将代理对象放入二级缓存<AService:AService代理对象>
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池

2. 源码分析

doCreateBean方法中:

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// 如果当前创建的是单例bean,并且允许循环依赖,并且还在创建过程中,那么则提早暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isDebugEnabled()) {
      logger.debug("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   // 此时的bean还没有完成属性注入,是一个非常简单的对象
   // 构造一个对象工厂添加到addSingletonFactory中
   // 第四次调用后置处理器,传入一个lambda作为参数
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// Initialize the bean instance.
// 将对象暴露出去
Object exposedObject = bean;

所以上述过程可以改变写法

AService的生命周期
1. 实例化AService(new AService),AService原始对象-》第三级缓存将对象放入Map<AService:lambda(AService原始对象,beanName,BeanDefinition)>
2. 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
4.5. 从二级缓存中取出AService的代理对象
5. 添加到单例池(从二级缓存中取出对象放入一级缓存)

BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》二级缓存-》找不到-》createing-》第三级缓存将对象放入Map<AService:lambda(AService原始对象,beanName,BeanDefinition)>-》执行lambda表达式-》AOP-》AService代理对象赋值给BService-》将代理对象放入二级缓存<AService:AService代理对象>
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池

lambda表达式

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
           // 后置处理器
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}

getEarlyBeanReference(exposedObject, beanName);

// AService出现了循环依赖
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   // 判断
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey); // 代理对象,执行AOP
}

解释源码中判断和原始bean相等的意义:

// 4. 初始化和BeanPostProcessor
exposedObject = initializeBean(beanName, exposedObject, mbd);
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
    // 如果提前暴露的对象和经过完整的生命周期后的对象相等,则把代理对象赋值给exposedObject
    if (exposedObject == bean) {
        exposedObject = earlySingletonReference;
    }

这里对exposedObject作判断经过BeanPostProcessor是否和bean相等,这里这个bean是一个原始对象,如果在经过BeanPostProcessor之后,exposedObject和bean相等,那么直接将二级缓存中的原始对象赋值给exposedObject,反之在经过其他BeanPostProcessor之后对bean做出了修改,即狸猫换太子这种说法,那么此时就会产生一个代理对象,和原始对象不相等,那么此时就要将最开始的exposedObject对象进行覆盖,将正真的对象放入二级缓存中

还剩最后一个问题:AService第4步判断已经进行过AOP呢?

追一下源码源码中是如何判断:

在AService中第四步正常进行AOP所以直接执行

/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
//正常进行AOP
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      // 这里实现了对beanName的对象是否已经AOP作出判断
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

经过分析在三级缓存之上还有两个集合:

// 保存bean对象正在创建的名字的
private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// 记录某个bean是否提前进行AOP了
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);

Spring无法解决的循环依赖

@Component
@Scope("prototype")
public class AService{}

@Component
@Scope("prototype")
public class BAervice{}

这种情况是无法解决的,分析一下:

AService依赖BService,于是去创建BService,在创建BService发现依赖AService,但是由于AService是一个原型Bean,他不会去单例池中查找,而是直接再去创建一个AService,而此时又发现依赖BService,同样BService也是一个原型Bean,所以也直接创建一个BService,而它填充属性的时候,发现依赖AService,所以又去创建AService,形成一个闭环。

还有一种就是构造依赖:

这个比较好理解,在第一步实例化bean的时候还没有将bean对象放入三级缓存就去开始BService的生命周期了,BService也一样

@Component
public class AService{
    private BService bService
    public AService(BService bService){
        this.bService = bService
    }
}

@Component
public class BAervice{
    private AService aService
    public BService(AService aService){
        this.aService = aService
    }
}

在构造方法上加个Lazy注解试试??

本文地址:https://blog.csdn.net/buhuiguowang/article/details/112001837

相关标签: 源码学习 spring