spring三级缓存以及@Async产生循环引用

  • spring三级缓存介绍
    • 三级缓存解除循环引用原理
    • 源码对应
      • 1、获取A,从三级缓存中获取,没有获取到
      • 2、构造A,将A置入三级缓存
        • 构造A(创建A实例)
        • 置入缓存
      • 3、注入属性,构造B
        • 扫描缓存实例的相关信息
        • 注入属性
      • 4、获取B,从三级缓存中没有获取到
      • 5、构造B,将B置入三级缓存
      • 6、注入属性,构造A
      • 7、获取A,三个缓存中三级缓存命中
      • 8、创建B实例后续步骤
      • 9、B构造完成,实例放入一级缓存,二级三级缓存移除
      • 10、创建A实例后续步骤
      • 11、A构造完成,实例放入一级缓存,二级三级缓存移除
  • @Async导致三级缓存没有解除循环引用
    • 复现
      • 测试类
      • 描述
      • 备注
      • 启动测试
    • 原因分析
    • 解决方法
    • 参考

spring三级缓存介绍

三级缓存解除循环引用原理

A => B => A
一级缓存 singletonObjects
二级缓存 earlySingletonObjects
三级缓存 singletonFactories

1、获取A,从三级缓存中获取,没有获取到
2、构造A,将A置入三级缓存
3、注入属性,构造B
4、获取B,从三级缓存中没有获取到
5、构造B,将B置入三级缓存
6、注入属性,构造A
7、获取A,从三级缓存中获取,一级没有、二级没有、三级存在。此时通过从三级的BeanFactory构造实例对象,放入二级缓存,移除三级缓存
8、创建B实例后续步骤
9、B构造完成,实例放入一级缓存,二级三级缓存移除
10、创建A实例后续步骤
11、A构造完成,实例放入一级缓存,二级三级缓存移除

源码对应

springboot 2.5.6

1、获取A,从三级缓存中获取,没有获取到

// AbstractBeanFactory#doGetBean   256行
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {String beanName = transformedBeanName(name);
Object beanInstance;// 从三个缓存中依次获取,注意方法点进去allowEarlyReference是true
Object sharedInstance = getSingleton(beanName);...

2、构造A,将A置入三级缓存

构造A(创建A实例)

// AbstractAutowireCapableBeanFactory#doCreateBean   582行
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {// 这里会通过构造函数创建Bean实例instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//省略代码...

置入缓存

// AbstractAutowireCapableBeanFactory#doCreateBean  613行
//省略代码...
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");}// 这里会把生成beanName的BeanFactory置入三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//省略代码...

3、注入属性,构造B

扫描缓存实例的相关信息

InitDestroyAnnotationBeanPostProcessor会解析缓存类中@PostStruct@PreDestroy等相关信息
CommonAnnotationBeanPostProcessor会解析缓存类中@Recource@WebServiceRef@PostStruct@PreDestroy等相关信息
AutowiredAnnotationBeanPostProcessor会解析缓存类中@Autowired@Value@Inject等相关信息

// AbstractAutowireCapableBeanFactory#doCreateBean 594行
//省略代码...
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;}
}
//省略代码...

注入属性

// AbstractAutowireCapableBeanFactory#doCreateBean 619行
//省略代码...
try {// 这里会将注入对应的属性populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//省略代码...// AbstractAutowireCapableBeanFactory#populateBean 1431行
//省略代码...
// 这里会依次遍历 如果是@Resource会在CommonAnnotationBeanPostProcessor中触发注入
// 如果是@Autowired则会在AutowiredAnnotationBeanPostProcessor中触发注入
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;
}
//省略代码...

4、获取B,从三级缓存中没有获取到

5、构造B,将B置入三级缓存

6、注入属性,构造A

上面三个其实类似,同前面三步骤差不多

7、获取A,三个缓存中三级缓存命中

 // AbstractBeanFactory#doGetBean   256行  入口
// DefaultSingletonBeanRegistry#getSingleton  150
// 此时,一级缓存和二级缓存里面都没有,三级缓存在第二步里面放进去了,此时能拿到对应的ObjectFactory,
//通过它可以获取对应的实例。然后将实例放入二级缓存,将三级缓存中对应的BeanName移除
protected Object getSingleton(String beanName, boolean allowEarlyReference) {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 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;
}

8、创建B实例后续步骤

@Async循环引用触发点

这里属性注入完后,剩下bean增强、循环依赖校验、注册DisposableBean

9、B构造完成,实例放入一级缓存,二级三级缓存移除

// DefaultSingletonBeanRegistry#getSingleton  259行
//省略代码...
// 这在执行链路的上面几层。这里判断是否是新的单例,如果是则置入缓存(一级缓存)中
if (newSingleton) {addSingleton(beanName, singletonObject);
}
//省略代码...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);}
}

10、创建A实例后续步骤

11、A构造完成,实例放入一级缓存,二级三级缓存移除

逻辑基本同上

@Async导致三级缓存没有解除循环引用

复现

测试类

@Service
@Slf4j
public class AService {static {log.info("AService init===>");}@Resourceprivate BService bService;public String a2bService(){return bService.get();}@Async("async-2")public void exec(){log.info("aService exec");}}
@Service
@Slf4j
public class BService {static {log.info("BService init===>");}@Autowiredprivate AService aService;public String get(){return "BService 666";}public void exec(){log.info("BService exec");}}

描述

这里AServiceBService在同一个目录下,也没有被在别的类中注入(如果有一个类把两者都注入了,且Bservice先注入,此时如果三个类中该类最先注入,就会导致Bservice先初始化)。在这种情况下,默认就会Aservice先初始化,然后在Bservice初始化,如此,才能复现问题。
如果想要调整类加载顺序,可以通过@DependsOn(value = "AService")或者@Lazy

备注

别开启懒加载,不然启动不会报错。
spring.main.lazy-initialization=true

启动测试

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] 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.at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:649)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434)at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)at com.yichen.casetest.CaseTestApplication.main(CaseTestApplication.java:47)

原因分析

在bean实例后续步骤中,会对bean增强

// AbstractAutowireCapableBeanFactory#doCreateBean  620行
try {populateBean(beanName, mbd, instanceWrapper);// 初始化bean,对Bean增强// 1、invokeAwareMethods// 2、applyBeanPostProcessorsBeforeInitialization// 3、invokeInitMethods// 4、applyBeanPostProcessorsAfterInitializationexposedObject = initializeBean(beanName, exposedObject, mbd);
}

applyBeanPostProcessorsAfterInitialization中,由于使用了异步线程池(用了@EnableAsync),使得AsyncAnnotationBeanPostProcessor注入了spring容器,它会为原有的bean实例创建CGLIB代理,使得最初的bean和实例化后暴露出去的bean不是同一个,没有通过循环引用校验,抛出了异常。

// AsyncAnnotationBeanPostProcessor的子类
//AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization  86行
// 如果类中有@Async会创建代理
if (isEligible(bean, beanName)) {ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);if (!proxyFactory.isProxyTargetClass()) {evaluateProxyInterfaces(bean.getClass(), proxyFactory);}proxyFactory.addAdvisor(this.advisor);customizeProxyFactory(proxyFactory);// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}return proxyFactory.getProxy(classLoader);
}
// AbstractAutowireCapableBeanFactory#doCreateBean  632行
// 循环引用检测
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.");}}}
}

解决方法

1、不用异步线程池,自己实现线程池
2、通过@lazy@DependsOn,调整类加载顺序,如果让Bservice先加载就不会出错
3、梳理业务逻辑,调整技术实现,让AServiceBservice不循环引用

参考

Using @Async in a circular dependency will throw a BeanCurrentlyInCreationException

spring三级缓存以及@Async产生循环引用相关推荐

  1. Spring三级缓存解决循环依赖

    1. 前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对方,最终形成闭环.用一副经典的图示可以表示成这样(A.B.C都代表对象,虚线代表引用关系): 其实可以N=1,也就 ...

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

    spring三级缓存解决循环依赖问题详解 前言 这段时间阅读了spring IOC部分的源码.在学习过程中,自己有遇到过很多很问题,在上网查阅资料的时候,发现很难找到一份比较全面的解答.现在自己刚学习 ...

  3. Spring——三级缓存解决循环依赖详解

    三级缓存解决循环依赖详解 一.什么是三级缓存 二.三级缓存详解 Bean实例化前 属性赋值/注入前 初始化后 总结 三.怎么解决的循环依赖 四.不用三级缓存不行吗 五.总结 一.什么是三级缓存 就是在 ...

  4. 什么是Spring三级缓存 对象在三级缓存中的创建流程 【三级缓存 循环依赖】

    一.什么是Spring三级缓存 第一级缓存:也叫单例池,存放已经经历了完整生命周期的Bean对象. 第二级缓存:存放早期暴露出来的Bean对象,实例化以后,就把对象放到这个Map中.(Bean可能只经 ...

  5. Spring三级缓存详解

    Spring三级缓存是为了解决对象间的循环依赖问题. A依赖B,B依赖A,这就是一个简单的循环依赖. 我们来先看看三级缓存的源码. (1)查看"获取Bean"的源码,注意getSi ...

  6. Spring三级缓存源码

    文章目录 Spring三级缓存类源码分析 Spring三级缓存类源码分析 public class DefaultSingletonBeanRegistry extends SimpleAliasRe ...

  7. spring 三级缓存_通过画图+视频把循环依赖、监听器等等spring源码讲明白了

    大家在阅读源码的时候有没有这种感觉:每次要看源码的时候十分信誓旦旦逼迫自己努力看着源码,但是还没看多长时间就会感觉枯燥,无味没意思,所以我是十分不愿意去看源码,但是今天福利来了,有位大神通过画图+视频 ...

  8. 面试官:连Spring三级缓存都答不好,自己走还是我送你?

    面试官:简历上写了精通Spring,那你回答一下Spring为什么用"三级缓存"去解决循环依赖? 我:.......应该有三个缓存的map结构 面试官:具体回答一下 我:平时没认真 ...

  9. 想不到,三级缓存是这样解决循环依赖的……

    前两天晚上,正当我加班沉浸在敲代码的快乐中时,听到前桌的同事在嘀咕:Spring究竟是如何解决的循环依赖? 这让我想起最开始学Java的时候,掌握了一点基本语法和面向对象的一点皮毛.当时心里也是各种想 ...

最新文章

  1. tensorflow youtube的一点笔记
  2. mysql+esc,mysql(3):基础,常用命令句使用(2)--上集
  3. Java代码优化(长期更新)
  4. 互联网1分钟 |1205
  5. Oracle入门(十四.18)之使用动态SQL
  6. 一步步编写操作系统 62 函数调用约定
  7. android 收获地址管理,android UiAutomator添加收货地址的用例
  8. CSS学习笔记(更新中...)
  9. UE4手册中文翻译速查表
  10. Nginx详解+身份验证+虚拟主机+反向代理负载均衡
  11. centos8 yum安装mysql8 流程配置
  12. 26. The Greenhouse Effect and Its Consequences 温室效应及其后果
  13. 56)函数模板的基本语法
  14. 各个版本的sqlite-jdbc jar下载链接
  15. 打印机扫描显示服务器没有响应,打印机扫描一体机能够打印却不能扫描,提示缺少WIA的驱动程序...
  16. pytorch_预训练Se_resnet50_自定义类别数量_源码分享
  17. css中zoom的用法
  18. 开发基于 Google Map 的 Android 应用
  19. 斐波那契数列求和公式
  20. 告别CMD.windows终端神器conemu设置

热门文章

  1. .NetCore——日志系统
  2. 物联网开发之4G通讯模块
  3. 电脑文件管理,教你在众多文件名中插入相同的日期
  4. Python之psutil库的使用
  5. 必备常识:新手必备WP8手机使用技巧
  6. 迷你单片机示波器!51单片机和STC15F104W适用~
  7. AD936x 配置软件介绍 上(AD936x Evaluation Software)
  8. Intellij IDEA激活服务器
  9. github删除仓库(repository )
  10. 负能量有时候比正能量更加治愈