Spring Bean循环引用的处理


循环依赖解决的思想


如上图所示,ABean引用了BBean,同时BBean也引用了ABean


Spring在初始化时,会按照beanDefinitionNames的顺序(就是Bean的注册顺序)依次初始化所有Bean(对所有的Bean调用一次getBean),然后由BeanFactory进行初始化


初始化Bean有两个关键的流程:

  1. instantiateBean - 创建Bean对象
  2. 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的引用,先从加载完成的resisterd集合中获取,如果没有再从InCreation集合中获取,此时会发现ABean的实例,然后将ABean实例 populate到BBean的实例中,BBean加载完成,添加到registered集合中;回到ABean的populate过程,此时已经获取到BBean的返回,直接populate到ABean实例中,ABean加载完成,大功告成


当然,上述解决方案只是针对属性形式的注入,如果是构造函数的注入就没法处理了,因为连构造方法都没法执行,不能正确的实例化


Spring中的处理思想也是大同小异,此处就不贴代码了,详细可以参考org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

注意:虽然spring对循环引用做了处理,但这种引用关系在程序设计中是不太合理的,应该尽量避免


你可能感兴趣的