Spring 的循环依赖已经被说烂了,可能不少人也看吐了。但不少博客上说的仍是不够清楚,没有完整的表达出 Spring 的设计目的。只介绍了 What ,对于 Why 的介绍却不太够。java
本文会从设计角度,一步一步详细分析 Spring 这个“三级缓存”的设计原则,说说为何要这么设计。spring
Spring 中的每个 Bean 都由一个BeanDefinition 建立而来,在注册完成 BeanDefinition 后。会遍历BeanFactory中的 beanDefinitionMap 对全部的 Bean 调用 getBean 进行初始化。缓存
简单来讲,一个 Bean 的建立流程主要分为如下几个阶段:app
在完成上面的建立流程后,将 Bean 添加到缓存中 - singletonObjects,之后在 getBean 时先从缓存中查找,不存在才建立。函数
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
先抛开 Spring 的源码不谈,先看看按照这个建立流程执行会遇到什么问题post
首先是第一阶段 - 实例化,就是调用 Bean Class的构造函数,建立实例而已,没啥可说的。至于一些获取 BeanDefinition 构造方法的逻辑,不是循环依赖的重点。this
第二阶段 - 填充Bean,其目的是查找当前 Bean 引用的全部 Bean,利用 BeanFactory 获取这些 Bean,而后注入到当前 Bean 的属性中。spa
正常状况下,没有循环引用关系时没什么问题。好比如今正在进行 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
这个阶段分为执行初始化方法 - initMethod,和执行 BeanFactory 中定义的 BeanPostProcessor(BPP)。执行初始化方法没啥可说的,重点看看 执行BeanPostProcessor 部分。
BeanPostProcessor 算是 Spring 的灵魂接口了,不少扩展的操做和功能都是经过这个接口,好比 AOP。在populate 完成以后,Spring 会对 Bean 顺序执行全部的 BeanPostProcessor,而后返回 BeanPostProcessor 返回的新 Bean 实例(可能有修改也可能没修改)
//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization public 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 支持了循环依赖(仅支持属性依赖方式,构造方法依赖不支持,由于实例化都完成不了),但实际项目中,这种循环依赖的关系每每是不合理的,应该从设计上就避免。
原创不易,禁止未受权的转载。若是个人文章对您有帮助,就请点赞/收藏/关注鼓励支持一下吧❤❤❤❤❤❤