spring循环依赖

参考文章:

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无法解决循环依赖问题。


三级缓存分别为

  1. singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
  2. earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
  3. 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。

大家先把这两点搞清楚,然后我来跟大家说上面代码的执行流程。

  1. 首先 AService 初始化,初始化完成之后,存入到三级缓存中。

  2. 执行 populateBean 方法进行 AService 的属性填充,填充时发现需要用到 BService,于是就去初始化 BService。

  3. 初始化 BService 发现需要用到 AService,于是就去缓存池中找,找到之后拿来用,但是!!!这里找到的 AService 不是代理对象,而是原始对象。因为在三级缓存中保存的 AService 的那个 ObjectFactory 工厂,在对 AService 进行提前 AOP 的时候,执行的是 SmartInstantiationAwareBeanPostProcessor 类型的后置处理器 中的 getEarlyBeanReference 方法,如果是普通的 AOP,调用 getEarlyBeanReference 方法最终会触发提前 AOP,但是,这里执行的是 AsyncAnnotationBeanPostProcessor 中的 getEarlyBeanReference 方法,该方法只是返回了原始的 Bean,并未做任何额外处理。

  4. 当 BService 创建完成后,AService 继续初始化,继续执行 initializeBean 方法。

  5. 在 initializeBean 方法中,执行其他的各种后置处理器,包括 AsyncAnnotationBeanPostProcessor,此时调用的是 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法,在该方法中为 AService 生成了代理对象。

  6. 在 initializeBean 方法执行完成之后,AService 会继续去检查最终的 Bean 是不是还是一开始的 Bean,如果不是(变成了代理类),就去检查初始 Bean 有没有被其他 Bean 引用过,如果被引用过,就会抛出来异常,也就是上图大家看到的异常信息。

详细源码见参考地址。

三级缓存整体流程大概为:

当我们创建一个 AService 的时候,通过反射刚把原始的 AService 创建出来之后,先去判断当前一级缓存中是否存在当前 Bean,如果不存在,则:

  1. 首先向三级缓存中添加一条记录,记录的 key 就是当前 Bean 的 beanName,value 则是一个 Lambda 表达式 ObjectFactory,通过执行这个 Lambda 可以给当前 AService 生成代理对象。

  2. 然后如果二级缓存中存在当前 AService Bean,则移除掉。

然后进行依赖注入,如果发现依赖有循环引用自身,就一次查询一二级缓存。,没有就查询三级缓存,执行getObject 方法。最后,把拿到手的对象存入到二级缓存中以备下次使用,同时删除掉三级缓存中对应的数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值