Spring 三级缓存解决循环依赖源码分析

SpringBoot 三级缓存解决循环依赖源码分析 针对这个问题,解决方式是借助三级缓存提供提前创建代理对象的触发点,并使用缓存标记目标对象的代理已经创建。笔者提到第三级缓存实际是存放到对象工厂,通过对象工厂的函数式接口来实现获取对象,则此处将触发。则证明已经为目标对象创建过代理对象了,不需要再重新创建,从而避免多个代理对象存在造成的不一致问题。,笔者提到从容器中获取对象时首先是从缓存中获取,则对于创建中的对象 A ,首先将调用。的处理流程中,可能创建出两个不同的 A 对象,违反默认的单例原则。需注意该缓存是为了避免重复创建目标对象的代理对象。 阅读详情

什么是循环依赖?

ServiceA依赖ServiceB,ServiceB依赖ServiceA。
启动Spring项目时,如果想实例化ServiceA,创建完ServiceA对象后,需要依赖注入ServiceB的对象,而ServiceB实例化时,需要ServiceA,如此反复,导致循环依赖。

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}
@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

在这里插入图片描述
#Spring如何解决循环依赖问题?
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry中有三个Map缓存了单例对象的信息

	/** Cache of singleton objects: bean name to bean instance. */
	//缓存的是已经创建完成,且完成依赖注入的单例对象(一级缓存)
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	//缓存的是对象工厂(三级缓存)
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	//缓存的是已经创建完成,但未完成依赖注入的单例对象(二级缓存)
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
  • 一级缓存:缓存的是已经创建完成,且完成依赖注入的单例对象
  • 二级缓存:缓存的是已经创建完成,但未完成依赖注入的单例对象
  • 三级缓存:缓存的是对象工厂

大概步骤:

  1. 从缓存中获取ServiceA的对象实例,如果有则返回(第一次肯定没有),如果没有,则创建ServiceA对象实例。
  2. 创建ServiceA对象并放入缓存中、依赖注入ServiceB对象。
  3. 创建ServiceB对象实例并放入缓存中,依赖注入ServiceA对象。
  4. 从缓存中获取ServiceA对象,依赖注入到ServiceB中,ServiceB对象创建成功。
  5. 回到步骤2,ServiceA对象创建成功。

源码分析

随便建立个SpringBoot项目,在AbstractBeanFactory的Object getBean(String name) throws BeansException方法上打断点,因为主要观察ServiceA、ServiceB的实例化,因此打上条件断点name.equals(“serviceA”)||name.equals(“serviceB”),调试启动项目。
在这里插入图片描述
这就是调用链,可以看到代码是如何走到getBean方法的。
在这里插入图片描述
AbstractBeanFactory.doGetBean(
String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException
从一二三级缓存中获取ServiceA单例实例
在这里插入图片描述
在这里插入图片描述
第一次肯定获取不到。
如果是单例,从一级缓存中获取ServiceA的实例对象,取不到则调用createBean方法创建ServiceA实例
在这里插入图片描述

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);//从一级缓存中获实例
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					singletonObject = singletonFactory.getObject();//会回调上面的createBean方法
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);//将创建的单例对象放入到一级缓存,同时移除二级三级缓存
				}
			}
			return singletonObject;
		}
	}

createBean方法如下
在这里插入图片描述
doCreateBean方法如下

  • 首先实例化对象

  • 如果一级缓存中没有该对象,会将实例的对象放入到三级缓存中,同时移除二级缓存。

  • 然后依赖注入。这里ServiceA依赖ServiceB,因此又回到了AbstractBeanFactory.getBean方法来获取ServiceB。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		//返回的是一个BeanWrapper对象
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
		//创建bean对象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			//如果一级缓存中没有实例,将实例工厂放入到三级缓存中,并移除二级缓存
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//依赖注入
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					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(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

源码分析总结

上面代码的链路已经了解,做下总结。

1.1 首先获取ServiceA的实例AbstractBeanFactory.getBean(String name),name是"serviceA"
1.2 AbstractBeanFactory.doGetBean。
1.2.1 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton,从三级缓存中获取serviceA实例,未获取到。如果取到了直接返回。
1.2.2 如果是原型,直接调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean方法,创建实例。
如果是单例,调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton方法获取单例
1.3 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton
1.3.1 从一级缓存中获取serviceA,未获取到。取到了则直接返回
1.3.2 未取到serviceA则通过singletonObject = singletonFactory.getObject()回调org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean方法创建实例
1.3.3 调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry方法,如果一级缓存中没有实例,则将实例工厂放入到三级缓存中,并将实例从二级缓存中移除
1.4 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean
1.4.1 调用doCreateBean方法
1.5 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean
1.5.1 调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance方法创建实例,并包装成BeanWrapper。
1.5.2 调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean进行依赖注入,依赖注入时会获取serviceB实例,回到第一步。
1.5.3 调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.addSingletonFactory

流程图

如果A依赖B,且B依赖A。

  • 首先getBean获取A实例,第一次创建缓存中都没有,会走到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean方法创建Bean,创建完A实例对象,并且将A对象的工厂放入到三级缓存后,执行依赖注入B。
  • 依赖注入B时需要获取B对象,缓存中没有,依然会走到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean方法创建Bean,创建完B实例对象,并且将B对象的工厂放入到三级缓存后,执行依赖注入A。
  • 这一步是解决循环依赖的重点,依赖注入A时需要获取A对象。三级缓存中有A对象工厂,根据工厂生成A对象并放入到二级缓存中(此时的A对象没有依赖注入B对象,A中存的B时null),且将一级三级缓存中的A对象移除。这里涉及到AOP,如果对象需要AOP,则根据工厂生成AOP的代理对象,这里不展开了。
  • 依赖注入A对象成功后,B对象实例化成功,并将B对象缓存到一级缓存中,并将二三级缓存中的B对象移除。
  • 回到A依赖注入B的步骤,将B注入A中,A对象实例化成功,并将A对象缓存到一级缓存中,并将二三级缓存中的A对象移除。
  • A、B实例化完成后,只有一级缓存中存有完整的A、B对象,二三级缓存中没有A、B对象
    在这里插入图片描述
Spring使用三级缓存解决循环依赖的源码分析。 本文将通过源码去解析,Spring为什么使用三级缓存机制去解决循环依赖,能够读懂本文 的基础是Spring生命周期和对三级缓存有基本的 认知才能理解更加深刻。还不了解Spring生命周期的源码和三级缓存的基本原理的话,可查看本文之前写过的两篇文章。Spring管理Bean生命周期源码分析。Spring为什么要用三级缓存解决循环依赖?首先,我们需要回忆Spring处理循环依赖的三级缓存机制。 阅读详情

相关推荐

新能源知识库(62)组串式、集中式和高压直挂式

因需多台PCS及独立监控设备,单位功率成本比集中式高15%–20%。高压直挂式因省去变压器,大容量项目成本更低(>5MW时优势显著)。组串式储能、集中式储能和高压直挂式储能是当前主流的三类技术路线,其核心差异源于系统架构和控制逻辑的设计差异。组串式交流侧需变压器升压至10kV/35kV,高压直挂式可直接输出6kV/35kV电压,减少转换损耗。多台小功率PCS并联(每簇独立管理)单台大功率PCS(多簇直流侧并联)环流导致容量衰减(损失5%–8%)无变压器损耗(效率>99%)高压直挂电站(≥10MW)

竹君子之家 1515

深入理解 Spring 循环依赖之三级缓存(附源码分析)

学过 Spring 的都知道 Spring 利用三级缓存解决了循环依赖问题,那你知道什么是循环依赖?什么又是三级缓存?本篇将从源码层面分析 Spring 是怎么去利用三级缓存帮我们解决循环依赖问题。

weixin_42118323的博客 1157

无需Root!安卓模拟器+BurpSuite轻松搞定微信小程序抓包

本文详细介绍了如何在不root安卓设备的情况下,使用安卓模拟器和BurpSuite实现微信小程序抓包。通过夜神模拟器降级安卓版本、配置BurpSuite代理及安装证书等步骤,开发者可以轻松绕过微信的证书绑定,高效抓取小程序数据包。文章还提供了常见问题排查和高阶技巧,帮助开发者提升抓包效率。

weixin_29213655的博客 365

Spring三级缓存源码

Spring三级缓存类源码分析

骑个小蜗牛的博客 2万+

BLDC方波控制双闭环Simulink仿真模型

无刷直流电机方波控制双闭环Simulink仿真模型搭建详细教程,包括速度PI环、电流PI环、换向逻辑和受控电压源,仿真结果良好。

m0_57458432的博客 6869

攻防世界 web高手进阶区 9分题 Web_python_flask_sql_injection

继续ctf的旅程,开始攻防世界web高手进阶区的9分题,本文是Web_python_flask_sql_injection的writeup

闵行小鱼塘 1473

Spring框架宝典:彻底理解三级缓存策略

在前面的obtainFreshBeanFactory()方法中,Spring会加载所有的BeanDefinition,这些BeanDefinition可能来自XML文件、注解扫描等。而加载BeanDefinition的过程,就是调用DefaultListableBeanFactory的registerBeanDefinition方法。registerBeanDefinition方法就是将加载的BeanDefinition存储到一个内部Map中,留待后续使用。

Hanko的专栏 1592

再不入坑就晚了,深度神经网络概念大整理,最简单的神经网络是什么样子?

再不入坑就晚了,深度神经网络概念大整理,最简单的神经网络是什么样子?

我是香菜 3万+

Spring Boot 的IOC启动源码分析——三级缓存、后置处理器、@Enablexxx 注解、事件监听器等原理解析

一、IoC与DI IoC(控制反转)就是把原来代码里需要实现的对象创建、依赖,反转给容器来帮忙实现。我们需要创建一个容器,同时需要一种描述来让容器知道要创建的对象和对象的关系。这个描述的最具体的表现就是我们看到的配置文件。 DI(Dependency Injection,依赖注入) 就是指对象被动接收依赖类而不自己主动去找,换句话说,就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象时主动将它依赖的类注入给它。 二、Spring核心容器类 1. BeanFactory Spring中Bea

u012361112的博客 1036

【单智能体】AI 数据分析智能体案例讲解(附完整源码)

本案例将引导您构建一个基于 AI 的数据分析智能体,让用户能够通过自然语言查询来分析 CSV 和 Excel 文件中的数据,无需掌握 SQL 知识。该智能体使用 Agno Agent 框架集成 OpenAI GPT-4o 模型,并利用 DuckDB 提供高效的数据处理能力。

rengang66的博客 238

VTK GPU 加速渲染技术

/ 创建颜色工具// 创建立方体数据// 创建OpenGL映射器(注意使用vtkOpenGLPolyDataMapper而不是常规的vtkPolyDataMapper)// 启用VBO(顶点缓冲对象)// 声明几何体是静态的以获得最佳性能// 创建actor// 创建渲染器、渲染窗口和交互器renderWindow->SetWindowName("VTK自定义着色器示例");// 添加actor到渲染器。

深之JohnChen的专栏 1240

Spring中三级缓存是如何解决循环依赖问题-源码分析

编者按:今天周六,话不多说,上图,弄完又是神清气爽的一天~~

NEWstyle_的博客 250

手把手教你学PCIE(1.38)--基本概念:ATU(地址转换单元)

ATU(Address Translation Unit)是一种硬件单元,用于在PCIe系统中进行地址转换。它将设备的本地地址(外部地址)转换为系统内存或配置空间中的全局地址(内部地址),反之亦然。ATU的工作原理:通过地址匹配和转换,确保事务能够在不同的地址空间之间正确路由。配置寄存器:通过配置寄存器来设置地址转换规则。应用场景:地址空间隔离、访问控制、动态地址管理、虚拟化和多租户。

MHD0815的博客 2054

spring的三级缓存与源码分析--解决循环依赖

一级缓存(内存中的 singletonObjects)二级缓存(earlySingletonObjects)三级缓存(singletonFactories)

weixin_54925172的博客 1330

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

spring 三级缓存_Spring源码分析之循环依赖及解决方案

Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 Spring源码分析之Bean的创建过程详解 正文: 首先,我们需要明白什么是循环依赖?简单来说就是A对象创建过程中需要依赖B对象,而B对象创建过程中同样也需要A对象,所以...

weixin_39653733的博客 92

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Spring的循环依赖,循环依赖底层原理、三级缓存解决循环依赖源码分析

什么是循环依赖 很简单就是A对象依赖了B对象,B对象依赖了A对象 //A依赖了B class A{ public B b; } //B依赖了A class B{ public A a; } 如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情 A a=new A(); B b=new B(); a.b=b; b.a=a; 这样A,B就依赖上了 spring的循环依赖 @Component("aService") public clas

weixin_49849351的博客 784

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

spring框架中循环依赖问题的解决方式:三级缓存核心源码分析

若未移除二级缓存中 A 的旧引用,当 B 依赖 A 时,会直接获取二级缓存中的旧实例,而不是通过新工厂生成的新实例,导致 A 和 B 引用的不是同一个对象,最终循环依赖处理失败,所以这里remove掉二级缓存的旧的Bean对象是很有必要的。A在创建Bean A的时候发现依赖Bean B,好,实例化放到缓存中,但如果这个时候发生了线程切换,A还没来得及注入属性和依赖,就去执行线程B,那么B在创建Bean B的时候去一级缓存中找,发现有所依赖的Bean A,就拿着这个未完全初始化的Bean A,就会抛出异常。

2301_78139252的博客 550

详解Spring的循环依赖问题、三级缓存解决方案源码分析

详解Spring的循环依赖问题、三级缓存解决方案

qurious7的博客 616

【Spring源码分析】7、Spring 利用三级缓存解决循环依赖问题分析(面试热点)

文章目录1、提出循环依赖问题(1)问题描述2、循环依赖问题分析(1)spring逻辑分析3、相关源码以及交流群 1、提出循环依赖问题 Spring怎么解决循环依赖问题?这是spring一个非常典型的面试题,堪比spring生命周期一样常问 (1)问题描述 下面通过一个简单的spring bean互相注入的案例,引入循环依赖问题。首先我们准备四个类,配置类,IndexService,UserService,测试类,然后使用IndexService注入UserService,UserService注入Ind

Tellsea 1354

Spring源码分析+图解--Spring如何解决循环依赖的,为什么要用三级缓存

宏观背景 在了解循环依赖之前,我们先宏观的温习一下bean生命周期,即Spring中Bean的创建过程。 看过spring源码的应该都有注意到BeanFactory中有一段长长的注释,是Spring官方给我们提供的,推荐的Bean的生命周期的顺序。 * <p>Bean factory implementations should support the standard bean lifecycle interfaces * as far as possible. The full set

Elsa~的博客 548

Spring三级缓存解决循环依赖问题

解析理解在互相依赖的 Bean 是单例的、依赖注入的方式不都是构造注入的前提下如何使用三级缓存解决Spring中的循环依赖的问题。

trong_ 的烂笔头 6227

Spring 的循环依赖问题

本文通过分析源码解释了在 Spring 中是如何通过三级缓存来解决循环依赖问题,并解释了为什么 Spring 解决循环依赖会有限制以及三级缓存的必要性。

programmerCy的博客 1736

spring循环依赖通过三级缓存解决过程分析-其二

使用三级缓存,前提就是spring觉得大部分程序,都应该是没有循环依赖,或者说循环依赖很少。基于这个理念,理想情况就是大部分的bean都是直接完成整个生命周期,在初始化中检查是否创建代理对象,然后把bean存入一级缓存,为了完成这个理想,加入了二级、三级缓存,尤其是三级缓存,延迟bean的代理创建时机,只有当发生循环依赖的时候不得已提前创建bean的代理放入二级缓存,最好的情况就是二级缓存永远都是空的,创建一个bean加入一级缓存然后就移除对应三级缓存中的lambda。

hello world 997

Spring源码分析 循环依赖 三级缓存

Spring源码分析,循环依赖,三级缓存

namelessmyth的专栏 605

Spring 一文彻底搞定循环依赖

Spring是怎么解决循环依赖问题第一级缓存存放的是完全初始化完成的可以直接使用的对象,第二级缓存存放的是经过后置处理器处理(即代理过)的对象,第三级缓存村放的是刚实例化好没有经过后置处理器处理(即没有被代理过)的对象。首先我们需要明确,什么是循环依赖呢?

lvyuanj的专栏 1779

Spring为什么使用三级缓存解决循环依赖

循环依赖是指两个或多个Bean之间相互依赖对方,形成了一个闭环的依赖关系。Bean A依赖Bean BBean B又依赖Bean A@Component@Autowired@Component@Autowired这就像是"鸡生蛋、蛋生鸡"的问题:Spring在创建Bean A时,发现需要先注入Bean B,于是去创建Bean B,但创建Bean B时又发现需要注入Bean A,这样就陷入了死循环。循环依赖是指两个或多个Bean之间相互依赖,形成闭环。Spring的三级缓存。

weixin_37518202的博客 1170
上一篇: Redis学习笔记
下一篇: Spring中Bean的生命周期及源码详解
ludkjava
博客等级 码龄11年 173粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值