当前位置: 首页 > news >正文

Spring三级缓存与依赖循环

前言

假设有两个类A和B,他们互相引用对方作为属性,那么实例化这两个类的时候就会产生依赖循环问题。Spring使用三级缓存解决了bean之间的依赖循环问题,前提是这些bean使用的是默认的单例模式。以下内容基于Spring源码分析。

三级缓存

Spring使用三级缓存保存不同时期的bean,三级缓存的位置在DefaultSingletonBeanRegistry这个类三种,三级缓存其实就是三个Map对象:

  • singletonObjects:一级缓存,存放完全实例化的bean对象。
  • earlySingletonObjects:二级缓存,存放的是已经实例化但是还没有填充属性的bean对象
  • singletonFactories:存放的是bean的工厂,可以从三级缓存获取bean的工厂对象,然后通过通常对象去实例化bean。

准备测试用例

版本信息

JDK:21

Spring:6.0.12

依赖

为了实现最小化的使用Spring容器的功能,之引入以下的依赖

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> </dependencies>

AB测试类

准备AB两个测试类,让他们互相引用对方作为属性

public class A { private B b; public A() { System.out.println("a的无参构造调用"); } public B getB() { return b; } public void setB(B b) { System.out.println("Aset注入B"); this.b = b; } @Override public String toString() { return "A{" + "b=" + b.hashCode() + '}'; } } public class B { private A a; public B() { System.out.println("b的无参构造调用"); } public A getA() { return a; } public void setA(A a) { System.out.println("Bset注入a"); this.a = a; } @Override public String toString() { return "B{" + "a=" + a.hashCode() + '}'; } }

Bean配置文件

由于使用的是最小化的Spring功能,所以没有引入注解功能,所以使用xml编写bean配置文件实例化bean。

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="a" class="com.dyl.spring6.A"> <property name="b" ref="b"/> </bean> <bean id="b" class="com.dyl.spring6.B"> <property name="a" ref="a"/> </bean> </beans>

测试类

使用ClassPathXmlApplicationContext加载xml配置文件,实现bean的注入。

@Test public void test1() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-bean.xml"); A a = context.getBean("a", A.class); B b = context.getBean("b", B.class); System.out.println(a); System.out.println(b); }

源码分析


ClassPathXmlApplicationContext

ClassPathXmlApplicationContext的构造方法进入,里面调用了refresh()方法,这个是实例化的入口

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); this.setConfigLocations(configLocations); if (refresh) { // 核心代码是这一个 this.refresh(); } }

进入到refresh()方法当中,发现这个方法调用了一个finishBeanFactoryInitialization()方法,这个方法对传入的bean工厂做了一些处理

this.finishBeanFactoryInitialization(beanFactory);

进入到finishBeanFactoryInitialization()方法当中,里面调用了bean工厂的preInstantiateSingletons()方法,这个方法会去实例化单例对象,即Spring当中使用工厂模式去创建bean

beanFactory.preInstantiateSingletons()

preInstantiateSingletons

进入到preInstantiateSingletons()方法内部,这个方法开始执行具体的实例化过程,这里循环bean名称,然后去实例化bean,中间有个if else判断,主要看单例bean的实例化,其他的非单例的不管。

public void preInstantiateSingletons() throws BeansException { if (this.logger.isTraceEnabled()) { this.logger.trace("Pre-instantiating singletons in " + this); } List<String> beanNames = new ArrayList(this.beanDefinitionNames); for(String beanName : beanNames) { RootBeanDefinition bd = this.getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (this.isFactoryBean(beanName)) { Object bean = this.getBean("&" + beanName); if (bean instanceof SmartFactoryBean) { SmartFactoryBean<?> smartFactoryBean = (SmartFactoryBean)bean; if (smartFactoryBean.isEagerInit()) { this.getBean(beanName); } } } else { // 获取单例bean this.getBean(beanName); } } } for(String beanName : beanNames) { Object singletonInstance = this.getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) { StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize").tag("beanName", beanName); smartSingleton.afterSingletonsInstantiated(); smartInitialize.end(); } } }

getBean&doGetBean

进入getBean方法获取bean,方法实际上又调用了doGetBean方法。

doGetBean方法的核心功能有:

  1. 从缓存当中获取bean
  2. 如果缓存当中没有的话就创建bean并放到缓存当中

以上两个核心功能其实都调用了getSingleton方法——获取单例对象,区别在于调用的getSingleton是经过重载之后的两个不同的方法,里面逻辑不同。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = this.transformedBeanName(name); // 从缓存获取bean Object sharedInstance = this.getSingleton(beanName); Object beanInstance; if (sharedInstance != null && args == null) { if (this.logger.isTraceEnabled()) { if (this.isSingletonCurrentlyInCreation(beanName)) { this.logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null); } else { if (this.isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } BeanFactory parentBeanFactory = this.getParentBeanFactory(); if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) { String nameToLookup = this.originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { AbstractBeanFactory abf = (AbstractBeanFactory)parentBeanFactory; return (T)abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly); } if (args != null) { return (T)parentBeanFactory.getBean(nameToLookup, args); } if (requiredType != null) { return (T)parentBeanFactory.getBean(nameToLookup, requiredType); } return (T)parentBeanFactory.getBean(nameToLookup); } if (!typeCheckOnly) { this.markBeanAsCreated(beanName); } StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate").tag("beanName", name); try { if (requiredType != null) { Objects.requireNonNull(requiredType); beanCreation.tag("beanType", requiredType::toString); } RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName); this.checkMergedBeanDefinition(mbd, beanName, args); String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for(String dep : dependsOn) { if (this.isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } this.registerDependentBean(dep, beanName); try { this.getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } if (mbd.isSingleton()) { // 创建bean,并加入缓存,最后返回bean sharedInstance = this.getSingleton(beanName, () -> { try { return this.createBean(beanName, mbd, args); } catch (BeansException ex) { this.destroySingleton(beanName); throw ex; } }); beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { Object prototypeInstance = null; try { this.beforePrototypeCreation(beanName); prototypeInstance = this.createBean(beanName, mbd, args); } finally { this.afterPrototypeCreation(beanName); } beanInstance = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean '" + beanName + "'"); } Scope scope = (Scope)this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { this.beforePrototypeCreation(beanName); Object var4; try { var4 = this.createBean(beanName, mbd, args); } finally { this.afterPrototypeCreation(beanName); } return var4; }); beanInstance = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); } } } catch (BeansException ex) { beanCreation.tag("exception", ex.getClass().toString()); beanCreation.tag("message", String.valueOf(ex.getMessage())); this.cleanupAfterBeanCreationFailure(beanName); throw ex; } finally { beanCreation.end(); } } return (T)this.adaptBeanInstance(name, beanInstance, requiredType); }

getSingleton1

先看一下第一个获取单例对象的方法,使用到了synchronized同步锁,代码核心逻辑就是按照顺序依次从123级缓存获取bean,那么就可以分为以下几种情况:

  1. 对于刚初始化的Spring容器,三级缓存肯定是都不存在bean的,所以这个方法无法获取到bean
  2. 对于首次实例化且存在依赖循环的情况的bean,会放到第三级缓存当中——后续会看到。第三级缓存存放的是bean的工厂对象,然后通过工厂对象去获取bean实例——工厂模式。从第三级缓存获取bean之后,会移除掉原第三级缓存的beanFactory,然后将bean放到二级缓存当中。
  3. 对于上面第二种情况的bean,在二级缓存当中并不是完全体的bean,如果此时再次调用getSingleton方法,那么就会直接从二级缓存当中获取
  4. 对于已经完全实例化的bean,会放到第一级缓存,getSingleton方法直接从第一级缓存的当中返回
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 从一级缓存获取bean Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { // 从二级缓存获取bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized(this.singletonObjects) { // 从一级缓存获取bean singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 从二级缓存获取bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 从三级缓存获取bean工厂 ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { // bean工厂获取bean singletonObject = singletonFactory.getObject(); // bean从三级缓存移除,移入二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

getSingleton2

对于beanA和beanB,第一次获取getBean的时候,肯定是不在任意一级缓存当中的,所以接下来看第二个getSingleton方法。这个方法比第一个getSingleton方法多了一个函数式接口传参,这个传参用于传入一个获取bean实例的方法,这里传递的是createBean方法。这第二个getSingleton方法的核心逻辑是:

  1. 尝试从一级缓存获取bean
  2. 一级缓存获取不到就调用外部传入的创建bean实例的方法,注意这里创建bean实例的方法返回的是一个完全实例化的bean,这个后续会看到
  3. 将完全实例化的bean放到一级缓存当中,即调用了addSingleton方法。进入addSingleton方法可以看到,逻辑很简单,即将bean放到一级缓存当中,然后将二级缓存和三级缓存对应的bean都移除掉。
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 (this.logger.isDebugEnabled()) { this.logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } this.beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = this.suppressedExceptions == null; if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet(); } try { // 创建bean singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException var17) { BeanCreationException ex = var17; if (recordSuppressedExceptions) { for(Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } this.afterSingletonCreation(beanName); } if (newSingleton) { // 将bean加入到一级缓存 this.addSingleton(beanName, singletonObject); } } return singletonObject; } }

createBean&doCreateBean

接下来看第二个获取单例对象getSingleton方法调用的创建对象createbean方法,这个方法会调用doCreateBean去创建bean,创建的是完全实例化的bean即已经实例化并填充好属性的bean。

看一下doCreateBean方法的核心逻辑:

  1. 判断是否是早期曝光对象,这个判断逻辑与bean的模式有关,必要条件之一就是bean需要处于单例模式,所以这也是为什么只有单例模式才能解决循环依赖的原因。
  2. 将bean的工厂放到第三级缓存当中 ,放的是bean的工厂此时并没有实例化bean,可以看到代码当中传入的是一个函数式的接口,只有在用到的时候才会调用。
  3. 填充bean的属性,这里会从Spring容器当中找到bean属性对应的值,如果没有找到的话就会去实例化对应的属性。有了这个方法,doCreateBean方法最终才会获取到完全实例化的bean。
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = this.createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } synchronized(mbd.postProcessingLock) { if (!mbd.postProcessed) { try { this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.markAsPostProcessed(); } } // 判断是否是早期曝光对象 boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName); if (earlySingletonExposure) { if (this.logger.isTraceEnabled()) { this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 将bean工厂放到第三级缓存当中 this.addSingletonFactory(beanName, () -> this.getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { // 填充bean的属性 this.populateBean(beanName, mbd, instanceWrapper); exposedObject = this.initializeBean(beanName, exposedObject, mbd); } catch (Throwable var18) { if (var18 instanceof BeanCreationException bce) { if (beanName.equals(bce.getBeanName())) { throw bce; } } throw new BeanCreationException(mbd.getResourceDescription(), beanName, var18.getMessage(), var18); } if (earlySingletonExposure) { Object earlySingletonReference = this.getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) { String[] dependentBeans = this.getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length); for(String dependentBean : dependentBeans) { if (!this.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."); } } } } try { this.registerDisposableBeanIfNecessary(beanName, bean, mbd); return exposedObject; } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } }

populateBean

populateBean方法用于给实例化后的bean填充属性,假设在发生了依赖循环的情况下,一个最普通bean属性的填充链路如下:

  1. applyPropertyValues
  2. resolveValueIfNecessary
  3. resolveReference
  4. applyPropertyValues——调用set方法注入属性

链路当中最核心的是第三步,resolveReference,这个方法里面实际上调用了beanFactory.getBean方法,那么这个方法就又回到了getBean这一步骤——即如果属性没有实例化那就会去实例化属性的bean,如果实例化了那么就会从三个级别的缓存获取。

bean = this.beanFactory.getBean(resolvedName);

执行逻辑

假设AB两个对象互相依赖,且A先实例化。

1、A实例化获取A的bean,此时缓存当中没有beanA,将A工厂放到三级缓存当中(提前曝光)。

2、A继续填充属性B,获取beanB,此时缓存当中没有beanB,单例模式且循环依赖的情况下,将B的工厂放到三级缓存当中(提前曝光)。

3、B继续填充属性A,获取beanA,此时可以直接从三级缓存当中通过A的工厂获取A的实例,并且会将beanA放到二级缓存,同时删除三级缓存当中的A工厂。B通过set填充A之后就属于完全实例化的bean,然后将B放到一级缓存,并删除三级缓存的B。

4、前面B放到一级缓存当中了,A填充属性B,此时直接从一级缓存当中获取,通过set注入,A属于完全实例化的bean,放到一级缓存当中,删除二级缓存。

PS:A实例化的过程中,经过了三级、二级、一级缓存的过程,而B实例化的时候没有经过二级缓存

http://www.jsqmd.com/news/521398/

相关文章:

  • DA14580烧录库深度解析:UART/JTAG模式与OTP安全编程
  • 【数据库】SQLite的基础使用
  • 【01】什么是机器学习?理论基础与技术要点
  • 从‘一视同仁’到‘慧眼识珠’:SE Block如何教会卷积神经网络关注重点通道
  • rl-agents项目实战:如何自定义你的强化学习环境与智能体配置文件?
  • lora-scripts参数调优指南:学习率、批次大小设置,避免过拟合
  • 【运维实践】【Ubuntu 22.04】从零配置:解锁Root账户并优化SSH安全登录
  • 玩过电源设计的都知道,Buck电路的双闭环控制就像炒菜放盐——调不好整锅都得翻车。今天咱们直接上干货,从数学建模到仿真验证,手把手把PI调节器的门道拆开了说
  • 用STM32F103+热敏打印头搭建标签打印机:字库存储、蓝牙控制与电源管理的完整实现
  • 如何快速优化暗影精灵笔记本性能:开源硬件控制工具终极指南
  • SEER‘S EYE预言家之眼效果对比:与传统规则引擎在推理游戏中的表现
  • 微信小程序逆向实战:从源码提取到动态调试全解析
  • 基于SpringAi 开发聊天机器人
  • Bark iOS推送通知工具:终极自定义推送解决方案
  • 避坑指南:HC32F460 Timer0异步计数那些容易忽略的细节(以K10按键停止计数为例)
  • 为 NativeScript 应用添加 GPS 功能
  • Asian Beauty Z-Image Turbo 保姆级部署:Ubuntu 20.04系统环境配置全攻略
  • 创建函数和调用函数
  • Realtek 8852CE网卡Linux驱动全攻略:从故障排查到性能优化
  • 杭电网安复试编程Day24
  • Qt6 QML自定义控件:从零到插件化的实战开发手册
  • 3分钟掌握WE Learn智能助手:让你的网课学习效率提升300%
  • MCP3208 12位SPI ADC驱动开发与嵌入式精度采集实战
  • 【Unity进阶】AudioSource 实战技巧与性能优化指南
  • 5V光耦隔离继电器模块硬件设计与RT-Thread驱动实现
  • 极简七段数码管驱动库:裸机嵌入式GPIO直写方案
  • 一文读懂-yolo26如何预测识别图片|视频|摄像头|文件夹检测适用v8v11
  • 35岁以后,我们这些老程序员们能去哪儿?
  • Phi-3-vision-128k-instruct 创意应用:辅助 Visio 图表设计与文档撰写
  • 如何通过Win11Debloat实现Windows系统深度优化:从性能提升到隐私保护的全流程指南