spring|spring源码系列8——最详细的循环依赖解读
前面系列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
文章图片
如上所示,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> 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, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存——存放刚实例化的bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map, ObjectFactory>> singletonFactories = new HashMap<>(16);
//二级缓存——存放早期曝光bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map, ObjectFactory>> singletonFactories = new HashMap<>(16);
//正在创建的bean集合
/** Names of beans that are currently in creation. */
private final Set> 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 doGetBean(
String name, @Nullable Class 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 extends ThreadLocal { 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> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {如果ThreadLocal的value已经是Set,则直接add
Set> beanNameSet = (Set>) 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集合)
- 一级缓存以及二级缓存能否解决循环依赖?
答:不能;
- 【spring|spring源码系列8——最详细的循环依赖解读】为什么需要三级缓存?
答:相信从三级缓存思考中得到答案;
推荐阅读
- Activiti(一)SpringBoot2集成Activiti6
- 【欢喜是你·三宅系列①】⑶
- SpringBoot调用公共模块的自定义注解失效的解决
- 解决SpringBoot引用别的模块无法注入的问题
- 你不可不知的真相系列之科学
- 人脸识别|【人脸识别系列】| 实现自动化妆
- Android事件传递源码分析
- 2018-07-09|2018-07-09 Spring 的DBCP,c3p0
- 2018-06-13金句系列7(金句结构-改编古现代诗词)
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听