千家信息网

SpringBoot中@Async引起循环依赖的示例分析

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,这篇文章将为大家详细讲解有关SpringBoot中@Async引起循环依赖的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。事故时间线本着"先止损、后复盘分析
千家信息网最后更新 2026年11月03日SpringBoot中@Async引起循环依赖的示例分析

这篇文章将为大家详细讲解有关SpringBoot中@Async引起循环依赖的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

事故时间线

本着"先止损、后复盘分析"的原则,我们来看一下这次发版事故的时间线。

2021年11月16日晚23点00分00秒开始发版,此时集团的devops有点慢

2021年11月16日晚23点03分01秒,收到发版失败的消息,登录服务器发现发生了循环依赖,具体错误如下图,从日志中可以看到是dataCollectionSendMessageService这个bean出现了循环依赖

问题发现了就需要先解决,然后再去分析为什么。看到这个报错日志我心里也大概知道是为什么了,所以很快就解决了,解决方案如下:给DataCollectionSendMessageService加上@Lazy注解

2021年11月16日晚23点07分16秒,使用重新集成的代码开始发版,大概10分钟后线上节点全部发版完成。从时间线来看从发现问题到解决问题,前后一共用了接近15分钟(这期间代码集成和发布用了过多的时间),也算是做到了及时止损,没有让问题继续扩大。

猜想

我大胆的猜想是因为打了@Aysnc注解的bean生成了对象的代理,导致Spring bean最终加载的不是一个原始对象导致了此次问题的发生,那么对不对呢,接下来我们通过源码详细分析一下。

什么是循环依赖

所谓循环依赖就是Spring IOC容器在加载bean时会按照顺序加载,先去实例化 beanA。然后发现 beanA 依赖于 beanB,接在又去实例化 beanB。实例化 beanB 时,发现 beanB 又依赖于 beanA。如果容器不处理循环依赖的话,容器会无限执行上面的流程,直到内存溢出,程序崩溃,所以这个时候就会抛出BeanCurrentlyInCreationException异常,也就是我们常说的循环依赖,下面是两种常见循环依赖的场景。

几个Bean之间的循环依赖

@Componentpublic class A {    @Autowired    private B b;}@Componentpublic class B {    @Autowired    private C c;}@Componentpublic class C {    @Autowired    private A a;}

效果图如下:

自己依赖自己

@Componentpublic class A {    @Autowired    private A a;}

效果图如下:

Spring是如何解决循环依赖的

首先Spring维护了三个Map,也就是我们通常说的三级缓存

  • singletonObjects:俗称单例池,缓存创建完成的单例Bean

  • singletonFactories:映射创建Bean的原始工厂

  • earlySingletonObjects:映射Bean的早期引用,也就是说这个Map里的Bean不是完整的,只是完成了实例化,但还没有初始化

Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。

紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

简单一句话说:先去缓存里找Bean,没有则实例化当前的Bean放到Map,如果有需要依赖当前Bean的,就能从Map取到。

什么是@Async

@Async注解是Spring为我们提供的异步调用的注解,@Async可以作用到类或者方法上,标记了@Async注解的方法将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。从源码中可以看到标记了@Async注解的方法会被提交到org.springframework.core.task.TaskExecutor中异步执行。


或者我们可以通过value来指定使用哪个自定义线程池,比如这样子:

@Async("asyncTaskExecutor")

被@Async标记的bean注入时机

我们从源码的角度来看一下被@Async标记的bean是如何注入到Spring容器里的。在我们开启@EnableAsync注解之后代表可以向Spring容器中注入AsyncAnnotationBeanPostProcessor,它是一个后置处理器,我们看一下他的类图。

真正创建代理对象的代码在AbstractAdvisingBeanPostProcessor中的postProcessAfterInitialization方法中,以下代码有所删减,只保留核心逻辑代码

       // 这个map用来缓存所有被postProcessAfterInitialization这个方法处理的bean        private final Map, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);        // 这个方法主要是为打了@Async注解的bean生成代理对象        @Override        public Object postProcessAfterInitialization(Object bean, String beanName) {                // 这里是重点,这里返回true                if (isEligible(bean, beanName)) {                        // 工厂模式生成一个proxyFactory                        ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);                        if (!proxyFactory.isProxyTargetClass()) {                                evaluateProxyInterfaces(bean.getClass(), proxyFactory);                        }                        // 切入切面并创建一个代理对象                        proxyFactory.addAdvisor(this.advisor);                        customizeProxyFactory(proxyFactory);                        return proxyFactory.getProxy(getProxyClassLoader());                }                // No proxy needed.                return bean;        }
        protected boolean isEligible(Class targetClass) {                // 首次从eligibleBeans这个map中一定是拿不到的                Boolean eligible = this.eligibleBeans.get(targetClass);                if (eligible != null) {                        return eligible;                }                // 如果没有advisor,也就是切面,直接返回false                if (this.advisor == null) {                        return false;                }                // 这里判断AsyncAnnotationAdvisor能否切入,因为我们的bean是打了@Aysnc注解,这里是一定能切入的,最终会返回true                eligible = AopUtils.canApply(this.advisor, targetClass);                this.eligibleBeans.put(targetClass, eligible);                return eligible;        }

至此打了@Aysnc注解的bean就创建完成了,结果是生成了一个代理对象。

循环依赖到底是怎么生成的

经过上面的源码分析,我们可以知道有@Aysnc注解的bean最后生成了一个代理对象,我们结合Spring bean创建的流程来分析这次问题。

  • beanA开始初始化,beanA实例化完成后给beanA的依赖属性beanB进行赋值

  • beanB开始初始化,beanB实例化完成后给beanB的依赖属性beanA进行赋值

  • 因为beanA是支持循环依赖的,所以可以在earlySingletonObjects中可以拿到beanA的早期引用的,但是因为beanB打了@Aysnc注解并不能在earlySingletonObjects中可以拿到早期引用

  • 接下来执行执行initializeBean(Object existingBean, String beanName)方法,这里beanA可以正常实例化完成,但是因为beanB打了@Aysnc注解,所以向Spring IOC容器中增加了一个代理对象,也就是说beanA的beanB并不是一个原始对象,而是一个代理对象

  • 接下来进行执行doCreateBean方法时对进行检测,以下代码有所删减,只保留核心逻辑代码

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)                        throws BeanCreationException {                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 actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);                                        // 重点在这里,这里会遍历所有依赖的bean,如果beanA依赖beanB和缓存中的beanB不相等                                        // 也就是说beanA本来依赖的是一个原始对象beanB,但是这个时候发现beanB是一个代理对象,就会增加到actualDependentBeans                                        for (String dependentBean : dependentBeans) {                                                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {                                                        actualDependentBeans.add(dependentBean);                                                }                                        }                                        // 发现actualDependentBeans不为空,就发生了我们最开始截图的错误                                        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 " +                                                                "'getBeanNamesOfType' 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;        }

解决循环依赖的正确姿势

  • @Lazy注解

  • 代码优化,不要让@Async的Bean参与循环依赖

关于"SpringBoot中@Async引起循环依赖的示例分析"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

对象 循环 注解 缓存 代理 实例 方法 代码 工厂 分析 也就是 容器 问题 生成 原始 时间 标记 源码 处理 接下来 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 湖南永感互联网科技有限公司 厦门中拓互联网络科技有限公司 互联网科技有限公司图片 互联网科技论文 上海万想互联网科技 互联网金融监管科技 互联网公司会议桌创意科技软装饰 科技与互联网哪个更好 互联网科技教育视频 彤创互联网科技有限公司 呼和浩特新华互联网科技学院 东莞新华互联网科技学校 企晟互联网信息科技有限公司 产业互联网科技服务公司 工业互联网科技创业 宁波跨境商互联网科技有限公司 福建互联网展示科技馆设计 版纳互联网科技选哪家 互联网公司属于科技吗 广州三三互联网科技有限公司 互联网科技产业 江苏根荣互联网科技 互联网科技工作委员会副主任毛军 互联网和科技创新 互联网科技中介 坤平互联网科技 富国互联网科技002692 长沙易易互联网科技有限公司 互联网企业和网络科技有限公司 科技美学互联网 艺术
0