spring|spring源码系列8——最详细的循环依赖解读

前面系列3到系列7总共5篇文章分析了spring容器启动的整个过程,但未对部分重要细节进行深入分析,比如spring循环依赖,因此本节对spring循环依赖进行深入分析。先思考以下四个问题:
A、 spring能解决所有的循环依赖吗?
B、 spring如何解决循环依赖?
C、 一级缓存以及二级缓存能否解决循环依赖?
D、为什么需要三级缓存?
相信看完本文,上面问题豁然开朗。
1、预备知识 1.1、预备知识1——bean生命周期 回顾一下系列7中总结的bean生命周期,如下所示:
spring|spring源码系列8——最详细的循环依赖解读
文章图片

可以大致分成两个主要阶段:实例化和初始化,进而可以细分成:实例化前、实例化、实例化后、初始化前、初始化、初始化后。
1.2、预备知识2——依赖注入 依赖注入根据配置的不同,可以分成xml和注解两种,如下:

  • xml
    构造注入、setter注入、静态工厂方法、实例工厂方法
  • 注解
    构造注入、filed注入
    构造注入——通过构造参数注入依赖
    filed注入——@Autowired、@Resource
2、循环依赖 2.1、什么是循环依赖? spring|spring源码系列8——最详细的循环依赖解读
文章图片

如上所示,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|spring源码系列8——最详细的循环依赖解读
文章图片

从启动结果可以看出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
spring|spring源码系列8——最详细的循环依赖解读
文章图片

因InstantA仅有一个带参的构造函数,因此只能用它,但发现需要InstantB作为构造参数,因此从容器中获取InstantB;
spring|spring源码系列8——最详细的循环依赖解读
文章图片

3. 从容器获取instantB将beanName加入singletonsCurrentlyInCreation
spring|spring源码系列8——最详细的循环依赖解读
文章图片

此时singletonsCurrentlyInCreation集合中有两个beanName:instantA和instantB。
4.推断实例化InstantB的构造函数并从容器获取依赖instantA
spring|spring源码系列8——最详细的循环依赖解读
文章图片

spring|spring源码系列8——最详细的循环依赖解读
文章图片

5. 再次从容器获取instantA并将beanName加入singletonsCurrentlyInCreation抛出异常
spring|spring源码系列8——最详细的循环依赖解读
文章图片

当再次将instantA加入singletonsCurrentlyInCreation set集合时,此时集合中已经存在instantA,则抛出BeanCurrentlyInCreationException异常。InstantA和InstantB实例化时,都彼此需要对方作为参数,形成死循环,将以上流程梳理总结如下。
3.2、流程总结 spring|spring源码系列8——最详细的循环依赖解读
文章图片

对上面流程简短说明:
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、 三级缓存 spring|spring源码系列8——最详细的循环依赖解读
文章图片

spring|spring源码系列8——最详细的循环依赖解读
文章图片

4.2、源码分析 1. 从容器获取instantA并将beanName instantA 加入singletonsCurrentlyInCreation
spring|spring源码系列8——最详细的循环依赖解读
文章图片

2. 推断实例化instantA的构造函数
spring|spring源码系列8——最详细的循环依赖解读
文章图片

3.将instanA加入三级缓存
spring|spring源码系列8——最详细的循环依赖解读
文章图片

4. populateBean——填充instantA的属性
spring|spring源码系列8——最详细的循环依赖解读
文章图片

为InstantA填充属性instanB
5. 从容器获取instantB并将beanName instantB加入singletonsCurrentlyInCreation
spring|spring源码系列8——最详细的循环依赖解读
文章图片

6. 推断InstantB的构造函数并生成实例
spring|spring源码系列8——最详细的循环依赖解读
文章图片

7. 将instantB加入三级缓存
spring|spring源码系列8——最详细的循环依赖解读
文章图片

此时instantA和instantB都在三级缓存中。
8. populateBean—填充instantB的属性
spring|spring源码系列8——最详细的循环依赖解读
文章图片

经过解析@Autowired注解,发现instantB依赖InstantA,因此从容器中获取InstantA.。
spring|spring源码系列8——最详细的循环依赖解读
文章图片

9. 再次从容器中获取instantA——从三级缓存中获取instantA并加入二级缓存
spring|spring源码系列8——最详细的循环依赖解读
文章图片

此时beanName instantA在singletonsCurrentlyInCreation集合中,因此193行方法isSingletonCurrentlyInCreation返回true,此时继续从二级三级缓存查找。
此时三级缓存中有instantA的回调函数,执行回调函数得到早期曝光bean并放入二级缓存,同时将回调函数从三级缓存中移除。下面看如何执行三级回调函数?
10. 执行instantA的三级缓存回调函数
spring|spring源码系列8——最详细的循环依赖解读
文章图片

spring|spring源码系列8——最详细的循环依赖解读
文章图片

生成cacheKey并放入earlyProxyReferences缓存中;下面继续看wrapIfNecessary方法:
spring|spring源码系列8——最详细的循环依赖解读
文章图片

此处instantA没aop增强,所以上图363行返回的specificInterceptors为空,因此未创建代理对象,将原有的bean直接返回。
11. 常规aop逻辑
spring|spring源码系列8——最详细的循环依赖解读
文章图片

常规创建aop代理对象是通过执行BeanPostProcessor的实现类AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization方法。
12. 填充instantB的属性
spring|spring源码系列8——最详细的循环依赖解读
文章图片

将容器中获取的instantA(此时位于二级缓存)填充到instantB的属性中。
13. 从singletonsCurrentlyInCreation移除instantB
spring|spring源码系列8——最详细的循环依赖解读
文章图片

移除后集合仅剩instantA。
14. 更新instantB的缓存
spring|spring源码系列8——最详细的循环依赖解读
文章图片

当前instantB位于三级缓存中,因此从三级缓存中移除并将生成的bean放到一级缓存单例池中;
15. 继续回到创建instantA的populateBean过程中
spring|spring源码系列8——最详细的循环依赖解读
文章图片

将容器获取的instantB填充到instantA实例中。
16. 更新instantA的缓存
当instantA属性填充完后,instantA生成完毕,此时进行缓存更新。
spring|spring源码系列8——最详细的循环依赖解读
文章图片

当instantA属性填充完后,再进行初始化。初始化后,instantA生成完毕并进行缓存更新,此时instantA位于二级缓存,将它从二级缓存中移除并加入一级缓存单例池。
4.3、流程总结 spring|spring源码系列8——最详细的循环依赖解读
文章图片

结合以上分析,可以看出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
spring|spring源码系列8——最详细的循环依赖解读
文章图片

2. 将instantA加入当前线程的prototypesCurrentlyInCreation
spring|spring源码系列8——最详细的循环依赖解读
文章图片

3. 填充InstantA实例,发现依赖InstantB
spring|spring源码系列8——最详细的循环依赖解读
文章图片

A、解析InstantA类的注解@Autowired发现依赖InstantB;
B、准备实例化InstantB,实例化之前检查prototypesCurrentlyInCreation是否包含instantB;
C、此时不包含InstantB,将instantB加入prototypesCurrentlyInCreation;
经过以上步骤,prototypesCurrentlyInCreation集合中则有instantA和instantB两个元素,如上图所示;
4. 解析instantB发现需要依赖instantA,则再次从容器获取instantA
spring|spring源码系列8——最详细的循环依赖解读
文章图片

spring|spring源码系列8——最详细的循环依赖解读
文章图片

获取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、总结 还记得开始前的四个问题吗?
  1. spring能解决所有的循环依赖吗?
    答:
    原型bean:spring不能解决原型bean任何注入方式产生的循环依赖;
    单例bean:能解决单例bean在setter、filed注入时产生的循环依赖,不能解决构造注入时产生的循环依赖;
  2. spring如何解决循环依赖?
    答:三级缓存+singletonsCurrentlyInCreation(正在创建的bean集合)
  3. 一级缓存以及二级缓存能否解决循环依赖?
    答:不能;
  4. 【spring|spring源码系列8——最详细的循环依赖解读】为什么需要三级缓存?
    答:相信从三级缓存思考中得到答案;

    推荐阅读