原型类型的对象创建过程分两步:① 实例化(反射调构造方法),② 初始化(属性填充),和单例类型对象的创建过程是一样的
依赖的处理是在初始化过程中进行的, loop 对象依赖 circle 属性,所以对 loop 对象的 circle 属性进行填充的时候,需要去 Spring 容器获取 circle 实例
又来到了我们熟悉的 getBean ,获取 loop 依赖的 circle 实例,我们继续往下跟
在 circle 对象创建之前,同样调用了 beforePrototypeCreation 方法,那么此时 prototypesCurrentlyInCreation 中就同时存在 loop 和 circle
表示当前线程正在创建 loop 实例和 circle 实例;继续往下走
兜兜转转又来到了 getBean ,获取 circle 对象依赖的 loop 属性,接下来是重点,大家看仔细了
因为 prototypesCurrentlyInCreation 中存在 loop 了,说明当前线程正在创建 loop 实例
而现在又要创建新的 loop ,说明陷入死循环了,所以抛出了 BeanCurrentlyInCreationException
总结经过上面的梳理,相信大家对之前的三个问题都没有疑问了,我们来总结下
1、Spring 是如何甄别单例情况下的构造方法循环依赖的
Spring 通过 Set<String> singletonsCurrentlyInCreation 记录当前正在创建中的实例名称
创建实例对象之前,会判断 singletonsCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示死循环了,那么抛出 BeanCurrentlyInCreationException
2、Spring 是如何甄别原型循环依赖的
Spring 通过 ThreadLocal<Object> prototypesCurrentlyInCreation 记录当前线程正在创建中的原型实例名称
创建原型实例之前,会判断 prototypesCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示死循环了,那么抛出 BeanCurrentlyInCreationException
3、为什么单例构造方法循环依赖和原型循环依赖的报错时机不一致
单例构造方法实例的创建是在 Spring 启动过程中完成的,而原型实例是在获取的时候创建的
所以两者的循环依赖的报错时机不一致
参考Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗