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

news2025/2/28 21:28:21

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

MyAspect

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

SpringBean.xml

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<bean id="myAspect" class="com.cjf.bean.MyAspect"/>
    <bean id="a" class="com.cjf.bean.A">
        <property name="b" ref="b"/>
    </bean>
    <bean id="b" class="com.cjf.bean.B">
        <property name="a" ref="a"/>
    </bean>

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) {
		// 获取缓存key
		Object 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) {
	// 获取缓存key
    Object 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

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

image-20221121181849060

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

  • 即: B ( a = A ( b = n u l l ) ) 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代理的bean
	Object 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, D
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/24447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity游戏Mod/插件制作教程03 - 插件实例1: HelloWorld

准备工作 作为编程类的教程&#xff0c;果然第一个需要来一个传统项目——HelloWolrd。 在开始之前&#xff0c;我先贴一个链接&#xff0c;这是BepInex官方的开发手册 https://bepinex.github.io/bepinex_docs/v5.0/articles/dev_guide/index.html 有什么问题也可以翻阅官方的…

论文阅读【6】RRN:LSTM论文阅读报告(1)

lstm类似于Simple_RNN,但是又比他复杂很多.我是参考这个视频的老师讲解的,这个老师讲解的非常好.https://www.bilibili.com/video/BV1FP4y1Z7Fj?p4&vd_source0a7fa919fba05ffcb79b57040ef74756 lstm的最重要的设计就是那一条传输带,即为向量CtC_tCt​,过去的信息通过他传送…

跨程序共享数据:Android四大组件之内容提供器

跨程序共享数据&#xff1a;Android四大组件之内容提供器前言七、跨程序共享数据&#xff1a;Android四大组件之内容提供器7.1 内容提供器&#xff08;Content Provider&#xff09;简介7.2 运行时权限&#xff08;软件不能为所欲为&#xff0c;想要什么权限&#xff0c;还得主…

【project 】软件使用

project软件使用 1.如何为某任务或资源创建日历 创建新日历 工具->更改工作时间->新建->定义日历名称&#xff0c;选择“新建基准日历”->根据各承建商的日历创建相应的日历 使用新日历 拷贝各承建商的各项任务到指定的项目计划中&#xff0c;然后&#xff…

基于特征选择的二元蜻蜓算法(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

C. String Equality(思维)

Problem - 1451C - Codeforces Ashish有两个字符串a和b&#xff0c;每个字符串的长度为n&#xff0c;还有一个整数k。 他想通过对a进行一些&#xff08;可能是零&#xff09;操作&#xff0c;将字符串a转换成字符串b。 在一次操作中&#xff0c;他可以 选择一个索引i&#x…

哪吒汽车的技术发布会都发布了什么?纯干货抢先看

11月21日&#xff0c;哪吒汽车发布了浩智超算、浩智电驱、浩智增程三大技术品牌&#xff0c;并推出三款技术产品&#xff0c;包括智能汽车中央超算平台、800V SiC高性能电驱系统、高效三合一增程器。去年年底&#xff0c;哪吒曾经发布过山海平台&#xff0c;据说是一个支持哪吒…

性能环境搭建(0-CentOS7 安装配置)

1.前言 根据现有的组件&#xff0c;准备动手搭建一套完整的监控环境。既然是练手&#xff0c;还是在虚拟机里自己先练习一下。出了问题也好恢复。所有就先从最基本的开始。那就是操作系统开始搭建玩起来。 2.环境 资源有效利用吧&#xff0c;公司的资源能自由使用的那最方便…

数据结构-复杂度(一)

目录 一、什么是复杂度&#xff1f; 算法效率&#xff1a; 复杂度&#xff1a; 二、复杂度分类 一、时间复杂度 二、空间复杂度&#xff08;Space Complexity&#xff09; 了解数据结构之前需要了解复杂度。 一、什么是复杂度&#xff1f; 在介绍复杂度之前我们现分享一…

CengBox靶机

0x01 信息收集 nmap -sV 10.0.2.6 22 ssh端口&#xff0c;弱口令爆破为主 80 web页面 目录遍历&#xff0c;备份文件查找等 dirsearch -u http://10.0.2.6 获取了目录&#xff0c;发现存在一个maseradmin目录&#xff0c;可能存在些东西&#xff0c;继续扫。 dirsearch -u …

C规范编辑笔记(四)

大家好&#xff0c;今天来给大家分享一下C规范编辑笔记第四篇&#xff0c;距离我们C规范编辑笔记第三篇也快过去了一个月&#xff0c;这次继续分享一波~ 1、以大写形式声明常量&#xff0c; 为避免误解&#xff0c;常量值必须根据其类型使用后缀。这不仅有助于代码阅读&#x…

CSO面对面丨如何通过“联合作战”,加强银行安全体系建设

随着数字化转型的深入&#xff0c;以银行为代表的金融机构不断加码金融科技建设。然而随着线上业务量不断上升&#xff0c;银行面临的安全风险暴露面也愈大、问题愈加复杂。本期腾讯安全《CSO面对面》栏目&#xff0c;邀请到某头部商业银行安全主管&#xff0c;以金融行业的安全…

【Lilishop商城】No1-1.业务了解+划分各模块逻辑

目录 A1.整体业务逻辑 B1.模块整理 C1.运营后台 C2.店铺后台 C3.买方平台 B2.重点模块梳理图 C1.订单模块 C2.退货/退款模块&#xff08;即售后模块&#xff09; C3.促销活动模块 A2.模块划分&#xff08;自己思考的&#xff09; A3.数据结构划分&#xff08;自己思考…

算法设计与分析 SCAU11079 可以移动的石子合并(优先做)

11079 可以移动的石子合并&#xff08;优先做&#xff09; 时间限制:1000MS 代码长度限制:10KB 提交次数:25 通过次数:9 题型: 编程题 语言: G;GCC;VC;JAVA Description 有n堆石子形成一行(a1,a2,…,an&#xff0c;ai为第i堆石子个数)&#xff0c;现要将石子合并成一堆&…

Android App开发手机阅读中贝塞尔曲线的原理讲解及实现波浪起伏动画实战(附源码和演示视频 可直接使用)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、贝塞尔曲线的原理 贝塞尔曲线是一种用于二维图形的数学曲线。贝塞尔曲线由节点和线段构成&#xff0c;其中节点是可拖动的支点&#xff0c;而线段彷佛有弹性的牛皮筋。它除了起点和终点之外&#xff0c;不再描绘中间的折现…

嵌入式(驱动开发)(中断处理)

一、什么是中断 一种硬件上的通知机制&#xff0c;用来通知CPU发生了某种需要立即处理的事件 分为&#xff1a; 内部中断 CPU执行程序的过程中&#xff0c;发生的一些硬件出错、运算出错事件&#xff08;如分母为0、溢出等等&#xff09;&#xff0c;不可屏蔽外部中断 外设发…

SpringBoot怎么整合第三方缓存技术/EhCache缓存技术使用以及Redis缓存技术使用怎么在SpringBoot中使用

写在前面&#xff1a; 继续记录自己的SpringBoot学习之旅&#xff0c;这次是SpringBoot应用相关知识学习记录。若看不懂则建议先看前几篇博客&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 3.5 整合第三方技术 3.5.1 缓存 3.5.1.1 介绍 缓…

2022/11/21[指针] 多维数组与指针的联系

1、指向数组元素的指针变量 #include<stdio.h> int main() {int* p;int a[3][4] { {1,2,3,4},{5,6,7,8},{9,10,11,12} };int i, j;//将第0行第0列的地址赋给pfor (p a[0]; p < a[0] 12; p)//注意是a[0]{if ((p - a[0]) % 4 0)printf("\n");printf("…

java--Lambda(2)表达式语法

文章目录0 写在前面1 Lambda 表达式的五种形式1.1 不包含参数&#xff1a;1.2 包含一个参数&#xff1a;1.3 有多个参数1.4 表达式主体是不是一个代码块1.5 不声明参数类型2 写在末尾0 写在前面 最基本的 Lambda 表达式&#xff0c;它由三部分组成具体格式是这样子的&#xff…

【Py】使用flask-apscheduler动态调整作业参数(附源码)

之前的项目常使用Apscheduler进行定时任务调度&#xff0c;但最近想通过接口对这些任务进行动态调整&#xff0c;比如调整任务启停、调整任务执行时间、间隔时间等等 flask-apscheduler这个基于flask的库能够满足上面的需求&#xff0c;而且由于基于flask&#xff0c;所以我常…