8.5 Spring解决循环依赖的机理(AOP)
创始人
2024-02-06 10:21:18

8.5 Spring解决循环依赖的机理(AOP)

MyAspect

@Aspect
public class MyAspect {@After(value = "execution(* com.cjf.bean.B.*(..))")public void myAfter(){System.out.println("最终通知的功能.........");}
}

SpringBean.xml

    

8.5.1、大致流程

  1. 先去一级缓存寻找 A ,没有去创建 A, 然后 A 将自己的 ObjectFactory ( lambda )放入到三级缓存中,初始化的时候需要 B ,去创建 B

  2. B 实例化同理 AB 将自己的工厂对象( lambda )放入到了 三级缓存),B 初始化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 AObjectFactory

  3. 然后调用 ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法,返回一个 A 的代理对象,然后把 proxyA 放入二级缓存里面,并删除三级缓存中的 ObjectFactoryA

  4. B 顺利赋值完毕,通过 populateBean 返回 ,然后调用 initializeBean 方法进行初始化操作

    • initializeBeanbean 的声明周期操作,以及上述说明的第二种 AOP 代理情况(代理 B 对象)
  5. 然后回来接着创建 A ,此时 B 已经创建结束,直接从一级缓存里面拿到 proxyB ,完成注入操作及初始化。

    • 由于完成注入操作及初始化返回的是 A 对象,不是 proxyA。再从二级缓存中获取到 proxyA 然后返回 proxyA
  6. 最后将 proxyA 放入到 一级缓存中,再从 二级缓存中删除 proxyA

可以看出,更非 AOP 的循环依赖类似,只不过多了进行代理操作。

8.5.2、Spring AOP 代理时机

参考来自 Spring源码最难问题《当Spring AOP遇上循环依赖》_bugpool的博客-CSDN博客

参考来自 Spring 为何需要三级缓存解决循环依赖,而不是二级缓存 - 半分、 - 博客园 (cnblogs.com)

对于 Spring AOP 代理,Spring AOP代理时机有2个:

  • 当自定义了 TargetSource ,则在 bean 实例化前完成 Spring AOP 代理并且直接发生短路操作,返回 bean

  • 正常情况下,都是在 bean 初始化后进行 Spring AOP 代理 ②

  • 如果要加上提前曝光代理,getEarlyBeanReference 可以说 3 种 ③

第二种,initializeBean (初始化 bean

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {// 调用bean初始化后置处理器处理Object current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}

调用 postProcessAfterInitialization (后置处理器的)方法进行代理

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {// 获取缓存keyObject cacheKey = getCacheKey(bean.getClass(), beanName);// 查看该bean是否被Spring AOP提前代理!而缓存的是原始的bean,因此如果bean被提前代理过,这此处会跳过// 如果bean没有被提前代理过,则进入AOP代理if (this.earlyProxyReferences.remove(cacheKey) != bean) {//返回一个新的代理对象return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

第三种,提前曝光代理

getEarlyBeanReference 方法

//AbstractAutowireCapableBeanFactory.java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {//返回一个生成的代理对象exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);}}return exposedObject;
}

getEarlyBeanReference 方法(不同的类),在这里 A 提前代理了。

// AbstractAutoProxyCreator.java
public Object getEarlyBeanReference(@Nullable Object bean, String beanName) {// 获取缓存keyObject cacheKey = this.getCacheKey(bean.getClass(), beanName);//将当前对象放入 earlyProxyReferences 中this.earlyProxyReferences.put(cacheKey, bean);return this.wrapIfNecessary(bean, beanName, cacheKey);return bean;
}

返回一个新的代理对象。

image-20221121180613964


8.5.3、提前曝光 A,创建 proxyA

情节说明(我们代理 AB):

  1. 先去一级缓存寻找 A ,没有去创建 A, 然后 A 将自己的 ObjectFactory ( lambda )放入到三级缓存中,初始化的时候需要 B ,去创建 B
  2. B 实例化同理 AB 将自己的工厂对象( lambda )放入到了 三级缓存),B 初始化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 AObjectFactory
  3. 然后调用ObjectFactory.getObject() 方法最终会调用 getEarlyBeanReference() 方法,返回一个 A 的代理对象,然后把 proxyA 放入二级缓存里面,并删除三级缓存中的 ObjectFactoryA

这里。通过上面第 333 种 提前曝光处理,创建 A 的代理对象

image-20221121181849060

一层一层的返回 ,sharedInstance 对象为 A 的代理对象( proxyA),然后对获取到的 proxyA 赋值处理,

  • 即:B(a=A(b=null))B(a=A(b=null))B(a=A(b=null)),但是这里的 AproxyA

image-20221121182246199


8.5.4、后置处理器创建 proxyB

此时:

  1. B 顺利赋值完毕,通过 populateBean 返回 ,然后调用 initializeBean 方法进行初始化操作
  • initializeBeanbean 的生命周期操作,以及上述说明的第二种 AOP 代理情况

对 B 对象进行后置处理器创建代理,返回 proxyB

image-20221121192307794

image-20221121192856920

  1. B 对象已经初始化完成, 此时 exposedObjectproxyB

proxyB 对象加入到一级缓存中( addSingleton(beanName, proxyB);

image-20221121194111902

  1. 然后回来接着创建 A ,此时 B 已经创建结束,直接从一级缓存里面拿到 proxyB ,完成对 A 对象的注入,以及对 原始 A 对象初始化操作(initializeBean

image-20221121184825150

// 6. 存在提前曝光情况下
if (earlySingletonExposure) {// earlySingletonReference:二级缓存,缓存的是经过提前曝光提前Spring AOP代理的beanObject earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {// exposedObject跟bean一样,//		说明初始化操作没用应用 getEarlyBeanReference (指AOP操作) 改变 exposedObject// 主要是因为exposedObject如果提前代理过,就会跳过Spring AOP代理,//		所以exposedObject没被改变,也就等于bean了if (exposedObject == bean) {// 将二级缓存中的提前AOP代理的bean赋值给exposedObject,并返回exposedObject = earlySingletonReference;}// 引用都不相等了,也就是现在的bean已经不是当时提前曝光的bean了else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {// dependentBeans也就是B, C, DString[] dependentBeans = getDependentBeans(beanName);Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}// 被依赖检测异常if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(...);}}}}

对于我们这里:

image-20221121202934389

此时 exposedObjectproxyA

最后将 proxyA 对象加入到一级缓存中( addSingleton(beanName, proxyB); )删除二级缓存中的 proxyA

完成 proxyA 对象的创建

8.5.5、注意事项

通过三级缓存拿到 ObjectFactory 对象后,调用 ObjectFactory.getObject() 方法最终会调用getEarlyBeanReference() 方法,我们会发现再执行一遍 singletonFactory.getObject() 方法又是一个新的代理对象,这就会有问题了,

  • 因为 A 是单例的,每次执行 singletonFactory.getObject() 方法又会产生新的代理对象,
  • 假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到 singleFactory 对象,执行 getObject() 方法又会产生新的代理对象,这是不行的,

因为 A 是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行 singletonFactory.getObject() 产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍 singletonFactory.getObject() 方法再产生一个新的代理对象,保证始终只有一个代理对象。

还有一个注意的点

  • 既然 singletonFactory.getObject() 返回的是代理对象,那么注入的也应该是代理对象,我们可以看到注入的确实是经过 CGLIB 代理的 A 对象。
  • 所以如果没有 AOP 的话确实可以两级缓存就可以解决循环依赖的问题,
  • 如果加上 AOP ,两级缓存是无法解决的,不可能每次执行 singleFactory.getObject() 方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。

绝大部分情况下,都是在 initializeBean(初始化 bean)进行创建代理对象的。只有循环依赖的时候,才会用到三级缓存,进行提前代理对象,也就是 getEarlyBeanReference

8.5.6、流程图

beanAop

相关内容

热门资讯

应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...