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

spring 第三级缓存singletonFactories的作用及@Async造成循环依赖报错原因分析

目录

  • 背景
  • 一、spring中三级缓存作用
    • 1、第一级缓存 singletonObjects:
    • 2、第二级缓存 earlySingletonObjects:
    • 3、第三级缓存 singletonFactories:
    • 4、使用场景及代码解析
      • 4.1、没有AOP的普通循环依赖(A → B → A)
        • 4.1.1 代码示例
        • 4.1.2 三级缓存使用情况
      • 4.2、有AOP代理的循环依赖(比如 A有@Transctional方法, A → B → A)
  • 二、@Transctional和@Async创建代理的区别
    • 1、AsyncAnnotationBeanPostProcessor:负责处理 @Async。
    • 2、InfrastructureAdvisorAutoProxyCreator:负责处理 @Transactional(以及其他Spring基础设施AOP,如@Retry)
      • 1、第一个是从第三级缓存执行getObject时,执行getEarlyBeanReference,进而执行wrapIfNecessary去创建代理对象。
      • 2、第二个是和@Async一样在初始化(Initialization)方法中调用后置处理器BeanPostProcessor.postProcessAfterInitialization,进而执行wrapIfNecessary创建代理对象。这里需要注意如果在获取缓存的时候已经创建过代理对象后,会缓存起来,在这个地方会先获取缓存getCacheKey,如果缓存中有,不会重复创建代理对象
  • 三、@Async造成循环依赖报错代码位置
  • 四、解决方法
    • 1、@lazy注解
    • 2、修改代码结构
    • 3、使用@DependsOn

背景

回顾前面所学知识时,发现spring中的第三级缓存singletonFactories是为了解决代理对象循环依赖的,但是使用@Transctional注解生成的代理对象有循环依赖时第三级缓存生效了,启动项目没有报循环依赖的错误,使用@Async也会创建代理对象,同样有循环依赖时,第三级缓存失效了,启动报循环依赖的错误。所以产生了疑问使用@Transctional和@Async注解都可以生成代理类,但是spring为什么能解决@Transctional的循环依赖,@Async的解决不了呢,带着疑问进行了如下探索。

一、spring中三级缓存作用

//一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//三级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

Spring容器内部维护了三个Map,也就是我们常说的“三级缓存”:

1、第一级缓存 singletonObjects:

存储内容:完全初始化好的单例Bean。

特点:这是最成熟的缓存。从这里面取出的Bean立即可用。

2、第二级缓存 earlySingletonObjects:

存储内容:提前暴露的、尚未完成初始化的Bean(早期引用)。

特点:用于解决普通的循环依赖(没有AOP的情况)。

3、第三级缓存 singletonFactories:

存储内容:单例对象工厂(ObjectFactory)的缓存是一个函数式接口。在代理对象循环依赖的时候,让创建代理对象逻辑提前执行,提前暴露代理对象到缓存。

特点:这是最关键的一级缓存。它不直接存储Bean实例,而是存储一个能生产Bean的“工厂”。这个工厂的核心逻辑是调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()方法。

4、使用场景及代码解析

4.1、没有AOP的普通循环依赖(A → B → A)

4.1.1 代码示例

创建A类,包含B属性。

@Component
public class A {@AutowiredB b;
}

创建B类,包含A属性

@Component
public class B {@AutowiredA a;
}
4.1.2 三级缓存使用情况

Bean的创建流程不再赘述,现在只看几个关键点。
1、创建A的时候,放入三级缓存的时机。如下图,箭头指向了调用栈,在getSingleton方法中执行了beforeSingletonCreation(beanName);

在这里插入图片描述
beforeSingletonCreation方法中,把beanName放入了Set集合singletonsCurrentlyInCreation中。
在这里插入图片描述
doCreateBean方法中isSingletonCurrentlyInCreation是判断集合Set中是否存在当前类,allowCircularReferences默认是true,所以所有条件都满足进入addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
在这里插入图片描述
在这里插入图片描述
从addSingletonFactory可以看到把当前类的beanName为key,匿名内部类为value放入到第三级缓存中singletonFactories。

	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);}}}

继续进入populateBean方法,给A类中属性B赋值,进入B类的创建流程,B也会执行上面介绍的步骤,在B创建完实例,也进入populateBean进行属性A赋值时,会调用A的创建流程,首先先从三级缓存中获取。
在这里插入图片描述
在执行到doGetBean方法时,会执行getSingleton
在这里插入图片描述
下面是获取缓存的详细代码,可以看到如果singletonObjects(一级缓存)有值,if条件都不满足会直接返回。如果没值会继续获取earlySingletonObjects(二级缓存),如果二级缓存有值,也是直接返回不走if里面。如果没值会继续获取singletonFactories(三级缓存),调用 singletonFactory.getObject()。调用完getObject方法后,会把返回的值赋值给二级缓存,三级缓存清理掉当前beanName。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject 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 locksingletonObject = 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;}

下面看一下 singletonFactory.getObject()方法。先回顾一下第三级缓存添加的内容addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));会执行getEarlyBeanReference方法。从下面getEarlyBeanReference方法内容可以看到循环遍历SmartInstantiationAwareBeanPostProcessor 类型的组件,执行getEarlyBeanReference方法。因为A和B类都没有被AOP代理所以会返回A的原始对象。最后给B中的A属性赋值此时的A是一个还没有被属性赋值完成的半成品。B被创建完成后,A的属性赋值完成,A也会被创建完成。

	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;}

getSingleton->addSingleton(beanName, singletonObject);把创建完成的A类方入到一级缓存,清理掉二级和三级缓存。B类也是一样的流程。因为A和B 都没有代理所以如果只有一级和二级缓存两个缓存也能解决循环依赖的问题。当有代理类的时候就需要三级缓存了。

	protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}

4.2、有AOP代理的循环依赖(比如 A有@Transctional方法, A → B → A)

@Component
@Transcational
public class A {@AutowiredB b;}
@Component
public class B {@AutowiredA a;
}

假设Bean A被AOP增强(例如 @Transactional等),需要被代理。

1、开始创建A(目标对象):Spring实例化A的原始对象(不是代理对象),然后将A的对象工厂放入第三级缓存。这个工厂的核心逻辑是:如果需要代理,则调用getEarlyBeanReference()返回代理对象;如果不需要,则返回原始对象。

2、为A注入属性:Spring发现A依赖B,去获取B。

3、开始创建B:Spring实例化B,同样将B的对象工厂放入第三级缓存。

4、为B注入属性:Spring发现B依赖A,去获取A。

5、关键步骤:获取A的早期引用:

Spring在第三级缓存中找到A的ObjectFactory。

调用getObject()方法。因为A需要被AOP代理,这个方法不会返回原始的A对象,而是会返回一个A的代理对象(早期代理)。

将这个早期代理对象放入第二级缓存。

6、B完成创建:B注入的是A的代理对象。B创建完成,放入一级缓存。

7、A完成创建:A继续完成后续的生命周期步骤。当AOP发生时,Spring会发现A已经有一个早期代理暴露在外了(在第二级缓存中),为了保证唯一性,Spring会直接返回之前创建的那个早期代理,而不是再创建一个新的代理。最终,这个完整的代理对象A被放入第一级缓存。

二、@Transctional和@Async创建代理的区别

Spring通过BeanPostProcessor来创建代理。处理@Async和@Transactional的关键处理器分别是:

1、AsyncAnnotationBeanPostProcessor:负责处理 @Async。

AsyncAnnotationBeanPostProcessor后置处理器的调用时机是在Bean的初始化方法中,调用BeanPostProcessor.postProcessAfterInitialization。
Bean的创建过程:①实例化(Instantiation)②、属性填充(Population)③、初始化(Initialization)
BeanPostProcessor.postProcessBeforeInitialization
@PostConstruct 方法
InitializingBean.afterPropertiesSet
BeanPostProcessor.postProcessAfterInitialization <-- @Async代理在此创建!
所以说当发生循环依赖时,A中的第三级缓存中调用getObject返回的还是A的原始实例不是代理后的实例,赋值给了B中的A属性,但是A属性赋值完成后,最终被代理成了代理类,导致A有两中类型,一个是B中的属性原始实例,一个是A代理类,spring最后检查时发现这种情况会报循环依赖错误。

2、InfrastructureAdvisorAutoProxyCreator:负责处理 @Transactional(以及其他Spring基础设施AOP,如@Retry)

InfrastructureAdvisorAutoProxyCreator的父类AbstractAutoProxyCreator。会在两个地方调用。

1、第一个是从第三级缓存执行getObject时,执行getEarlyBeanReference,进而执行wrapIfNecessary去创建代理对象。

	@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);}

2、第二个是和@Async一样在初始化(Initialization)方法中调用后置处理器BeanPostProcessor.postProcessAfterInitialization,进而执行wrapIfNecessary创建代理对象。这里需要注意如果在获取缓存的时候已经创建过代理对象后,会缓存起来,在这个地方会先获取缓存getCacheKey,如果缓存中有,不会重复创建代理对象

	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}

这就解释了,@Transactional注解的对象会从获取缓存的阶段创建代理,赋值给B对象代理后的类,避免了原始实例和代理实例不一致的问题。

三、@Async造成循环依赖报错代码位置

@Component
public class A {@AutowiredB b;@Asyncpublic void doAMethod(){System.out.println("doAMethod");}
}
@Component
public class B {@AutowiredA a;
}

如下图,exposedObject是被代理后的类型。从缓存中取出来earlySingletonReference是原始类型。635行代码判断不相等进入else,
else中获取所有拥有原始类型属性的实例,这里获取到B放入actualDependentBeans,actualDependentBeans不为空则报循环依赖的错误。在这里插入图片描述

四、解决方法

1、@lazy注解

A创建过程中,属性赋值时,发现B上有@Lazy注解会直接返回一个代理对象,没有直接创建B,当使用B的时候(调用B中方法),代理对象B才去真正的创建B,这个时候A已经创建完成了,可以直接从一级缓存中获取A的代理对象,所以解决了循环依赖报错的问题。

@Component
public class A {@Autowired@LazyB b;@Asyncpublic void doAMethod(){System.out.println("doAMethod");}
}
@Component
public class B {@AutowiredA a;
}

2、修改代码结构

@Service
public class AsyncTaskService {@Asyncpublic void executeAsyncTask() {// 执行异步任务}
}// 原来的A Service不再使用@Async,而是注入AsyncTaskService
@Service
public class A {@Autowiredprivate AsyncTaskService asyncTaskService;// ... 其他代码
}

3、使用@DependsOn

让B比A优先创建,B先放入第三级缓存原始类型,再去属性赋值创建A,A创建的时候虽然也会把原始类型放入第三级缓存,@Async造成循环依赖报错代码位置,在获取依赖A属性的时候会是空,就是没有A的原始类型被引用,actualDependentBeans数组是空,不会抛出循环依赖的异常。

@Component
@DependsOn("b")
public class A {@AutowiredB b;@Asyncpublic void doAMethod(){System.out.println("doAMethod");}
}
@Component
public class B {@AutowiredA a;
}
http://www.dtcms.com/a/411179.html

相关文章:

  • 什么是静态IP?静态IP和动态IP的对比
  • IP子网掩码的计算
  • 济南富新网站建设福州服务类网站建设
  • 网站设置快捷方式到桌面找大学生做家教的网站
  • 手机提词器APP对比测评
  • 【不背八股】18.GPT1:GPT系列的初代目
  • 体系化能力
  • 小谈:AR/VR(增强/虚拟现实)技术
  • 服务器建网站seo外链推广平台
  • Android studio图像视图和相对布局知识点
  • 网站备案主体空壳上不了国外网站 怎么做贸易
  • 适合设计制作公司的网站asp远吗宁波网站建设培训学校
  • 【AI论文】Qwen3-Omni技术报告
  • 门业网站 源码杭州亚运会闭幕式
  • 中裕隆建设有限公司网站考研资料找微信hyhyk1推广可以
  • LeetCode 3132.找出与数组相加的整数 II
  • 手机网站设计推荐微信电影网站建设教程
  • 【AI八股文】03-监督学习方案面试篇
  • 机器学习第十六章 基于RNN和注意力机制的自然语言处理
  • 上海建设摩托车官方网站中国宣布入境最新消息2023
  • 商城系统网站建设开发赤水市建设局官方网站
  • 韶关微网站建设阜阳企业做网站
  • 加查网站建设seo网络优化是什么工作
  • 海丰县建设局官方网站自己网站
  • 区块链可投会议CCF B--SIGMETRICS 2026 截止10.14 附录用率
  • 自由贸易试验区网站建设方案网站建设工程师的职位要求
  • 【Coze】【视频】卡通风格历史故事工作流
  • 用dw 网站开发与设计报告苏州园科生态建设集团网站
  • 网站开发运营工作总结网站编程培训
  • opendds初入门之对其支持的tools进行梳理