Spring Bean循环依赖学习与探究

news2024/12/27 11:44:48

文章目录

  • 原理学习
  • 源码溯源

本文参考:

画图带你彻底弄懂三级缓存和循环依赖的问题

Spring 三级缓存解决bean循环依赖,为何用三级缓存而非二级_笑矣乎的博客-CSDN博客

Spring为何需要三级缓存解决循环依赖,而不是二级缓存?_石杉的架构笔记的博客-CSDN博客

原理学习

主要的三级缓存工作机理学习参考 画图带你彻底弄懂三级缓存和循环依赖的问题

文章中解决了“是什么”的问题,这里主要梳理下”为什么“的问题,即为什么非得是三级缓存才能解决呢?也就是上面积累下来的问题

七、不用三级缓存,用二级缓存能不能解决循环依赖

遇到这种面试题,你就跟面试官说,如果行的话,Spring的作者为什么不这么写呢?

哈哈,开个玩笑,接下来说说到底为什么不行。

这里我先说一下前面没提到的细节,那就是通过ObjectFactory获取的Bean可能是两种类型,第一种就是实例化阶段创建出来的对象,还是一种就是实例化阶段创建出来的对象的代理对象。至于是不是代理对象,取决于你的配置,如果添加了事务注解又或是自定义aop切面,那就需要代理。这里你不用担心,如果这里获取的是代理对象,那么最后完全创建好的对象也是代理对象,ObjectFactory获取的对象和最终完全创建好的还是同一个,不是同一个肯定会报错,所以上面的理论依然符合,这里只是更加的细节化。

有了这个知识点之后,我们就来谈一下为什么要三级缓存。

第一级缓存,也就是缓存完全创建好的Bean的缓存,这个缓存肯定是需要的,因为单例的Bean只能创建一次,那么肯定需要第一级缓存存储这些对象,如果有需要,直接从第一级缓存返回。那么如果只能有二级缓存的话,就只能舍弃第二级或者第三级缓存。

假设舍弃第三级缓存

舍弃第三级缓存,也就是没有ObjectFactory,那么就需要往第二缓存放入早期的Bean,那么是放没有代理的Bean还是被代理的Bean呢?

1)如果直接往二级缓存添加没有被代理的Bean,那么可能注入给其它对象的Bean跟最后最后完全生成的Bean是不一样的,因为最后生成的是代理对象,这肯定是不允许的;

2)那么如果直接往二级缓存添加一个代理Bean呢?

  • 假设没有循环依赖,提前暴露了代理对象,那么如果跟最后创建好的不一样,那么项目启动就会报错,
  • 假设没有循环依赖,使用了ObjectFactory,那么就不会提前暴露了代理对象,到最后生成的对象是什么就是什么,就不会报错,
  • 如果有循环依赖,不论怎样都会提前暴露代理对象,那么如果跟最后创建好的不一样,那么项目启动就会报错

通过上面分析,如果没有循环依赖,使用ObjectFactory,就减少了提前暴露代理对象的可能性,从而减少报错的可能。

假设舍弃第二级缓存

假设舍弃第二级缓存,也就是没有存放早期的Bean的缓存,其实肯定也不行。上面说过,ObjectFactory其实获取的对象可能是代理的对象,那么如果每次都通过ObjectFactory获取代理对象,那么每次都重新创建一个代理对象,这肯定也是不允许的。

从上面分析,知道为什么不能使用二级缓存了吧,第三级缓存就是为了避免过早地创建代理对象,从而避免没有循环依赖过早暴露代理对象产生的问题,而第二级缓存就是防止多次创建代理对象,导致对象不同。

源码溯源

为了从源码角度解决“不用三级缓存,用二级缓存能不能解决循环依赖”的问题,继续去找各种文章去解答:

参考 Spring 三级缓存解决bean循环依赖,为何用三级缓存而非二级_笑矣乎的博客-CSDN博客

DefaultSingletonBeanRegistry.java中有三级缓存,以及如何获取的源码

/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
          // 查询一级缓存
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
            // 若一级缓存不存在,查询二级缓存
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
              // 若二级缓存不存在,查询三级缓存
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

AbstractBeanFactory下面有一个createBean()的中心方法,其核心实现方法doCreateBean(),可以看出来是在类实例化以后,属性填充之前,先检查是否允许提前暴露,如果允许则将其加入三级缓存工厂中
在这里插入图片描述

文章作者认为二级缓存不能解决问题,主要是因为在AOP的场景下,注入到其他bean的,不是AOP代理对象,而是原始对象。但其实这也不完全对,完全可以让AOP代理对象注入到二级缓存中,但是这有悖于AOP的设计原理。AOP就是要等Bean实例化,属性填充,初始化完成之后才去 生成AOP代理对象的,如果使用二级缓存,又要将AOP代理对象注入的话,就需要强行将AOP的代理工作提前到早期暴露实例之前。

但是上面总归还是理论性的知识,在源码层面上,三级缓存究竟做了什么还需要探究,参考

Spring为何需要三级缓存解决循环依赖,而不是二级缓存?_石杉的架构笔记的博客-CSDN博客

三级缓存中key-value加入的是一个beanName 和一个ObjectFactory的工厂类

/**
	 * Add the given singleton factory for building the specified singleton
	 * if necessary.
	 * <p>To be called for eager registration of singletons, e.g. to be able to
	 * resolve circular references.
	 * @param beanName the name of the bean
	 * @param singletonFactory the factory for the singleton object
	 */
	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

在前面获取的时候,也是直接调用ObjectFactory的工厂类的getObject方法

// 若二级缓存不存在,查询三级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
  singletonObject = singletonFactory.getObject();
  this.earlySingletonObjects.put(beanName, singletonObject);
  this.singletonFactories.remove(beanName);
}

最终调用getEarlyBeanReference方法

/**
	 * Obtain a reference for early access to the specified bean,
	 * typically for the purpose of resolving a circular reference.
	 * @param beanName the name of the bean (for error handling purposes)
	 * @param mbd the merged bean definition for the bean
	 * @param bean the raw bean instance
	 * @return the object to expose as bean reference
	 */
	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

在这里插入图片描述
从getSingleton()中可以看出,在AOP的情况下,每次singletonFactory.getObject()取出来的都是不同的Aservice代理类,由于Aservice是单例的,这样子显然不行。因此将singletonFactory.getObject()取出来的东西放入二级缓存中,key还是beanName,这样子别的Bean在取的时候直接就可以从二级缓存中拿出来,不必每次调用singletonFactory.getObject()产生一个新的代理对象。

总而言之,在没有AOP的情况下,确实可以通过两级缓存解决循环依赖问题,但是如果加上AOP,两级缓存就不行了。

最后再复习下CGLib动态代理的用法:

package com.jxz.AopTest;

import net.sf.cglib.proxy.Enhancer;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/9/16
 */
public class CGlibTest {
    public static void main(String[] args) {
        WeakService weakService = new WeakService();

        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(weakService.getClass().getClassLoader());
        enhancer.setSuperclass(weakService.getClass());
        enhancer.setCallback(new JXZMethodInterceptor());

        // 创建代理类
        WeakService proxy = (WeakService)enhancer.create();
        proxy.f("I'm jxz");
    }
}
package com.jxz.AopTest;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author jiangxuzhao
 * @Description
 * @Date 2023/9/16
 */
public class JXZMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("增强前...");
        // 这里调用父类方法,不然一直调用本方法死循环了
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("增强后...");
        return result;
    }
}

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

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

相关文章

如何用在线模版快速制作活动海报?

在时代的发展和信息传播的快速发展下&#xff0c;活动海报成为了宣传活动的重要方式之一。设计一张吸引眼球的活动海报&#xff0c;不仅能够有效传递信息&#xff0c;还能够吸引人们的注意力。那么&#xff0c;在这里我将教会大家如何设计活动海报&#xff0c;只需要三分钟&…

SAP MM学习笔记31 - 已割当供给元的购买依赖

上次学习了未割当供给元的购买依赖&#xff08;未分配供应商采购申请&#xff09;&#xff0c;咱们本章来学习一下 已割当供给元的购买依赖如何处理。 SAP MM学习笔记30 - 未割当供给元的购买依赖_东京老树根的博客-CSDN博客 如下图所示&#xff0c;利用 - 购买依赖割当一览&…

Vue记录(下篇)

Vuex getters配置项 *Count.vue <template><div><h1>当前求和为&#xff1a;{{$store.state.sum}}</h1><h3>当前求和的10倍为&#xff1a;{{$store.getters.bigSum}}</h3><select v-model.number"n"><option value&q…

150.逆波兰表达式求值

目录 一、题目 二、分析代码 三、中缀表达式转后缀表达式 一、题目 150. 逆波兰表达式求值 - 力扣&#xff08;LeetCode&#xff09; 二、分析代码 class Solution { public:int evalRPN(vector<string>& tokens) {stack<int>s;for(auto ch:tokens){if(ch!…

小白备战大厂算法笔试(九)——九大排序算法

文章目录 排序选择排序冒泡排序插入排序快速排序基准数优化尾递归优化 归并排序堆排序桶排序计数排序基数排序排序算法对比 排序 评价维度&#xff1a; 运行效率&#xff1a;我们期望排序算法的时间复杂度尽量低&#xff0c;且总体操作数量较少&#xff08;即时间复杂度中的常…

基于Yolov8的交通标志牌(TT100K)识别检测系统

1.Yolov8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的&#xff08;SOTA&#xff09;模型&#xff0c;它建立在先前YOLO成功基础上&#xff0c;并引入了新功能和改进&#xff0c;以进一步提升性能和灵活…

在gazebo仿真环境中加载多个机器人

文章目录 前言一、基本概念1、xacro2、Gazebo 加载单个机器人模型 二、原先launch文件代码三、 修改launch文件加载多个机器人总结 前言 单个机器人的各项仿真实验都基本完成&#xff0c;也实现了远程控制&#xff0c;接下来主要对多机器人编队进行仿真实验&#xff0c;在进行…

Git 命令图形化在线练习

git 命令在线练习网址如下: http://onlywei.github.io/explain-git-with-d3/ 在master上先提交2个commit,创建3个分支,分支1打5个commit,分支2打6commit ,分支3commit,master分支打9commit. git commit -m "master c 1" git commit -m "master c 1"git …

程序员必备神器:He3万能工具箱全解析

He3是一个为前端、后端开发者打造的终极工具箱软件&#xff0c;提供了近400的功能&#xff0c;将开发效率提升到一个新的水平。。本文将介绍 He3 开发者工具箱的主要功能和特点。 He3包含Web版和客户端&#xff0c;客户端支持windows和mac。下载后即可使用非常方便。 先睹为快…

FL Studio v21.1.1.3750 Producer Edition inc crack官方中文免费激活版功能介绍及百度网盘下载

FL Studio v21.1.1.3750 Producer Edition inc crack官方中文免费激活版是一款功能强大的软件音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。它代表了25多年的创新发展&#xff0c;在一个软件包中拥有您所需的一切&#xff0c;以创作、编排、录制、编辑、混音和掌…

C++之vector元素访问函数operator[]、at、front、back、data总结(二百零三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

如何在 Excel 中计算日期之间的天数

计算两个日期之间的天数是 Excel中的常见操作。无论您是规划项目时间表、跟踪时间还是分析一段时间内的趋势&#xff0c;了解如何在 Excel 中查找日期之间的天数都可以提供强大的日期计算功能。 幸运的是&#xff0c;Excel 提供了多种简单的方法来获取两个日期之间的天数。继续…

Javascript EventListener 事件监听 (mouseover、mouseout)

事件指的是在html元素上发生的事情&#xff0c;例如图片元素被点击事件触发时&#xff0c;可设置执行一段js代码。对事件作出反应&#xff0c;通过元素的事件属性&#xff0c;启用事件监听器。 事件监听器是指 addEventListener (给DOM对象添加事件处理程序) 和 removeEventLis…

441分2023级东南大学920专业基础综合信号和数字电路考研上岸经验分享信息科学与工程学院

写在前面的话 本人是23年考生&#xff0c;本科就读于西电电子信息工程&#xff0c;以441分总分&#xff08;数学一149&#xff0c;英语83&#xff0c;专业课137&#xff0c;政治73&#xff09;考上东南信院电路与系统专业。以下所言皆是考研历程中的重要感悟&#xff0c;因为一…

c语言练习61:malloc和free

malloc和free malloc C语⾔提供了⼀个动态内存开辟的函数&#xff1a; 1 void* malloc (size_t size); 这个函数向内存申请⼀块连续可⽤的空间&#xff0c;并返回指向这块空间的指针。 • 如果开辟成功&#xff0c;则返回⼀个指向开辟好空间的指针。 • 如果开辟失败&…

mysql的变量

在 MySQL 中变量分为三种类型 : 系统变量、用户定义变量、局部变量。 系统变量 系统变量 是 MySQL 服务器提供&#xff0c;不是用户定义的&#xff0c;属于服务器层面。分为全局变量&#xff08; GLOBAL &#xff09;、会话变量&#xff08;SESSION &#xff09;。 查看系统变…

选择适合您网站的SSL证书,保障安全与信任

在如今数字化的时代&#xff0c;拥有一个安全可靠的网站是至关重要的。而SSL证书作为保护网站和用户数据安全的关键工具&#xff0c;选择适合自己网站的SSL证书成为了每个网站管理员必须面对的重要任务。下面将为您分享几个关键因素&#xff0c;帮助您做出明智的选择。 1. 网站…

Linux的调试工具 - gdb(超详细)

Linux的调试工具 - gdb 1. 背景2. 开始使用指令的使用都用下面这个C语言简单小代码来进行演示&#xff1a;1. list或l 行号&#xff1a;显示文件源代码&#xff0c;接着上次的位置往下列&#xff0c;每次列10行。2. list或l 函数名:列出某个函数的源代码。3. r或run: 运行程序。…

链队列的基本操作(带头结点,不带头结点)

结构体 typedef struct linknode{int data;struct linknode* next;后继指针 }linknode; typedef struct {linknode* front, * rear;//队头队尾指针 }linkquene; 初始化队列&#xff08;带头结点&#xff09; int initquene(linkquene* q)//初始化队列 {q->front q->r…

geant4创建自己的physicslist(以电磁物理为例)

1 基本概念 1.1 需要创建一个类继承 G4ModularPhysicist 每个physics都是继承 G4PhysicsConstruct Physicslist 由很多 physics组成&#xff0c;physics里面包含很多的process,也就是物理过程,光电效应就是一个process 1.2 model的概念:实现proces,一个process可以对应多个…