5.0 Spring循环依赖问题

一、什么是循环依赖

就是A对象依赖了B对象,B对象依赖了A对象。

A a = new A();
B b = new B();
a.b = b;
b.a = a;

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。

为什么在Spring中循环依赖就是一个问题了? 因为在Spring中,一个对象会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题

出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的 场景则需要程序员来解决

A类Bean创建-->依赖了B属性-->触发B类Bean创建--->B依赖了A属性--->需要A类Bean(但A类Bean还在 创建过程中)

从而导致A类Bean创建不出来,B类Bean也创建不出来。

在spring中三级缓存机制帮助开发者解决部分循环依赖的问题

二、 三级缓存

三级缓存是通用的叫法。

一级缓存为 singletonObjects:经历了完整生命周期的bean对象

二级缓存为 earlySingletonObjects: Bean的生命周期还没走完Bean对象

三级缓存为 singletonFactories: 用来创建早期bean对象的 工厂ObjectFactory

三、解决循环依赖思路

上文分析得到,之所以产生循环依赖的问题,主要是:

A创建时--->需要B---->B去创建--->需要A,从而产生了循环

1. 解决一般的循环依赖(二级缓存)

核心思想就是打破这个循环,加个中间人(缓存

  1. A创建时—>A的原始对象放入缓存
  2. A需要B
  3. B去创建,B需要A,则从缓存中取
  4. B创建完成
  5. 将B赋值给A对象
  6. A,B对象都创建完成

2. 解决AOP对象的循环依赖(三级缓存)

在上述过程中,只需要一个缓存就能解决循环依赖了,拿为什么Spring中还有SingletonFactory呢?

因为B依赖的A和最终的A不是同一个对象。

基于上面的场景

  1. A的原始对象注入给B属性之后,A的原始对象进行了AOP产生一个代理对象,对于A对象而言,它的Bean对象其实是AOP之后的代理对象
  2. 而B的a属性对应的不是AOP之后的代理对象,这就产生了冲突

AOP的流程:

A类--->生成原始对象-->属性注入-->基于切面生成一个代理对象-->把代理对象放入singletonObject单例池

循环依赖属于IOC的范畴,这个部分和AOP有了交汇,解决方式就是利用第三级缓存singletonFactory

首先,singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中, 生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中。这个ObjectFactory 是一个函数式接口:() -> getEarlyBeanReference(beanName, mbd, bean),存入时不会执行

// 该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法
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;
}

// 核心调用方法逻辑 AbstractAutoProxyCreator#getEarlyBeanReference
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
}

上面的AOP逻辑为:

  1. 首先得到一个cachekeycachekey就是 beanName
  2. 然后把beanName和bean(这是原始对象)存入earlyProxyReferences
  3. 调用 wrapIfNecessary进行AOP,得到一个代理对象。

解决AOP对象的循环依赖流程:

  1. singletonFactories根据beanName得到一个ObjectFactory
  2. 然后执行 ObjectFactory,也就是执行getEarlyBeanReference方法,此时会得到一个A原始对象经过AOP之后的代理对象,
  3. 然后把该代理对象放入earlySingletonObjects中,此时,我们只得到了A原始对象的代理对 象,这个对象还不完整,因为A原始对象还没有进行属性填充,假设现在有其他对象依 赖了A,那么则可以从earlySingletonObjects中得到A原始对象的代理对象了,并且是A的同一个代 理对象。
  4. 当B创建完了之后,A继续进行生命周期,而A在完成属性注入后,会按照它本身的逻辑去进行AOP, 而此时我们知道A原始对象已经经历过了AOP,所以对于A本身而言,不会再去进行AOP了
  5. 那么怎 么判断一个对象是否经历过了AOP呢?会利用上文提到的earlyProxyReferences,在 AbstractAutoProxyCreator#postProcessAfterInitialization方法中,会去判断当前beanName是 否在earlyProxyReferences,如果在则表示已经提前进行过AOP了,无需再次进行AOP。 对于A而言,进行了AOP的判断后,以及BeanPostProcessor的执行之后,就需要把A对应的对象放 入singletonObjects中了,但是我们知道,应该是要把A的代理对象放入singletonObjects中,所以 此时需要从earlySingletonObjects中得到代理对象,然后放入singletonObjects中。

四、总结三级缓存

  1. singletonObjects:缓存经过了完整生命周期的bean
  2. earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖, 就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入 earlySingletonObjects。代理对象所代理的原始对象也是没有经过完整生命周期的
  3. singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean 的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,那么这个Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖 (当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行 Lambda表达式得到一个对象,并把得到的对象放入二级缓存((如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象))。
  4. 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP 了。

五、反向分析singletonFactories

Spring原有的流程出现了循环依赖的情况下:

  1. 先从singletonFactories中拿到lambda表达式,这里肯定是能拿到的,因为每个bean实例化之 后,依赖注入之前,就会生成一个lambda表示放入singletonFactories中
  2. 执行lambda表达式,得到结果,将结果放入earlySingletonObjects中

思考:如果没有singletonFactories,该如何把原始对象或AOP之后的代理对象放入 earlySingletonObjects中呢?何时放入呢?

将原始对象或AOP之后的代理对象放入earlySingletonObjects中的有两种:

  1. 实例化之后,依赖注入之前:如果是这样,那么对于每个bean而言,都是在依赖注入之前会去 进行AOP,这是不符合bean生命周期步骤的设计的。
  2. 发现某个bean出现了循环依赖时:按现在Spring源码的流程来说,就是 getSingleton(String beanName, boolean allowEarlyReference)中,是在这个方法中判断出来了当前获取的这个bean在创建中,就表示获取的这个bean出现了循环依赖,那在这个方法中 该如何拿到原始对象呢?更加重要的是,该如何拿到AOP之后的代理对象呢?难道在这个方法中 去循环调用BeanPostProcessor的初始化后的方法吗?不是做不到,不太合适,代码太丑。最关键的是在这个方法中该如何拿到原始对象呢?还是得需要一个Map,预习把这个Bean实例化后的对象存在这个Map中,那这样的话还不如直接用第一种方案,但是第一种又直接打破了 Bean生命周期的设计

所以,我们可以发现,现在Spring所用的singletonFactories,为了调和不同的情况,在 singletonFactories中存的是lambda表达式,这样的话,只有在出现了循环依赖的情况,才会执行 lambda表达式,才会进行AOP,也就说只有在出现了循环依赖的情况下才会打破Bean生命周期的设 计,如果一个Bean没有出现循环依赖,那么它还是遵守了Bean的生命周期的设计。

posted @ 2022-10-12 11:36  浮沉丶随心  阅读(65)  评论(0编辑  收藏  举报