1. 什么是循环依赖?
譬如,A对象依赖B对象,B对象依赖A对象
public class A{
//依赖B
private B b;
}
public class B{
//依赖A
private A a;
}
如果不考虑spring,循环依赖并不是问题,对象之间相互依赖是很正常的事情
A a = new A();
B b = new B();
但是在Spring中循环依赖就是一个问题,在Spring中,一个对象并不是new出来这么简单,而是会经过一系列的Bean生命周期,就是因为bean的生命周期所有才会出现循环依赖问题。要想明白Spring的循环依赖问题,首先要知道Spring Bean的生命周期。
2. Spring Bean的生命周期
2.1. 生命周期的回顾
大家可以看SpringBean的生命周期
2.2. Bean的生成步骤
被Spring管理的对象叫做bean,bean的生成步骤如下:
- Spring扫描指定路径下的配置文件或者配置类得到BeanDefinition
- 根据得到的BeanDefinition生成bean,这个bean只是被实例化了,并没有属性赋值
- 填充bean对象的属性,也就是依赖注入
- 如果bean对象的某一个方法被AOP了,那么需要根据bean对象生成一个代理对象
- 把最终生成的代理对象放入到单例池中(源码叫singletonObjects),下次getBean时就直接从单例池中获取
Spring Bean的生成过程步骤很多,上面只是写了一部分,各种后置处理器就不写出来了,不是本文的重要内容。
从上面的步骤中,可以看到Spring需要给对象中的属性进行依赖注入,这个注入过程是啥样的呢?
比如上文中的A类, A类中存在一个B类的b属性,所以当A类生成一个bean对象之后,就会给b属性去赋值,此时会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。
- 如果此时BeanFactory中存在B对应的bean,那么直接拿来赋值给b属性
- 如果此时BeanFactory中不存在B对应的bean,那么需要生成一个B对应的bean,然后赋值给b属性
在第二种的情况下,问题就出现了,如果此时BeanFactory中还没有生成对应的bean,那么就需要去生成,就会经历B的bean生命周期。
那么在创建B类的bean的过程中,如果B类中依赖一个A类的a属性,那么在创建B的bean的过程中就需要A类对应的bean,但是触发B类的
bean创建的条件是A类bean在创建过程中的依赖注入,所以这里就出现了循环依赖;
A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)
从而导致A 的bean创建不出来,B的bean也创建不出来。
这是循环依赖的场景,但是Spring通过某种机制解决了部分循环依赖的问题,这个机制就是三级缓存。
3. 三级缓存
- 一级缓存为:singletonObjects;
- 二级缓存为:earlySingletonObjects;
- 三级缓存为:singletonFactories;
/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
3.1. 三个缓存分别有什么作用
- singletonObjects:缓存的是已经经历了完整生命周期的bean对象
- earlySingletonObjects:比singletonObjects多了一个early,表示缓存的是早期的bean对象,早期指的是生命周期还没走完就把这个bean放入到了earlySingletonObjects中。
- singletonFactories:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象。
4. 思路分析
上面分析到,之所以产生依赖,是因为A创建时,需要B去创建,而B创建时需要A,从而产生了依赖。
为了打破这个循环,Spring决定加个缓存。
A的bean在创建过程中,在进行依赖注入之前,先把A的原始bean放入缓存(提早暴露,放到缓存中,其他bean需要时直接从缓存中拿),放入
缓存后,再进行依赖注入,此时A的bean依赖了B的bean。
如果B的bean不存在,则需要创建B的bean,而创建B的bean的过程和A的一样,先是创建一个B的原始bean,放入到缓存中,然后B的bean
进行依赖注入A,此时能从缓冲中拿到A的原始bean,B的原始bean依赖注入完成之后,B的生命周期结束,那么A的生命周期也结束。
4.1. 为什么需要三级缓存?
从上面的分析过程可以看出,只需要一个缓存好像就能解决循环依赖的问题了,为什么Spring中还需要三级缓存呢?
联想一个场景:
如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的bean对象其实应该
是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。
B依赖的A和最终的A不是同一个对象。
那么这个问题怎么解决呢?
这个问题可以说是没有办法解决的,因为在一个bean的生命周期最后,Spring会提供BeanPostProcessor可以对bean进行加工,这个加工不
仅仅只是能修改bean的属性值,也可以替换掉当前bean。
而BeanPostProcessor的执行在bean的生命周期中是出于属性注入之后的,循环依赖是发生在属性注入过程中的,所以很有可能导致注入
给B对象的A对象和经历过完整生命周期之后的A对象不是一个对象。
所以这种情况下的循环依赖,Spring是解决不了的,因为在属性注入时,Spring也不知道A对象后会经历过哪些BeanPostProcessor以及会对
A对象做什么处理。
5. Spring解决了哪种情况下的循环依赖
AOP就是通过一个BeanPostProcessor来实现的,在Spring中AOP利用的要么是JDK动态代理,要么是CGLib的动态代理,所以如果给一个
类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。
一般的过程就是:A类->生成一个bean->属性注入->基于切面生成一个代理对象->把代理对象放入singltonObjects单例池中。
而 AOP 可以说是 Spring 中除开 IOC 的另外一大功能,而循环依赖又是属于 IOC 范畴的,所以这两大功能想要并存,Spring 需要特殊处理。
如何处理的,就是利用了第三级缓存singletonFactories。
首先,singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中。
5.1. ObjectFactory
这个ObjectFactory是一个函数式接口,支持Lambda表达式:() ->getEarlyBeanReference(beanName, mbd, bean)
执行该lambda表达式会执行getEarlyBeanReference方法:
该方法会执行SmartInstantiationAwareBeanPostProcessor接口中的getEarlyBeanReference方法,而这个接口中的实现类只有两个类,一个是AbstractAutoProxyCreator,一个是InstantiationAwareBeanPostProcessorAdapter:
AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
this.earlyProxyReferences.add(cacheKey);
}
return wrapIfNecessary(bean, beanName, cacheKey);
}
InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
可以看到,默认只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,而该类就是用来进行AOP的。
5.2. getEarlyBeanReference() 方法
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// cachekey就是beanName
this.earlyProxyReferences.add(cacheKey);
}
return wrapIfNecessary(bean, beanName, cacheKey);
}
首先得到一个cachekey,cachekey就是beanName。然后把beanName和bean(这是原始对象)存入 earlyProxyReferences 中。调用 wrapIfNecessary 进行AOP,得到一个代理对象。
那么什么时候会调用 getEarlyBeanReference 方法呢?
图中的 ObjectFactory 就是上文说的 labmda 表达式,中间有 getEarlyBeanReference 方法。
注意存入 singletonFactories 时并不会执行 lambda 表达式,也就是不会执行getEarlyBeanReference 方法。
从 singletonFactories 根据 beanName 得到一个 ObjectFactory ,然后执行 ObjectFactory ,也就是执行 getEarlyBeanReference 方法,此时会得到一个 A 原始对象经过 AOP 之后的代理对象,然后把该代理对象放入 earlySingletonObjects 中。
此时并没有把代理对象放入 singletonObjects 中,那什么时候放入到 singletonObjects 中呢?
此时,我们只得到了 A 原始对象的代理对象,这个对象还不完整,因为 A 原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入 singletonObjects 中,所以只能把代理对象放入earlySingletonObjects 。
假设现在有其他对象依赖了 A,那么则可以从 earlySingletonObjects 中得到 A 原始对象的代理对象了,并且是A的同一个代理对象。
当 B 创建完了之后,A 继续进行生命周期,而 A 在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道 A 原始对象已经经历过了 AOP ,所以对于 A 本身而言,不会再去进行 AOP了,那么怎么判断一个对象是否经历过了 AOP 呢?
会利用上文提到的 earlyProxyReferences,在 AbstractAutoProxyCreator 的 postProcessAfterInitialization 方法中,会去判断当前
beanName 是否在 earlyProxyReferences,如果在则表示已经提前进行过 AOP了,无需再次进行 AOP。
对于 A 而言,进行了 AOP 的判断后,以及 BeanPostProcessor 的执行之后,就需要把 A 对应的对象放入 singletonObjects 中了,但是我们知道,应该是要 A 的代理对象放入 singletonObjects 中,所以此时需要从 earlySingletonObjects 中得到代理对象,然后入 singletonObjects 中。
6. 总结
A对象实例化之后,在属性注入之前,会将A对象放入到三级缓存中,key是beanName,value是ObjectFactory,等到A对象属性注入时,发
现依赖B,又去实例化B,B在属性注入时需要去获取A对象,这里就从三级缓存拿出ObjectFactory,从ObjectFactory等到对应的bean(就是
对象A),把三级缓存的A删除,放到二级缓存中,二级缓存中存的key是beanName,value是bean(这时候的bean还不是完整的,没有属性注
入),等到实例化完成之后,就把二级缓存给删除,放到一级缓存中,我们自己去getBean的时候,实际上拿到的是一级缓存的。