spring中循环依赖的解决方案

1.什么是循环依赖
A对象依赖了对象B,B对象依赖了A对象
循环依赖在JAVA中实际上不是问题,因为对象之间互相依赖是很正常的事情
在这里插入图片描述

对于程序而言不会存在问题,但是,在Spring中循环依赖(出现循环依赖的Bean必须要是单例)就是⼀个问题了,为什么?
因为,在Spring中,⼀个对象并不是简单new出来了,⽽是会经过⼀系列的Bean的⽣命周期,就是因为 Bean的⽣命周期所以才会出现循环依赖问题。

要明⽩Spring中的循环依赖,得先明⽩Spring中Bean的⽣命周期
Bean的⽣命周期
Bean的⽣命周期指的就是:在Spring中,Bean是如何⽣成的?
被Spring管理的对象叫做Bean。Bean的⽣成步骤如下:

  1. Spring扫描class得到BeanDefinition
  2. 根据得到的BeanDefinition去⽣成bean
  3. ⾸先根据class推断构造⽅法
  4. 根据推断出来的构造⽅法,反射,得到⼀个对象(暂时叫做原始对象)
  5. 填充原始对象中的属性(依赖注⼊)
  6. 如果原始对象中的某个⽅法被AOP了,那么则需要根据原始对象⽣成⼀个代理对象
  7. 把最终⽣成的代理对象放⼊单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例 池拿即可

然后模拟一下spring中循环依赖的场景
创建AService 和BService 他们两个互相注入
在这里插入图片描述
然后就出现了下面这个问题
在这里插入图片描述
A创建时—>需要B---->B去创建—>需要A,从⽽产⽣了循环
程序执行过程如下
AService创建的时候需要去填充属性,就去创建BService属性
在这里插入图片描述
这个时候就出现了循环依赖
在这里插入图片描述
这个时候就需要用到缓存了

"二级缓存"如何解决循环依赖
我们将对象引用中加入二级缓存
在这里插入图片描述
AService初始化的时候得到AServicve代理对象加入到二级缓存,然后填充BService的时候到单利池中找BService,没有找到,就到二级缓存中找BService的代理对象,依旧没有找到,然后就去创建BService当然BService也会创建代理对象加入到二级缓存中,填充AService属性,到单利池中找,没有找到,然后到二级缓存中找,找到了代理对象,这个时候接着往下走,打破了循环依赖的问题
在这里插入图片描述
从上⾯这个分析过程中可以得出,只需要⼀个缓存就能解决循环依赖了,那么为什么Spring中还需要 用到三级缓存呢?
如果A的原始对象注⼊给B的属性之后,A的原始对象进⾏了AOP 产⽣了⼀个代理对象,此时就会出现,对于A⽽⾔,它的Bean对象其实应该是AOP之后的代理对象,⽽B 的a属性对应的并不是AOP之后的代理对象,这就产⽣了冲突。
因为是提前获取的Aservice对象,AService对象还未完成初始化,导致最终B依赖的A和最终的A不是同⼀个对象。
这个时候就需要用到三级缓存了
初始化AService,填充属性BService,到单利池中获取BService属性,没有获取到,就创建BService对象,然后填充AService属性,到单利池中拿AService,没有拿到,到二级缓存中拿AService也没有拿到,然后判断是不是出现了循环依赖,如果出现了循环依赖,使用AOP切面拦截,到三级缓存中获取,执行lambda表达式获取AService代理对象,放入到二级缓存中
其中执行lambda表达式获取的AServuice相当于前置操作了
在这里插入图片描述
在这里插入图片描述
当然如果没有出现依赖循环的话就不会执行AOP就会返回一个原始对象,然后等到程序加载完成以后,会重新生成AService代理对象
在这里插入图片描述
程序源码如下:
三级缓存singletonFactories
⾸先,singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的⽣命周期中,⽣成 完原始对象之后,就会构造⼀个ObjectFactory存⼊singletonFactories中。这个ObjectFactory是⼀个函 数式接⼝,所以⽀持Lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean)
上⾯的Lambda表达式就是⼀个ObjectFactory,执⾏该Lambda表达式就会去执⾏ getEarlyBeanReference⽅法,⽽该⽅法如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefi nition mbd, Object bean) {
   //原始对象
    Object exposedObject = bean;
   // 出现依赖循环的话执行AOP就会返回一个代理对象
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessor s()) {
	for (BeanPostProcessor bp : getBeanPostProcessors()) {
		if (bp instanceof SmartInstantiationAwareBeanPostProcess or) {
			SmartInstantiationAwareBeanPostProcessor ibp = (Smar tInstantiationAwareBeanPostProcessor) bp;
			exposedObject = ibp.getEarlyBeanReference(exposedObj ect, beanName);
		 }
 	 }
 }
 	//没有出现依赖循环的话就不会执行AOP就会返回一个原始对象
	return exposedObject;
}

该⽅法会去执⾏SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference⽅法,⽽这 个接⼝下的实现类中只有两个类实现了这个⽅法,⼀个是AbstractAutoProxyCreator,⼀个是 InstantiationAwareBeanPostProcessorAdapter,它的实现如下:

// InstantiationAwareBeanPostProcessorAdapter 
@Override 
public Object getEarlyBeanReference(Object bean, String beanName) thr ows BeansException { 
 	return bean;  
 }
// AbstractAutoProxyCreator 
 @Override 
 public Object getEarlyBeanReference(Object bean, String beanName) { 
 	Object cacheKey = getCacheKey(bean.getClass(), beanName);  
 	this.earlyProxyReferences.put(cacheKey, bean); 
 return wrapIfNecessary(bean, beanName, cacheKey); 
  }

所以很明显,在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了 getEarlyBeanReference⽅法,⽽该类就是⽤来进⾏AOP的。上⽂提到的 AnnotationAwareAspectJAutoProxyCreator的⽗类就是AbstractAutoProxyCreator。
那么getEarlyBeanReference⽅法到底在⼲什么?
⾸先得到⼀个cachekey,cachekey就是beanName。 然后把beanName和bean(这是原始对象)存⼊earlyProxyReferences中 调⽤wrapIfNecessary进⾏AOP,得到⼀个代理对象。
那么,什么时候会调⽤getEarlyBeanReference⽅法呢?
在这里插入图片描述
三级缓存总结

  1. singletonObjects:缓存某个beanName对应的经过了完整⽣命周期的bean
  2. earlySingletonObjects:缓存提前拿原始对象进⾏了AOP之后得到的代理对象,原始对象还没有进⾏ 属性注⼊和后续的BeanPostProcessor等⽣命周期
  3. singletonFactories:缓存的是⼀个ObjectFactory,主要⽤来去⽣成原始对象进⾏了AOP之后得到的 代理对象,在每个Bean的⽣成过程中,都会提前暴露⼀个⼯⼚,这个⼯⼚可能⽤到,也可能⽤不到, 如果没有出现循环依赖依赖本bean,那么这个⼯⼚⽆⽤,本bean按照⾃⼰的⽣命周期执⾏,执⾏完后 直接把本bean放⼊singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执 ⾏ObjectFactory提交得到⼀个AOP之后的代理对象(如果有AOP的话,如果⽆需AOP,则直接得到⼀ 个原始对象)。
  4. 其实还要⼀个缓存,就是earlyProxyReferences,它⽤来记录某个原始对象是否进⾏过AOP了。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值