前言
Spring 的循环依赖曾经被说烂了,可能很多人也看吐了。但很多博客上说的还是不够分明,没有残缺的表白出 Spring 的设计目标。只介绍了 What ,对于 Why 的介绍却不太够。
本文会从设计角度,一步一步详细分析 Spring 这个“三级缓存”的设计准则,说说为什么要这么设计。
Bean 创立流程
Spring 中的每一个 Bean 都由一个BeanDefinition 创立而来,在注册实现 BeanDefinition 后。会遍历BeanFactory中的 beanDefinitionMap 对所有的 Bean 调用 getBean 进行初始化。
简略来说,一个 Bean 的创立流程次要分为以下几个阶段:
- Instantiate Bean - 实例化 Bean,通过默认的构造函数或者构造函数注入的形式
- Populate Bean - 解决 Bean 的属性依赖,能够是Autowired注入的,也能够是 XML 中配置的,或者是手动创立的 BeanDefinition 中的依赖(propertyValues)
- Initialize Bean - 初始化 Bean,执行初始化办法,执行 BeanPostProcessor 。这个阶段是各种 Bean 的后置解决,比方 AOP 的代理对象替换就是在这个阶段
在实现下面的创立流程后,将 Bean 增加到缓存中 - singletonObjects,当前在 getBean 时先从缓存中查找,不存在才创立。
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
先抛开 Spring 的源码不谈,先看看依照这个创立流程执行会遇到什么问题
1. Instantiate Bean
首先是第一阶段 - 实例化,就是调用 Bean Class的构造函数,创立实例而已,没啥可说的。至于一些获取 BeanDefinition 构造方法的逻辑,不是循环依赖的重点。
2. Populate Bean
第二阶段 - 填充Bean,其目标是查找以后 Bean 援用的所有 Bean,利用 BeanFactory 获取这些 Bean,而后注入到以后 Bean 的属性中。
失常状况下,没有循环援用关系时没什么问题。比方当初正在进行 ABean 的 populate 操作,发现了 BBean 的援用,通过 BeanFactory 去 getBean(BBean) 就能够实现,哪怕当初 BBean 还没有创立,在getBean中实现初始化也能够。实现后将 BBean 增加到已创立实现的 Bean 缓存中 - singletonObjects。
最初再将获取的 BBean 实例注入到 ABean 中就实现了这个 populate 操作,看着还挺简略。
此时援用关系产生了点变动,ABean 也依赖了 BBean,两个 Bean 的援用关系变成了相互援用,如下图所示:
再来看看当初 populate 该怎么执行:
首先还是先初始化 BBean ,而后发现了 Bbean 援用的 ABean,当初 getBean(ABean),发现 ABean 也没有创立,开始执行对 ABean 的创立:先实例化,而后对 ABean 执行 populate,可 populate 时又发现了 ABean 援用了 BBean,可此时 BBean 还没有创立实现,Bean 缓存中也并不存在。这样就呈现死循环了,两个 Bean 互相援用,populate 操作齐全没法执行。
其实解决这个问题也很简略,呈现死循环的要害是两个 Bean 相互援用,populate 时另一个 Bean 还在创立中,没有创立实现。
只须要减少一个中间状态的缓存容器,用来存储只执行了 instantiate 还未 populate 的那些 Bean。到了populate 阶段时,如果残缺状态缓存中不存在,就从中间状态缓存容器查找一遍,这样就防止了死循环的问题。
如下图所示,减少了一个中间状态的缓存容器 - earlySingletonObjects,用来存储刚执行 instantiate 的 Bean,在 Bean 实现创立后,从 earlySingletonObjects 删除,增加到 singletonObjects 中。
回到下面的例子,如果在 ABean 的 populate 阶段又发现了 BBean 的援用,那么先从 singletonObjects 查找,如果不存在,持续从 earlySingletonObjects 中查找,找到当前注入到 ABean 中,而后 ABean 创立实现(BeanPostProcessor待会再说)。当初将 ABean 增加到 singletonObjects 中,接着返回到创立 BBean 的过程。最初把返回的 ABean 注入到 BBean 中,就实现了 BBean 的 populate 操作,如下图所示:
循环依赖的问题,就这么轻易的解决了,看着很简略,只是加了一个中间状态而已。但除了 instantiate 和 populate 阶段,还有最初一个执行 BeanPostProcessor 阶段,这个阶段可能会加强/替换原始 Bean
3. Initialize Bean
这个阶段分为执行初始化办法 - initMethod,和执行 BeanFactory 中定义的 BeanPostProcessor(BPP)。执行初始化办法没啥可说的,重点看看 执行BeanPostProcessor 局部。
BeanPostProcessor 算是 Spring 的灵魂接口了,很多扩大的操作和性能都是通过这个接口,比方 AOP。在populate 实现之后,Spring 会对 Bean 程序执行所有的 BeanPostProcessor,而后返回 BeanPostProcessor 返回的新 Bean 实例(可能有批改也可能没批改)
//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitializationpublic Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; //对以后 Bean 程序的执行所有的 BeanPostProcessor,并返回 for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; } return result;}
Spring 的 AOP 加强性能,也是利用 BeanPostProcessor 实现的,如果该 Bean 有 AOP 加强的配置,那么执行完 BeanPostProcessor 之后就会返回一个新的 Bean,最初存储到 singletonObjects 中的也是这个加强之后的 Bean
可咱们下面的“中间状态缓存”解决方案中,存储的却只是刚执行实现 instantiate 的 Bean。如果在下面循环依赖的例子中,populate ABean 时因为 BBean 只实现了实例化,所以会从 earlySingletonObjects 获取只实现初始化的 BBean 并注入到 ABean中。
如果 BBean 有 AOP 的配置,那么此时注入到 ABean 中的 只是一个只实例化未 AOP 加强的对象。当 BBean 执行 BeanPostProcessor 后,又会创立一个加强的 BBean 实例,最终增加到 singletonObjects 中的,是加强的 BBean 实例,而不是那个刚实例化的 BBean 实例
如下图所示,BBean 中注入的是黄色的只实现了初始化的 ABbean,而最终增加到 singletonObjects 却是执行完 AOP 的加强 ABean 实例:
因为 populate 之后还有一步 BeanPostProcessor 的加强,导致咱们下面的解决方案有效了。但也不是齐全无解,如果能够让增强型的 BeanPostProcessor 提前执行,而后增加到“中间状态的缓存容器”中,是不是也能够解决问题?
不过并不是所有的 Bean 都有 AOP(及其他执行 BPP 后返回新对象) 的需要,如果让所有 Bean 都提前执行 BeanPostProcessor 并不适合。
所以这里能够采纳一种“提早解决”的形式,在两头减少一层 Factory,在这个 Factory 中实现“提前执行”的操作。
如果没有提前调用某个 Bean 的 “提早解决”Factory,那么就不会导致提前执行 BeanPostProcessor,只有循环依赖场景下,才会呈现这种只实现初始化却未齐全创立的 Bean ,才会调用这个 Factory。这个 Factory 的模式就叫提早解决,如果不调用 Factory 就不会提前执行 BPP。
instantiate 实现后,把这个 Factory 增加到“中间状态的缓存容器”中;这样当产生循环依赖时,原先获取的中间状态 Bean 实例就会变成这个 Factory,此时执行这个Factory 就能够实现“提前执行 BeanPostProcessor”的操作,并且获取执行后的新 Bean 实例
当初减少一个 ObjectFactory,用来实现提早解决:
public interface ObjectFactory<T> { T getObject() throws BeansException;}
而后再创立一个 singletonFactories,作为咱们新的中间状态缓存容器,不过这个容器存储的并不是 Bean 实例,而是创立 Bean 的实现代码
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
当初再写一个“提前执行”BeanPostProcessor 的 ObjectFactory,增加到 singletonFactories 中。
//实现bean 的 instantiate 后//创立一个对该 Bean 提前执行 BeanPostProcessor 的 ObjectFactory//最初增加到 singletonFactories 中addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean) );protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); ...... } }}protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { //提前执行 BeanPostProcessor for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject;}
再次回到下面循环依赖的例子,如果在对 ABean 执行 populate 时,又发现了 BBean 的援用,那么此时先从咱们这个新的提早解决+提前执行的缓存容器中查找,不过当初找到的曾经不再是一个 BBean 实例了,而是咱们下面定义的那个getEarlyBeanReference 的 ObjectFactory ,通过调用 ObjectFactory.getObject() 来获取提前执行 BeanPostProcessor 的这个 ABean 实例。
如下图所示,在对 ABean 执行 populate 时,发现了对 BBean 的援用,那么间接从 singletonFactories 中查找 BBean 的 ObjectFactory 并执行,获取 BeanPostProcessor 加强/替换后的新 Bean
当初因为咱们的中间状态数据从 Bean 实例变成了 ObjectFactory,所以还须要在初始化之后,再检查一下 singletonFactories 是否有以后 Bean,如果有的化须要手动调用一下 getObject 来获取最终的 Bean 实例。
通过“提早执行”+“提前执行”两个操作,终于解决了这个循环依赖的问题。不过提前执行 BeanPostProcessor 会导致最终执行两遍 BeanPostProcessor ,这个执行两遍的问题还须要解决。
这个问题解决倒还算简略,在那些会更换原对象实例的 BeanPostProcessor 中减少一个缓存,用来存储曾经加强的 Bean ,每次调用该 BeanPostProcessor 的时候,如果缓存中曾经存在那就阐明创立过了,间接返回上次创立的即可。Spring 为此还独自设计了一个接口,命名也很形象 - SmartInstantiationAwareBeanPostProcessor
如果你定义的 BeanPostProcessor 会加强并替换原有的 Bean 实例,肯定要实现这个接口,在实现内进行缓存,防止反复加强
貌似当初问题曾经解决了,一开始设计的 earlySingletonObjects 也不须要了,间接应用咱们这个中间状态缓存工厂 - singletonFactories 就搞定了问题。
不过……如果依赖关系再简单一点,比方像上面这样,ABean 中有两个属性都援用了 BBean
那么在对 ABean 执行 populate 时,先解决 refB 这个属性;此时从 singletonFactories 中查找到 BBean 的这个提前执行 BeanPostProcessor 的 ObjectFactory,调用 getObject 获取到提前执行 BeanPostProcessor 的 BBean 实例,注入到 refB 属性中。
那到了 refB1 这个属性时,因为 BBean 还是一个没有创立实现的状态(singletonObjects 中不存在),所以依然须要获取 BBean 的 ObjectFactory,执行 getObject,导致又对 BBean 执行了一遍 BeanPostProcessor。
为了解决这个屡次援用的问题,还是须要有一个中间状态的缓存容器 - earlySingletonObjects。不过这个缓存容器和一开始提到的那个 earlySingletonObjects 有一点点不同;一开始提到的 earlySingletonObjects 是存储只执行了 instantiate 状态的 Bean 实例,而咱们当初存储的是执行 instantiate 之后,又提前执行了 BeanPostProcessor 的那些 Bean。
在提前执行了 BeanPostProcessor 之后,将返回的新的 Bean 实例也增加到 earlySingletonObjects 这个缓存容器中。这样就算处于中间状态时有屡次援用(屡次 getBean),也能够从 earlySingletonObjects 获取曾经执行完 BeanPostProcessor 的那个 Bean,不会造成反复执行的问题。
总结
回顾一下下面一步步解决循环依赖的流程,最终咱们通过一个提早解决的缓存容器,加一个提前执行结束BeanPostProcessor 的中间状态容器就完满解决了循环依赖的问题
至于 singletonObjects 这个缓存容器,它只用来存储所有创立实现的 Bean,和解决循环依赖关系并不大。
至于这个解决机制,叫不叫“三级缓存”……见仁见智吧,Spring 在源码/正文中也没有(3-level cache之类的字眼)。而且要害的循环依赖解决,只是“二级”(提早解决的 Factory + 提前执行 BeanPostProcessor 的Bean),所谓的“第三级”是应该是指 singletonObjects。
上面用一张图,简略总结一下解决循环依赖的外围机制:
不过提前执行 BeanPostProcessor 这个操作,算不算突破了原有的设计呢?本来 BeanPostProcessor 可是在创立Bean 的最初阶段执行的,可当初为了解决循环依赖,给挪动到 populate 之前了。尽管是一个不太优雅的设计,但用来解决循环依赖也不错。
只管 Spring 反对了循环依赖(仅反对属性依赖形式,构造方法依赖不反对,因为实例化都实现不了),但理论我的项目中,这种循环依赖的关系往往是不合理的,应该从设计上就防止。
原创不易,禁止未受权的转载。如果我的文章对您有帮忙,就请点赞/珍藏/关注激励反对一下吧❤❤❤❤❤❤