广告
返回顶部
首页 > 资讯 > 精选 >Spring处理@Async导致的循环依赖失败问题怎么解决
  • 855
分享到

Spring处理@Async导致的循环依赖失败问题怎么解决

2023-07-02 16:07:57 855人浏览 安东尼
摘要

本文小编为大家详细介绍“spring处理@Async导致的循环依赖失败问题怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring处理@Async导致的循环依赖失败问题怎么解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入

本文小编为大家详细介绍“spring处理@Async导致的循环依赖失败问题怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring处理@Async导致的循环依赖失败问题怎么解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    简介

    说明

    本文介绍SpringBoot中的@Async导致循环依赖失败的原因及其解决方案。

    概述

    我们知道,Spring解决了循环依赖问题,但Spring的异步(@Async)会使得循环依赖失败。本文将用实例来介绍其原因和解决方案。

    问题复现

    启动类

    启动类添加@EnableAsync以启用异步功能。

    package com.knife; import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync@SpringBootApplicationpublic class DemoApplication {     public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    } }

    Service

    A

    package com.knife.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component; @Componentpublic class A {    @Autowired    private B b;     @Async    public void print() {        System.out.println("Hello World");    }}

    B

    package com.knife.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component; @Componentpublic class B {    @Autowired    private A a;}

    Controller

    package com.knife.controller; import com.knife.service.A;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.WEB.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController; @RestControllerpublic class HelloController {    @Autowired    private A a;     @GetMapping("/test")    public String test() {        a.print();        return "test success";    }}

    启动:(报错)

    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] 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:624) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanReGIStry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        ... 20 common frames omitted

    原因分析

    @EnableAsync开启时向容器内注入AsyncAnnotationBeanPostProcessor,它是一个BeanPostProcessor,实现了postProcessAfterInitialization方法。创建代理的动作在抽象父类AbstractAdvisingBeanPostProcessor上:

        // 这个方法主要是为有@Async 注解的 bean 生成代理对象    @Override    public Object postProcessAfterInitialization(Object bean, String beanName) {        if (this.advisor == null || bean instanceof aopInfrastructureBean) {            // Ignore AOP infrastructure such as scoped proxies.            return bean;        }         // 如果此Bean已经被代理了(比如已经被事务那边给代理了~~)        if (bean instanceof Advised) {            Advised advised = (Advised) bean;                    if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {                // Add our local Advisor to the existing proxy's Advisor chain...                // beforeExistingAdvisors决定这该advisor最先执行还是最后执行                // 此处的advisor为:AsyncAnnotationAdvisor  它切入Class和Method标注有@Aysnc注解的地方~~~                if (this.beforeExistingAdvisors) {                    advised.addAdvisor(0, this.advisor);                } else {                    advised.addAdvisor(this.advisor);                }                return bean;            }        }        // 若不是代理对象,则进行处理        if (isEligible(bean, beanName)) {            //copy属性 proxyFactory.copyFrom(this); 工厂模式生成一个新的 ProxyFactory            ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);            // 如果没有采用CGLIB,就去探测它的接口            if (!proxyFactory.isProxyTargetClass()) {                evaluateProxyInterfaces(bean.getClass(), proxyFactory);            }            // 切入切面并创建一个getProxy 代理对象            proxyFactory.addAdvisor(this.advisor);            customizeProxyFactory(proxyFactory);            return proxyFactory.getProxy(getProxyClassLoader());        }         // No proxy needed.        return bean;    }     protected boolean isEligible(Object bean, String beanName) {        return isEligible(bean.getClass());    }     protected boolean isEligible(Class<?> targetClass) {        //首次从 eligibleBeans 这个 map 中获取值肯定为 null        Boolean eligible = this.eligibleBeans.get(targetClass);        if (eligible != null) {            return eligible;        }        //如果没有配置 advisor(即:切面),返回 false        if (this.advisor == null) {            return false;        }                // 若类或方法有 @Aysnc 注解,AopUtils.canApply 会判断为 true        eligible = AopUtils.canApply(this.advisor, targetClass);        this.eligibleBeans.put(targetClass, eligible);        return eligible;    }
    1. 创建A,A实例化完成后将自己放入第三级缓存,然后给A的依赖属性b赋值

    2. 创建B,B实例化后给B的依赖属性a赋值

    3. 从第三级缓存中获得A(执行A的getEarlyBeanReference方法)。执行getEarlyBeanReference()时@Async根本还被扫描,所以返回的是原始类型地址(没被代理的对象地址)。

    4. B完成初始化、属性的赋值,此时持有A原始类型引用(没被代理)

    5. 完成A的属性的赋值(此时持有B的引用),继续执行初始化方法initializeBean(...),解析@Aysnc注解,生成一个代理对象,exposedObject是一个代理对象(而非原始对象),加入到容器里。

    6. 问题出现了:B的属性A是个原始对象,而此处的实例A却是个代理对象。(即:B里的A不是最终对象(不是最终放进容器的对象))

    7. 执行自检程序:由于allowRawInjectionDespiteWrapping默认值是false,表示不允许上面不一致的情况发生,就报错了

    解决方案

    有三种方案:

    懒加载:使用@Lazy或者@ComponentScan(lazyInit = true)

    不要让@Async的Bean参与循环依赖

    将allowRawInjectionDespiteWrapping设置为true

    方案1:懒加载

    说明

    建议使用@Lazy。

    不建议使用@ComponentScan(lazyInit = true),因为它是全局的,容易产生误伤。

    实例

    这两个方法都是可以的:

    • 法1. 将@Lazy放到A类的b成员上边

    • 法2: 将@Lazy放到B类的a成员上边

    法1:将@Lazy放到A类的b成员上边

    package com.knife.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Lazy;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component; @Componentpublic class A {    @Lazy    @Autowired    private B b;     @Async    public void print() {        System.out.println("Hello World");    }}

    法2:将@Lazy放到B类的a成员上边

    package com.knife.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Lazy;import org.springframework.stereotype.Component; @Componentpublic class B {    @Lazy    @Autowired    private A a;}

    这样启动就能成功。

    原理分析

    以这种写法为例进行分析:@Lazy放到A类的b成员上边。

    即:

    package com.knife.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Lazy;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component; @Componentpublic class A {    @Lazy    @Autowired    private B b;     @Async    public void print() {        System.out.println("Hello World");    }}

    假设 A 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是一个被 @Lazy 标记过的属性。那么就不会去直接加载 B,而是产生一个代理对象注入到了 A 中,这样 A 就能正常的初始化完成放入一级缓存了。

    B 加载时,将前边生成的B代理对象取出,再注入 A 就能直接从一级缓存中获取到 A,这样 B 也能正常初始化完成了。所以,循环依赖的问题就解决了。

    方案2:不让@Async的类有循环依赖

    略。

    方案3:allowRawInjectionDespiteWrapping设置为true

    说明

    本方法不建议使用。

    这样配置后,容器启动不报错了。但是:Bean A的@Aysnc方法不起作用了。因为Bean B里面依赖的a是个原始对象,所以它不能执行异步操作(即使容器内的a是个代理对象)。

    方法

    import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;import org.springframework.stereotype.Component; @Componentpublic class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);    }}

    为什么@Transactional不会导致失败

    概述

    同为创建动态代理对象,同为一个注解标注在类/方法上,为何@Transactional就不会出现这种启动报错呢?

    原因是,它们代理的创建的方式不同:

    @Transactional创建代理的方式:使用自动代理创建器InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator的子类),它实现了getEarlyBeanReference()方法从而很好的对循环依赖提供了支持

    @Async创建代理的方式:使用AsyncAnnotationBeanPostProcessor单独的后置处理器。它只在一处postProcessAfterInitialization()实现了对代理对象的创建,因此若它被循环依赖了,就会报错

    详解

    Spring处理@Async导致的循环依赖失败问题怎么解决

    处理@Transactional注解的是InfrastructureAdvisorAutoProxyCreator,它是SmartInstantiationAwareBeanPostProcessor的子类。AbstractAutoProxyCreator对SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法进行了覆写:

    AbstractAutoProxyCreator# getEarlyBeanReference

    public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {            // 其他代码        @Override    public Object getEarlyBeanReference(Object bean, String beanName) {        Object cacheKey = getCacheKey(bean.getClass(), beanName);        this.earlyProxyReferences.put(cacheKey, bean);        return wrapifNecessary(bean, beanName, cacheKey);    }    }

    AbstractAutoProxyCreator#postProcessAfterInitialization方法中,判断是否代理过,是的话,直接返回:

    public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {            // 其他代码        @Override    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;    }    }

    读到这里,这篇“Spring处理@Async导致的循环依赖失败问题怎么解决”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

    --结束END--

    本文标题: Spring处理@Async导致的循环依赖失败问题怎么解决

    本文链接: https://www.lsjlt.com/news/343095.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • Spring处理@Async导致的循环依赖失败问题怎么解决
      本文小编为大家详细介绍“Spring处理@Async导致的循环依赖失败问题怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring处理@Async导致的循环依赖失败问题怎么解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入...
      99+
      2023-07-02
    • Spring处理@Async导致的循环依赖失败问题的方案详解
      目录简介问题复现原因分析解决方案方案1:懒加载方案2:不让@Async的类有循环依赖方案3:allowRawInjectionDespiteWrapping设置为true为什么@Tr...
      99+
      2022-11-13
    • 怎么解决Spring循环依赖问题
      本篇内容介绍了“怎么解决Spring循环依赖问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言循环依赖...
      99+
      2022-10-19
    • Spring循环依赖问题怎么解决
      在Spring中,循环依赖问题是指两个或多个bean之间出现相互依赖的情况。由于Spring容器默认使用单例模式管理bean,因此循...
      99+
      2023-08-31
      Spring
    • Spring中怎么处理循环依赖问题
      Spring中怎么处理循环依赖问题,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。什么是循环依赖依赖指的是Bean与Bean之间的依赖关系,循环依赖指的是两个或者...
      99+
      2023-06-20
    • 怎么在spring中解决循环依赖问题
      怎么在spring中解决循环依赖问题?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。setter singleton循环依赖使用SingleSetterBeanA依赖Sing...
      99+
      2023-06-08
    • 怎么使用Spring三级缓存解决循环依赖问题
      这篇文章主要介绍了怎么使用Spring三级缓存解决循环依赖问题的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用Spring三级缓存解决循环依赖问题文章都会有所收获,下面我们一起来看看吧。循环依赖什么是循环...
      99+
      2023-07-05
    • spring bean的自动注入及循环依赖问题怎么解决
      这篇文章主要介绍了spring bean的自动注入及循环依赖问题怎么解决的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇spring bean的自动注入及循环依赖问题怎么解决文章都会有所收获,下面我们一起来看看吧...
      99+
      2023-07-05
    • Spring解决循环依赖问题及三级缓存的作用是什么
      本文小编为大家详细介绍“Spring解决循环依赖问题及三级缓存的作用是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring解决循环依赖问题及三级缓存的作用是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知...
      99+
      2023-07-02
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作