spring源码系列8——最详细的循环依赖解读
前面系列3到系列7总共5篇文章分析了spring容器启动的整个过程,但未对部分重要细节进行深入分析,比如spring循环依赖,因此本节对spring循环依赖进行深入分析。先思考以下三个问题:spring能解决所有的循环依赖吗?spring如何解决循环依赖?一级缓存能否解决循环依赖?两级缓存能否解决循环依
前面系列3到系列7总共5篇文章分析了spring容器启动的整个过程,但未对部分重要细节进行深入分析,比如spring循环依赖,因此本节对spring循环依赖进行深入分析。先思考以下四个问题:
A、 spring能解决所有的循环依赖吗?
B、 spring如何解决循环依赖?
C、 一级缓存以及二级缓存能否解决循环依赖?
D、为什么需要三级缓存?
相信看完本文,上面问题豁然开朗。
1、预备知识
1.1、预备知识1——bean生命周期
回顾一下系列7中总结的bean生命周期,如下所示:
可以大致分成两个主要阶段:实例化和初始化,进而可以细分成:实例化前、实例化、实例化后、初始化前、初始化、初始化后。
1.2、预备知识2——依赖注入
依赖注入根据配置的不同,可以分成xml和注解两种,如下:
-
xml
构造注入、setter注入、静态工厂方法、实例工厂方法 -
注解
构造注入、filed注入构造注入——通过构造参数注入依赖
filed注入——@Autowired、@Resource
2、循环依赖
2.1、什么是循环依赖?
如上所示,InstantA依赖InstantB,InstantB依赖InstantC,依次往下传递到最终依赖InstantZ,InstantZ依赖InstantA,形成依赖闭环,即循环依赖。
2.2、构造注入
为了简化分析难度,此处只分析A依赖B,B依赖A的情况,同时现在注解用得更广泛,因此下面以注解作为demo进行分析。
@Component
public class InstantA {
private InstantB instantB;
public InstantA(InstantB instantB) {
this.instantB = instantB;
}
}
@Component
public class InstantB {
private InstantA instantA;
public InstantB(InstantA instantA) {
this.instantA = instantA;
}
}
可以看到InstantA中的属性依赖InstantB,InstantB依赖InstantA,相互依赖,因此形成循环依赖。
启动spring容器时,报错如下:
从启动结果可以看出spring无法解决构造注入时产生的循环依赖;
2.3、filed注入
@Component
public class InstantA {
@Autowired
private InstantB instantB;
}
@Component
public class InstantB {
@Autowired
private InstantA instantA;
}
容器能正常启动并成功注入依赖,表明Spring能成功解决filed注入时产生的循环依赖。
接下来,深入源码探究为什么不能解决构造注入时产生的循环依赖,而可以解决filed注入时产生的循环依赖?发车!
3、构造注入循环依赖
3.1、源码分析
1. 从容器获取insantA并将beanName加入singletonsCurrentlyInCreation集合
//正在创建的bean集合
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
singletonsCurrentlyInCreation记录当前正在创建bean的名字集合。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//创建和销毁冲突
if (this.singletonsCurrentlyInDestruction) {
//省略非关键代码
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
//回调前:将当前beanName加入singletonsCurrentlyInCreation,如果已在创建过程中则抛出异常
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
//执行回调函数
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
//省略非关键代码
}
catch (BeanCreationException ex) {
//省略非关键代码
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
//回调后:从singletonsCurrentlyInCreation移除beanName
afterSingletonCreation(beanName);
}
if (newSingleton) {
//从二级、三级缓存移除,并将当前实例加入单例池中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
beforeSingletonCreation和afterSingletonCreation两个方法分别将beanName从集合中加入或者移除,重点看beforeSingletonCreation
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
//当多次加入同一个beanName,则抛出异常 构造注入出现循环依赖时 此处会抛出异常
throw new BeanCurrentlyInCreationException(beanName);
}
}
2. 推断instantA的构造函数并从容器获取依赖instantB
因InstantA仅有一个带参的构造函数,因此只能用它,但发现需要InstantB作为构造参数,因此从容器中获取InstantB;
3. 从容器获取instantB将beanName加入singletonsCurrentlyInCreation
此时singletonsCurrentlyInCreation集合中有两个beanName:instantA和instantB。
4.推断实例化InstantB的构造函数并从容器获取依赖instantA
5. 再次从容器获取instantA并将beanName加入singletonsCurrentlyInCreation抛出异常
当再次将instantA加入singletonsCurrentlyInCreation set集合时,此时集合中已经存在instantA,则抛出BeanCurrentlyInCreationException异常。InstantA和InstantB实例化时,都彼此需要对方作为参数,形成死循环,将以上流程梳理总结如下。
3.2、流程总结
对上面流程简短说明:
A、当实例InstantA前,将beanName instantA加入集合;
B、实例化InstantA时,发现依赖InstantB;
C、实例InstantB前,将beanName instantB加入集合;
D、实例化InstantB时,发现依赖InstantA;
E、从容器获取InstantA,发现没有,则再次执行创建流程,将beanName instantA加入集合,此时集合中已经存在beanName instantA,抛出异常;
因此,spring无法解决构造注入时产生的循环依赖;
4、filed注入循环依赖
4.1、 三级缓存
4.2、源码分析
1. 从容器获取instantA并将beanName instantA 加入singletonsCurrentlyInCreation
2. 推断实例化instantA的构造函数
3.将instanA加入三级缓存
4. populateBean——填充instantA的属性
为InstantA填充属性instanB
5. 从容器获取instantB并将beanName instantB加入singletonsCurrentlyInCreation
6. 推断InstantB的构造函数并生成实例
7. 将instantB加入三级缓存
此时instantA和instantB都在三级缓存中。
8. populateBean—填充instantB的属性
经过解析@Autowired注解,发现instantB依赖InstantA,因此从容器中获取InstantA.。
9. 再次从容器中获取instantA——从三级缓存中获取instantA并加入二级缓存
此时beanName instantA在singletonsCurrentlyInCreation集合中,因此193行方法isSingletonCurrentlyInCreation返回true,此时继续从二级三级缓存查找。
此时三级缓存中有instantA的回调函数,执行回调函数得到早期曝光bean并放入二级缓存,同时将回调函数从三级缓存中移除。下面看如何执行三级回调函数?
10. 执行instantA的三级缓存回调函数
生成cacheKey并放入earlyProxyReferences缓存中;下面继续看wrapIfNecessary方法:
此处instantA没aop增强,所以上图363行返回的specificInterceptors为空,因此未创建代理对象,将原有的bean直接返回。
11. 常规aop逻辑
常规创建aop代理对象是通过执行BeanPostProcessor的实现类AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization方法。
12. 填充instantB的属性
将容器中获取的instantA(此时位于二级缓存)填充到instantB的属性中。
13. 从singletonsCurrentlyInCreation移除instantB
移除后集合仅剩instantA。
14. 更新instantB的缓存
当前instantB位于三级缓存中,因此从三级缓存中移除并将生成的bean放到一级缓存单例池中;
15. 继续回到创建instantA的populateBean过程中
将容器获取的instantB填充到instantA实例中。
16. 更新instantA的缓存
当instantA属性填充完后,instantA生成完毕,此时进行缓存更新。
当instantA属性填充完后,再进行初始化。初始化后,instantA生成完毕并进行缓存更新,此时instantA位于二级缓存,将它从二级缓存中移除并加入一级缓存单例池。
4.3、流程总结
结合以上分析,可以看出spring可以解决filed注入产生的循环依赖,主要借助以下四个集合(三级缓存外加正在创建的bean集合):
//单例池
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存——存放刚实例化的bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存——存放早期曝光bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//正在创建的bean集合
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));
5、原型bean的循环依赖
前面的循环依赖主要是针对单例bean,接下来看看spring能否解决原型bean的循环依赖?还是以注解手动注入为例,Go!
5.1、demo示例
@Component
@Scope("prototype")
public class InstantA {
@Autowired
private InstantB instantB;
}
@Component
@Scope("prototype")
public class InstantB {
@Autowired
private InstantA instantA;
}
5.2、源码分析
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) { //省略非关键代码
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
//省略非关键代码
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// Create bean instance.
if (mbd.isSingleton()) {
//省略非关键代码
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
//创建之前将beanName设置到线程中或者加入集合中
beforePrototypeCreation(beanName);//说明1
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
public class NamedThreadLocal<T> extends ThreadLocal<T> {
private final String name;
/**
* Create a new NamedThreadLocal with the given name.
* @param name a descriptive name for this ThreadLocal
*/
public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
- beforePrototypeCreation
protected void beforePrototypeCreation(String beanName) {
//如果ThreadLocal的value为空,则直接将beanName当作value
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
//如果ThreadLocal的value已经是String,则ThreadLocal的value需要保存多个值,则将新建hashSet作新value
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
如果ThreadLocal的value已经是Set,则直接add
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
1. 实例化InstantA之前检查prototypesCurrentlyInCreation是否包含instantA
2. 将instantA加入当前线程的prototypesCurrentlyInCreation
3. 填充InstantA实例,发现依赖InstantB
A、解析InstantA类的注解@Autowired发现依赖InstantB;
B、准备实例化InstantB,实例化之前检查prototypesCurrentlyInCreation是否包含instantB;
C、此时不包含InstantB,将instantB加入prototypesCurrentlyInCreation;
经过以上步骤,prototypesCurrentlyInCreation集合中则有instantA和instantB两个元素,如上图所示;
4. 解析instantB发现需要依赖instantA,则再次从容器获取instantA
获取instantA之前再次检查prototypesCurrentlyInCreation是否包含instantA,此时已经包含instantA,因此抛出异常。
5.3、总结
可以看到spring无法解决原型的filed注入循环依赖,当然构造注入的循环依赖也无法解决。
6、关于三级缓存的思考
6.1、一级缓存能否解决循环依赖?
不行。
因为实例化后(未填充属性)的bean以及完成属性填充实例化的bean都放到一级缓存。如果在实例化后与属性填充之间获取bean,则得到非完整bean,可能属性为空;
6.2、二级缓存能否解决循环依赖?
A、如果没aop,二级缓存能解决;bean生产过程可以分成实例化和初始化两个阶段,实例化后放在二级缓存,初始化完再放到一级缓存。生成bean完成后,后续获取bean只从一级缓存中获取,可以保证bean的完整性;
B、如果有aop,二级无法解决循环依赖,会出现二级缓存中相同的beanName在不同阶段(实例化后和初始化后)不是同一个bean(因此执行aop后会返回代理对象,和之前的bean不是同一个对象),导致混乱;
6.3、三级缓存存在的意义
A、为什么不直接在放入两级缓存之前提前执行aop逻辑?
因为循环依赖的相对较少,没必要针对小部分实例执行一遍(wrapIfNecessary)。反正后续的BeanPostProcessor中的postProcessAfterInitialization有对aop的处理;
B、三级缓存的目的就是让如果有循环依赖,提前执行可能存在的aop操作(如果存在aop),从而放入二级缓存的bean是生成的代理对象,从而保证二级缓存中相同的beanName是同一个bean;
7、总结
还记得开始前的四个问题吗?
-
spring能解决所有的循环依赖吗?
答:
原型bean:spring不能解决原型bean任何注入方式产生的循环依赖;
单例bean:能解决单例bean在setter、filed注入时产生的循环依赖,不能解决构造注入时产生的循环依赖; -
spring如何解决循环依赖?
答:三级缓存+singletonsCurrentlyInCreation(正在创建的bean集合)
-
一级缓存以及二级缓存能否解决循环依赖?
答:不能; -
为什么需要三级缓存?
答:相信从三级缓存思考中得到答案;
更多推荐
所有评论(0)