参考文章:
spring循环依赖如何通过三级缓存解决 Spring 循环依赖_Java_江南一点雨_InfoQ写作社区。
源码一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】-腾讯云开发者社区-腾讯云
个人理解:在创建bean执行到doCreateBean方法时,会执行addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));(向三级缓存中添加)
getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法 也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑~~~ // 比如在getEarlyBeanReference()里可以实现AOP的逻辑 我理解各种PostProcessor可以返回生成的代理类。这样能够把代理类放入二级缓存,而不是放入原对象(这样其他类依赖注入的就是代理类,原bean也是代理类,是一致的)。
一般的 AOP 都是由 AbstractAutoProxyCreator 这个后置处理器来处理的,通过这个后置处理器生成代理对象,AbstractAutoProxyCreator 后置处理器是 SmartInstantiationAwareBeanPostProcessor 接口的子类,并且 AbstractAutoProxyCreator 后置处理器重写了 SmartInstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 方法;
而 @Async 是由 AsyncAnnotationBeanPostProcessor 来生成代理对象的,AsyncAnnotationBeanPostProcessor 也是 SmartInstantiationAwareBeanPostProcessor 的子类,但是却没有重写 getEarlyBeanReference 方法,默认情况下,getEarlyBeanReference 方法就是将传进来的 Bean 原封不动的返回去,(这样其他类依赖注入的是原类,而原bean是代理类,不一致会检测到报错)。因此使用@Async无法解决循环依赖问题。
三级缓存分别为
singletonObjects
:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用earlySingletonObjects
:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖singletonFactories
:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖
依赖注入时调用的getSingleton方法中,如果二级缓存没取到,则执行如下代码
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
即去三级缓存中取出beanFactory并执行这里的 getObject 方法,这个方法在执行的过程中,会去判断是否需要生成一个代理对象,如果需要就生成代理对象返回,如果不需要生成代理对象,则将原始对象返回即可。
最后,把拿到手的对象存入到二级缓存中以备下次使用,同时删除掉三级缓存中对应的数据。其实这里就是把AOP创建代理类的过程提前了。
防止循环依赖关键在于提前暴露,刚刚创建好的对象还没有进行任何赋值的时候,将之暴露出来放到缓存中,供其他 Bean 提前引用(二级缓存)。
但以下情况不能防止循环依赖:
基于构造器注入:如果 AService 依赖的 BService 是通过构造器注入的,那就会导致在创建 AService 原始对象的时候就需要用到 BService,去创建 BService 时候又需要 AService,这样就陷入到死循环了,对于这样的循环依赖执行时候就会出错。
prototype对象:scope 为 prototype 意思就是说这个 Bean 每次需要的时候都现场创建,不用缓存里的。那么 AService 需要 BService,所以就去现场创建 BService,结果 BService 又需要 AService,继续现场创建,AService 又需要 BService...,所以最终就陷入到死循环了。
@Async:如上文。简单理解:ServiceA最后经过initializeBean变成了代理类,但ServiceB之前通过三级缓存getObject获取的是A的原始类,这导致了版本不一致,就会报错。不理解的话看如下原因:
AsyncAnnotationBeanPostProcessor 也是 SmartInstantiationAwareBeanPostProcessor 的子类,但是却没有重写 getEarlyBeanReference 方法,默认情况下,getEarlyBeanReference 方法就是将传进来的 Bean 原封不动的返回去。
在 Bean 初始化的时候,Bean 创建完成后,后面会执行两个方法:
-
populateBean:这个方法是用来做属性填充的。
-
initializeBean:这个方法是用来初始化 Bean 的实例,执行工厂回调、init 方法以及各种 BeanPostProcessor。
大家先把这两点搞清楚,然后我来跟大家说上面代码的执行流程。
-
首先 AService 初始化,初始化完成之后,存入到三级缓存中。
-
执行 populateBean 方法进行 AService 的属性填充,填充时发现需要用到 BService,于是就去初始化 BService。
-
初始化 BService 发现需要用到 AService,于是就去缓存池中找,找到之后拿来用,但是!!!这里找到的 AService 不是代理对象,而是原始对象。因为在三级缓存中保存的 AService 的那个 ObjectFactory 工厂,在对 AService 进行提前 AOP 的时候,执行的是 SmartInstantiationAwareBeanPostProcessor 类型的后置处理器 中的 getEarlyBeanReference 方法,如果是普通的 AOP,调用 getEarlyBeanReference 方法最终会触发提前 AOP,但是,这里执行的是 AsyncAnnotationBeanPostProcessor 中的 getEarlyBeanReference 方法,该方法只是返回了原始的 Bean,并未做任何额外处理。
-
当 BService 创建完成后,AService 继续初始化,继续执行 initializeBean 方法。
-
在 initializeBean 方法中,执行其他的各种后置处理器,包括 AsyncAnnotationBeanPostProcessor,此时调用的是 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法,在该方法中为 AService 生成了代理对象。
-
在 initializeBean 方法执行完成之后,AService 会继续去检查最终的 Bean 是不是还是一开始的 Bean,如果不是(变成了代理类),就去检查初始 Bean 有没有被其他 Bean 引用过,如果被引用过,就会抛出来异常,也就是上图大家看到的异常信息。
详细源码见参考地址。
三级缓存整体流程大概为:
当我们创建一个 AService 的时候,通过反射刚把原始的 AService 创建出来之后,先去判断当前一级缓存中是否存在当前 Bean,如果不存在,则:
-
首先向三级缓存中添加一条记录,记录的 key 就是当前 Bean 的 beanName,value 则是一个 Lambda 表达式 ObjectFactory,通过执行这个 Lambda 可以给当前 AService 生成代理对象。
-
然后如果二级缓存中存在当前 AService Bean,则移除掉。
然后进行依赖注入,如果发现依赖有循环引用自身,就一次查询一二级缓存。,没有就查询三级缓存,执行getObject 方法。最后,把拿到手的对象存入到二级缓存中以备下次使用,同时删除掉三级缓存中对应的数据。