Spring 循环依赖及三级缓存

Spring在启动过程中,使用到了三个map,称为三级缓存。

Spring 循环依赖及三级缓存
Spring启动过程大致如下:
1.加载配置文件
2.解析配置文件转化beanDefination,获取到bean的所有属性、依赖及初始化用到的各类处理器等
3.创建beanFactory并初始化所有单例bean
4.注册所有的单例bean并返回可用的容器,一般为扩展的applicationContext

一级缓存

在第三步中,所有单例的bean初始化完成后会存放在一个Map(singletonObjects)中,beanName为key,单例bean为value。

第三步单例bean的初始化过程大致如下:
1.new出bean对象
2.填充属性
3.初始化bean,处理Aware接口并执行各类bean后处理器,执行初始化方法
4.如果存在循环依赖,解决之
5.此时bean已经可以被使用,进行bean注册(标记)并注册销毁方法。

二级缓存

根据以上步骤可以看出bean初始化是一个相当复杂的过程,假如初始化A bean时,发现A bean依赖B bean,即A初始化执行到了第2步,此时B还没有初始化,则需要暂停A,先去初始化B,那么此时new出来的A对象放哪里,直接放在容器Map里显然不合适,半残品怎么能用,所以需要提供一个可以标记创建中bean的Map(earlySingletonObjects),这就是二级缓存的由来,同时它还可以解决另一个问题,且看下面分析:
上述中A依赖了B,A初始化到一半时暂停并执行B的初始化,那么如果B也依赖了A呢,执行B的初始化到第2步时需要注入A怎么办,如果再去初始化一个A显然不可能,会进入无限死循环(而且AB都是单例,只有单例才会在容器启动时被初始化),这时可以把初始化了一半的A对象先注给B,反正大家这个时候都是半残,都不可用。所以二级缓存也解决了循环依赖,这里就有一个要求了,避免AB无限循环创建,则A和B都必须是单例的,Spring只解决单例的循环依赖,且只限于属性依赖,构造器依赖也无法解决。

三级缓存

上述的二级缓存貌似已经解决了bean初始化和循环依赖的问题,那么三级缓存(singletonFactories)是什么,存在的意义又是什么呢?当初这个问题困扰了我一阵,看源码也看不太明白,貌似是spring一贯的伎俩-为了扩展?网上搜了写资料,因为当时思维受限,搜索的姿势不对,搜到的答案不尽人意,看到有个人也猜测是为了扩展,就这么得过且过吧,spring博大精深也不是一朝一夕能吃透了。

不过这块没彻底搞清楚,始终是知识点的一个黑点,猜测无法得到证实直接说是为了扩展只会心里发虚,面试最怕这种似懂非懂。直到最近心血来潮又在网上找了找答案,这次搜索就比较直接“spring三级缓存的意义”,然后根据结果进一步一点点找,终于找到了一个大牛的详细分析,于是乎写出了个这篇重复造*。

对于单纯的bean初始化和循环依赖的解决,二级缓存就够了。当初的迷惑在于没有进一步深入源码,串联spring知识点,spring aop中代理对象的生成是在bean初始化的第3步,而二级缓存中的bean是半残的原始bean,循环依赖直接使用这个bean显然是不对的,aop就用不到了,而且spring也针对这种情况进行了报错,大致意思是“这里需要的是一个代理对象,你给个原始的对象,是不是不太合适呀?”。但是代理对象需要在初始化bean时生成,这时bean的属性填充已经结束了,这就矛盾了。

所以,spring在这块做了特殊处理,发现存在循环依赖且依赖的是代理对象时,先生成代理对象,解决循环依赖,然后再执行bean初始化,为了不影响原有逻辑同时提高初始化效率,设计了三级缓存,正常不会进入这块逻辑,原来怎么初始化依然不变,一旦出现循环依赖,则移除二级缓存,生成对象的工厂,提供代理对象的获取方法并放入三级缓存。若存在aop代理,则对象工厂会提前生了代理对象,后面也就不再生成代理对象了,aop和初始化并不冲突。

至于三级缓存为什么不直接是代理对象而是对象工厂?加入了aop循环依赖,则二级缓存不再处理循环依赖,只要存在循环依赖都会使用三级缓存,对象工厂提供的getObject获取依赖对象bean既可以是原始bean也可以是代理bean,同时支持使用和不使用aop类型的循环依赖。