共计 1331 个字符,预计需要花费 4 分钟才能阅读完成。
循环依赖解决的思维
如上图所示,ABean 援用了 BBean,同时 BBean 也援用了 ABean
Spring 在初始化时,会依照 beanDefinitionNames 的程序(就是 Bean 的注册程序)顺次初始化所有 Bean(对所有的 Bean 调用一次 getBean),而后由 BeanFactory 进行初始化
初始化 Bean 有两个要害的流程:
- instantiateBean – 创立 Bean 对象
- populateBean – 填充 Bean,将 Bean 中的援用 Bean 填充至以后 Bean
那么问题来了,如果在 ABean populateBean 的过程中发现了另一个 BBean 的援用,此时须要对另一个 BBean 提前进行初始化操作(getBean),可是 BBean 初始化的时候也发现了 ABean 的援用,又会返回对 ABean 进行初始化,此时就会陷入死循环
抛开 Spring 的实现细节,这个问题也不难解决,当发现循环援用的时候,只须要提早 populateBean 的机会就行:
比方发现 BBean 的援用时,调用 getBean(BBean)操作,这个时候 B 不必实现全副初始化操作,只须要实现第一步 instantiateBean 而后就返回 BBean 的实例
当按 beanDefinitionNames 的程序初始化到 BBean 的时候,在对 BBean 进行 populateBean 就能够防止死循环的问题
对于下面的问题解决,关键点是要解决这个未齐全初始化然而有相互援用的对象。最容易想到的就是保护一个中间状态“半初始化”,代表只 instantiate 并没有进行 populate 的 Bean,这样就能够将实例化和注入“离开”进行,注入时只须要获取已对象的实例即可,对象是否 populate 实现并不关怀
这个半初始化状态实现也很简略,只须要保护两组对象列表,一个汇合负责存储已 instantiate 但并没有实现 populate 半初始化的 bean – InCreation,另一个汇合保护加载实现的 bean – registered
回到下面那个流程,当 ABean 执行完 instantial 后增加到下面的 InCreation 汇合中,此时 populate 会调用 BBean 的初始化,这时 Bbean 会发现对 ABean 的援用,先从加载实现的 InCreation 汇合中获取,如果没有再从 InCreation 汇合中获取,此时会发现 ABean 的实例,而后将 ABean 实例 populate 到 BBean 的实例中,BBean 加载实现,增加到 registered 汇合中;回到 ABean 的 populate 过程,此时曾经获取到 BBean 的返回,间接 populate 到 ABean 实例中,ABean 加载实现,功败垂成
当然,上述解决方案只是针对属性模式的注入,如果是构造函数的注入就没法解决了,因为连构造方法都没法执行,不能正确的实例化
Spring 中的解决思维也是大同小异,此处就不贴代码了,具体能够参考 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
留神:尽管 spring 对循环援用做了解决,但这种援用关系在程序设计中是不太正当的,应该尽量避免