Spring 如何解决循环依赖
一、背景
我们都知道 Spring 可以通过 xml,或者解析我们的注解,通过扫描所有资源文件,从而将所有匹配到的资源封装成为一个 BeanDefinition 注册到我们的 BeanFactory 中。此时,Spring 已经知道了所有我们想要注册到容器中的 BeanDefinition,下一步就是将 BeanDefinition 实例化,这样才能提供出来给我们使用。
二、Spring 中 Bean 的实例化
我们发现 Spring 整个加载过程都在 AbstractApplicationContext.refresh()中去完成。
我们着重关注一下 finishBeanFactoryInitialization 方法,它是 Spring 实例化的入口方法。
获取 BeanFactory 中所有的 beanDefinition 名称
合并 RootBeanDefinition
非抽象的,单例的,非懒加载的就实例化
是否实现了 FactoryBean 接口,如果是加一个 &前缀调用内部的 getObject,否则直接获取
首先尝试从缓存中获取 getSingleton(beanName),(首次获取必然获取不到)接着进入创建方法
单例创建之前的操作:加入到正在创建的一个 set 集合中 singletonsCurrentlyInCreation
调到外部的匿名类中的实例化方法,如果有值已经创建成功 singletonFactory.getObject();
调到 doCreateBean 创建实例 BeanWrapper
允许早期引用加入单例工厂直接返回这个 bean 的引用。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
填充属性的值 populateBean
initializeBean

三、Spring 容器如何解决循环依赖
什么是循环依赖

循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 CircleA 引用 C ircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,则它们最终反映为一个环。
首先我们需要明确的一点是:Spring 只会处理上述类型的循环依赖(单例,非构造函数注入)其它情况直接报错。
Spring 在处理 Bean 实例化的过程中是如何解决循环依赖的呢?我们需要着重关注如下 3 个 Map。
具体步骤如下:
CircleClassA 在实例化的时候 首先从缓存中获取不到,然后进入创建方法,接着将 CircleClassA 加入到 singletonsCurrentlyInCreation 中,并在 singletonFactories 加入一个 getEarlyBeanReference,表示当前 CircleClassA 正在创建中。
当 CircleClassA 填充属性的值 populateBean 时,发现依赖了 CircleClassB,触发 CircleClassB 的实例化。
实例化 CircleClassB,首先从缓存中获取不到,然后进入创建方法,接着将 CircleClassB 加入到 singletonsCurrentlyInCreation 中,并在 singletonFactories 加入一个 getEarlyBeanReference,表示当前 CircleClassB 正在创建中。
当 CircleClassB 填充属性的值 populateBean 时,发现依赖了 CircleClassA,触发 CircleClassA 的实例化。
再次进入 CircleClassA 的实例化方法,此时虽然 singletonObjects 中获取不到 CircleClassA,但是检测到 CircleClassA 存在早期暴露的实例因此尝试从 earlySingletonObjects 中获取,首次调用获取不到从 singletonFactories 中获取,取到之后将 CircleClassA 放入 earlySingletonObjects,并提供给 CircleClassB 填充属性的值 populateBean 时使用。(此时的 CircleClassA 只是个引用的地址,实际上并不是一个完整的 CircleClassA)。
此时 CircleClassB 已经完成了(内部依赖的 CircleClassA 是个不完整的实例)并提供给 CircleClassA 填充属性的值 populateBean 时使用。CircleClassA 完成了 CircleClassB 的注入,它变成了一个完整的实例。
又由于 CircleClassB 中引用了 CircleClassA 的一个地址。所以它也同时变成了一个完整的。
实例化完成之后删除早期引用 map,并放入单例 map 中缓存 singletonObjects。

程序员的核心竞争力其实还是技术,因此对技术还是要不断的学习,关注 “IT 巅峰技术” 公众号 ,该公众号内容定位:中高级开发、架构师、中层管理人员等中高端岗位服务的,除了技术交流外还有很多架构思想和实战案例。
作者是 《 消息中间件 RocketMQ 技术内幕》 一书作者,同时也是 “RocketMQ 上海社区”联合创始人,曾就职于拼多多、德邦等公司,现任上市快递公司架构负责人,主要负责开发框架的搭建、中间件相关技术的二次开发和运维管理、混合云及基础服务平台的建设。
版权声明: 本文为 InfoQ 作者【IT巅峰技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/bcb3c2abddafd15bb071d564d】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论