采取@Async异步注解导致该Bean在轮回依赖时启动报BeanCurrentlyInCreationException异常的直接原因分析,以及提供解決方案【享学Spring】

原创
小哥 3年前 (2022-11-07) 阅读数 7 #大杂烩

每篇一句

面试造飞机,工作螺丝钉。在工作中,您只需要知道如何使用这些调用命令,但您需要了解它们背后的逻辑。

前言

今天在他们自己的工程中使用 @Async 当时,我遇到了一个问题: Spring循环依赖性(circular reference)问题
也许说到这里,我的一些朋友会感到震惊。 Spring你没有解决循环依赖的问题吗?它支持循环依赖?怎样

不可否认,在这之前我是如此的确信,而且我每次都使用它。 屡试不爽 如果你有像我现在一样强烈的想法, 那么我相信这篇文章可以让你收到很多货物。~~

不得不提,关于 @Async 有关姿势的使用,请参见:
【小家Spring】Spring异步处理@Async使用和原理,源代码分析(@EnableAsync)
关于Spring Bean有关循环依赖关系问题,请参见:
【小家Spring我会在一篇文章中告诉你。Spring是如何使用"三级缓存"巧妙解决Bean关于循环依赖问题

我通过实验得出结论 @Async 导致循环依赖问题 必要 条件:

  1. 已开启 @EnableAsync 的支持
  2. @Async 注释所在位置Bean周期性依赖于

背景

如果你是一个有经验的程序员,你一定在开发中遇到过这种现象: 交易不生效

关于交易不生效方面的原因,可参考: 【小家java】Spring交易不生效的原因大解读

本文中场景的背景是相同的,我想调用这个类的异步方法(标记为。 @Async 注意),显然我知道为了让 @Async 有效地,我投入了自己,然后通过了。service接口调用,代码如下:

@Service
public class HelloServiceImpl implements HelloService {
    @Autowired
    private HelloService helloService;

    @Override
    public Object hello(Integer id) {
        System.out.println("线程名称:" + Thread.currentThread().getName());
        helloService.fun1(); // 改为使用接口方式调用this
        return "service hello";
    }

    @Async
    @Override
    public void fun1() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

这种方法首先是Spring典型的循环依赖场景如下: 你靠自己 。本以为能够像解决交易不生效问题一样依旧 屡试不爽 然而,我没想到会很丢脸,在开始后立即报告错误:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name helloServiceImpl: Bean with name helloServiceImpl has been injected into other beans [helloServiceImpl] 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.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    ...

这里,为什么一个小伙伴告诉我:我用它 @Async 即使这类方法调用从未遇到过此错误?这不是很常见吗?
为此,经过我的调查,包括查看一些同事和小伙伴的代码,我发现 :不使用 @Async 他没有开始报告错误,而是在调用该类时直接调用该方法,因此 @Async 不是有效的,而是小伙伴 全然不知 而已。

至于 @Async 为什么这个问题没有生效???即使过了很长一段时间,也没有人发现并关注??
事实上,原因很简单, 它和交易不生效 不一样, @Async 若没生效99%在这种情况下,它不会影响业务的正常运行,因为它不会影响数据的正确性,只会影响性能(无非是异步同步,这是兼容的)。
但我期望的是 作为技术人员 ,或者可以有 技术敏感性 。可以快速帮助您自己或您周围的同事找到这个问题,这可能是您的宝贵资本~


我们知道 交易不生效@Async 无效的根本原因是相同的:直接调用该类的方法。 接口方法/代理对象方法
解决这类 不生效 我们通常有两种解决方案:

  1. 给自己注射 ,然后调用接口方法(当然,这里的一个变体是使用编程形式,例如: AInterface a = applicationContext.getBean(AInterface.class); 这种手动采集也是可行的。~~~本文不讨论这种更直接和简单的方法)
  2. 使用 AopContext.currentProxy(); 方式

这篇文章解释了如何取一个。 给自己注射 解决方案 更多问题 ,使用 AopContext.currentProxy(); 该方法将在下一篇博客文章中详细解释~

注意: 给自己注射 是能够完美解决交易不生效问题。如题,本文旨在讲解解决 @Async 的问题~~~

一些小伙伴肯定会说:不要叫这个班 @Async 方法不够;没有循环依赖是不够的。这些都是解决方案。~
事实上,你所说的没有错,但我想说:理想的设计当然不建议循环依赖。 但在现实中 业务开发 中周期依赖性为100%不可避免的是,同一样本类方法的互调也是不可避免的。~

关于 @Async 感兴趣的学生可以先补课:
【小家Spring】Spring异步处理@Async使用和原理,源代码分析(@EnableAsync)

你靠自己 分析该方案提出的问题

注意:默认情况下,所有示例都是。 @EnableAsync 已经开启~ 因此,示例代码不再被特别标记。

你靠自己 这种方法是一种典型的用法 循环依赖 解决问题的方法, 在大多数情况下 这是一个非常好的解决方案。
例如,在这种情况下要解决。 @Async 这个类调用这个问题,我们的代码将这样写:

@Service
public class HelloServiceImpl implements HelloService {

    @Autowired
    private HelloService helloService;

    @Transactional
    @Override
    public Object hello(Integer id) {
        System.out.println("线程名称:" + Thread.currentThread().getName());
        // fun1(); // 这样书写@Async它肯定不会生效~
        helloService.fun1(); //调用接口方法
        return "service hello";
    }

    @Async
    @Override
    public void fun1() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

我认为这样的写作肯定会完美地解决问题,就像解决事务问题一样。但为什么 带来了新问题 ,启动报告错误:

错误报告信息如下~~~

BeanCurrentlyInCreationException 这种例外类型的小伙伴应该并不陌生。 循环依赖 在那篇文章中(参见相关阅读),据说有日志提醒小伙伴注意错误报告,这肯定有一天会发生。 碰面 ,没想到来得这么快~

对于上面的异常信息,我大致翻译如下:

创建名称“helloServiceImpl”的bean错误:名为“helloServiceImpl”的bean其他已作为循环引用的一部分注入其原始版本bean[helloServiceImpl]中,
**但最终它被打包了**。这意味着另一个bean不使用bean最终版本。
问题定位

本着 只有先定位问题才能解决问题的原则。 找到问题的根本原因已经成为我现在最需要做的事情。从错误信息的描述可以看出,根本原因是。 helloServiceImpl 最终被打包(代理),如此使用。bean不是最终版本,所以Spring自查机制~~~

说明:Spring管理的Bean是单一案例,所以Spring默认需要确保 使用 此Bean所有位置都指向同一地址,即最终版本Bean,否则可能出现故障,Spring还应提供 自检机制~

上述叙述有点苍白。我相信我的朋友们也在看一张愚蠢的武力的脸和两张愚蠢的力量的脸。下面是一个示例代码分析以查看结果。

为了更好地解释这个问题,这里不使用它。 你靠自己 为了表达(因为相同的名字很容易混淆,不方便解释问题)A、B两个类的形式描述:

@Service
public class A implements AInterface {
    @Autowired
    private BInterface b;
    @Async
    @Override
    public void funA() {
    }
}

@Service
public class B implements BInterface {
    @Autowired
    private AInterface a;
    @Override
    public void funB() {
        a.funA();
    }
}

如上所述,示例代码将在启动时报告错误:(示例代码模仿成功)

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 getBeanNamesOfType with the allowEagerInit flag turned off, for example.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
    ...

以下是跟踪源代码并定位此问题的关键点:

protected Object doCreateBean( ... ){
    ...
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    ...

    // populateBean这句话特别关键,需要给出。A属性赋值的,所以它将在这里实例化。B~~
    // 而B从上面我们可以看出,它是一个普通的。Bean(不需要创建代理对象),在实例化完成后,继续给出其属性。A在这一点上A早期参考文献
    // 它也在这里给予B的属性a当分配一个值时,它将被执行并放在上面。Bean A流程中的getEarlyBeanReference()方法  从而拿到A早期参考文献~~
    // 执行A的getEarlyBeanReference()方法,则执行自动代理创建程序,但因为。A没有标记的事务,因此最终不会创建代理,so B限定的属性引用将是A的**原始对象**
    // 应注意:@Async代理对象不在中。getEarlyBeanReference()创建于,位于postProcessAfterInitialization代理已创建
    // 从中我们也可以看出@Async的代理它默认并不支持你去循环引用,因为它并没有把代理对象早期参考文献提供出来~~~(请注意这与自动代理创建器之间的区别。~)

    // 结论:这里A依赖关系属性字段B赋值为了B的实例(因为B不需要创建代理,因此它是原始对象。)
    // 这是一个例子。B依赖性内部。A注射的仍然是Bean A普通实例对象(注意  原始对象是非代理对象)  注:此时exposedObject还是原始对象
    populateBean(beanName, mbd, instanceWrapper);

    // 标注有@Async的Bean代理对象在此处生成。~~~ 参照类:AsyncAnnotationBeanPostProcessor
    // 所以在这句话被执行之后  exposedObject它将是代理对象而不是原始对象。
    exposedObject = initializeBean(beanName, exposedObject, mbd);

    ...
    // 这是错误报告的重点。~~~
    if (earlySingletonExposure) {
        // 上面说了A被B周期是依赖的,所以在这一点上。A它被放入二级缓存,所以这里earlySingletonReference 是A对原始对象的引用
        // (这也解释了我为什么说:如果A没有循环依赖,不会有错误,也不会有问题。   因为如果没有循环依赖earlySingletonReference =null后面是直接return了)
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 上述分析exposedObject 是被@Aysnc已被代理的对象, 而bean是原始对象 所以这里不相等。  走else逻辑
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            // allowRawInjectionDespiteWrapping 标签是否允许Bean将的原始类型注入另一个。Bean内部,即使它们最终将被打包(代理)
            // 默认是false指示如果更改,则不允许true如果你允许,你就不会犯错误。这是我们稍后将讨论的解决方案之一。~~~
            // 另外dependentBeanMap记录每个Bean它依赖于Bean的Map~~~~
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                // 我们的Bean A依赖于B,so此处值为["b"]
                String[] dependentBeans = getDependentBeans(beanName);
                Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);

                // 逐个检查所有依赖项。~  比如此处B会有问题的
                // “b”它经过removeSingletonIfCreatedForTypeCheckOnly最终回报false  因为alreadyCreated它已经有了。B它已完全创建~~~
                // 而b全部完成,因此属性a还指定一个值以完成聊天 但是B引用于a我的主要过程是A甚至不相等,那肯定有问题(描述不是最终的)~~~
                // so最终将添加到actualDependentBeans进去,指示A真正的依赖~~~
                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 " +
                            "getBeanNamesOfType with the allowEagerInit flag turned off, for example.");
                }
            }
        }
    }
    ...
}

不要回避这里的知识点 @Aysnc 带注释的Bean创建代理的时间。
@EnableAsync 打开后,注入容器。 AsyncAnnotationBeanPostProcessor ,它是一个 BeanPostProcessor ,实现了 postProcessAfterInitialization 方法这里我们看一下代码,在抽象父类中创建代理的动作。 AbstractAdvisingBeanPostProcessor 上:

// @since 3.2   注意:@EnableAsync在Spring3.1后出现
// 继承自ProxyProcessorSupport,因此具有动态代理相关属性。~ 易于创建代理对象。
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {

    // 全部已处理Bean~~~  eligible:合适的
    private final Map, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);

    //postProcessBeforeInitialization方法不应该做什么~
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    // 重点在这里。什么时候Bean初始化完成后,它将在这里执行,并在这里做出决定,看看是否有必要。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;

            // 这里是AopUtils.getTargetClass(bean)目标对象,做出最终判断
            // isEligible()判断方法是否合适。  是本文中最重要的方法之一,如下所述~
            // 这里有一个小细节:isFrozen为false也就是说,在它被冻结之前,只有一个剪切界面被添加到它。   不要自己创建代理对象。  省事
            if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                // Add our local Advisor to the existing proxys Advisor chain...
                // beforeExistingAdvisors决定这该advisor是先执行还是最后执行
                // 此处的advisor为:AsyncAnnotationAdvisor  它切入Class和Method标注有@Aysnc注意这个地方~~~
                if (this.beforeExistingAdvisors) {
                    advised.addAdvisor(0, this.advisor);
                } else {
                    advised.addAdvisor(this.advisor);
                }
                return bean;
            }
        }

        // 如果它不是代理对象,则必须从这里开始。~~~~isEligible() 这种方法特别重要
        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);
            //customize把它交给子类重写(实际的子类目前没有重写)~)
            customizeProxyFactory(proxyFactory);
            return proxyFactory.getProxy(getProxyClassLoader());
        }

        // No proxy needed.
        return bean;
    }

    // 我们发现BeanName最终,它实际上没有被使用。~~~
    // 但是子类AbstractBeanFactoryAwareAdvisingPostProcessor它被使用  什么都没做 可以忽略~~~
    protected boolean isEligible(Object bean, String beanName) {
        return isEligible(bean.getClass());
    }
    protected boolean isEligible(Class targetClass) {
        // 首次进来eligible的值必须为null~~~
        Boolean eligible = this.eligibleBeans.get(targetClass);
        if (eligible != null) {
            return eligible;
        }
        // 如果根本没有配置advisor  我不需要看~
        if (this.advisor == null) {
            return false;
        }

        // 最重要的是canApply该方法,如果AsyncAnnotationAdvisor  能切进它  那么这就是true
        // 在本例中,方法标签为@Aysnc注意,这样铁就可以切进去了。  返回true继续上面方法体的内容
        eligible = AopUtils.canApply(this.advisor, targetClass);
        this.eligibleBeans.put(targetClass, eligible);
        return eligible;
    }
    ...
}

经此一役 基本原则是,只要可以切割 AsyncAnnotationAdvisor 切入(即,只需要上课/方法已标记 @Async 注释已足够)Bean最终,将生成一个代理对象(如果它已经是代理对象,只需添加切线~)分配给上述人员 exposedObject 作为最终回报add进Spring容器内~


对于上述步骤,为了帮助理解,我尝试将文本描述总结如下:

  1. context.getBean(A) 开始创建A,A实例化完成后,给出A依赖关系属性b开始赋值~
  2. context.getBean(B) 开始创建B,B实例化完成后,给出B依赖关系属性a开始赋值~
  3. 焦点:此时因为A支持循环依赖,因此它将被执行。A的 getEarlyBeanReference 方法得到它早期参考文献。而执行 getEarlyBeanReference() 因为 @Async 它根本没有被执行,所以最后的返回仍然是。 原始对象 的地址
  4. B完成初始化,完成属性的分配,此时属性field持有的是Bean A 原始类型 的引用~
  5. 完成了A财产的转让B实例的),继续执行初始化方法。 initializeBean(...) ,在此处解决。 @Aysnc 注释,从而生成 代理对象 ,所以最终 exposedObject 是最终添加到容器中的代理对象(不是原始对象)。~
  6. 尴尬场面 出现了:B引用的属性A是一个原始对象,在这里它是准备好的。return的实例A 结果是一个代理对象 ,即B引用不是 最终对象 (不是最终在容器中的对象)
  7. 执行自检程序:到期。 allowRawInjectionDespiteWrapping 默认值是false表示不允许出现上述不一致,so最后,它被扔错了。~

这一步是我自己的一个即兴总结,希望能帮助我的朋友理解它。如果有任何错误,请同时指出,让我帮我纠正。

解决方案

通过以上分析,了解了问题的根本原因,并总结了上述问题的解决方案。 新问题 解决方案可分为以下三个选项:

  • allowRawInjectionDespiteWrapping 设置为true
  • 使用 @Lazy 或者 @ComponentScan(lazyInit = true) 解决
  • 不要让 @Async 的Bean涉及循环依赖

1、把 allowRawInjectionDespiteWrapping 设置为true:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
    }
}

在此配置之后,容器启动将不再报告错误, 但是 :Bean A的 @Aysnc 方法将不起作用,因为Bean B依赖性内部。a是一个原始对象,因此它最终无法执行异步操作( 即使集装箱a是代理对象 ):

应注意:但此时候Spring集装箱内部Bean A是Proxy代理对象的~~~

但是,如果这种情况通常取决于( 非循环依赖 )的a,注入的是代理对象, @Async 异步仍将生效~

一方面,这种解决方案并没有达到其真正目的(毕竟Bean A上的 @Aysnc 无效)。

由于它 仅在循环依赖范围内。Bean受影响 ,所以影响的范围不是全局,所以当没有更好的方法时,这也是一个好的计划,所以我个人对这个计划的态度是 不推荐也不反对

2、使用 @Lazy 或者 @ComponentScan(lazyInit = true) 解决

此Office使用 @Lazy 为例:( 强烈不推荐 使用 @ComponentScan(lazyInit = true) 作用范围太广,容易产生意外伤害)

@Service
public class B implements BInterface {
    @Lazy
    @Autowired
    private AInterface a;

    @Override
    public void funB() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
        a.funA();
    }
}

注意此 @Lazy 注意加法的位置,因为。a最终会是 @Async 代理对象的,因此在 @Autowired 它的位置加上
此外,如果没有循环依赖,而是直接引用。a,不添加 @Lazy

只需要在Bean b依赖关系属性上加上 @Lazy 够了。(既然是B希望最终的代理对象会出现,因此B添加它,A无需添加)

最终结果令人满意:启动正常 @Async 异步效应也起作用, 因此,我推荐这个方案

然而,需要注意的是,在这种情况下B里持有A的引用和Spring容器里的A 不一样 ,如下图所示:


两处实例a地址值不同,容器为。 $Proxy@6914 ,B持有的是 $Proxy@5899

关于 @Autowired@Lazy 事实上,为什么联合使用这种现象 @Lazy 的代理对象 ContextAnnotationAutowireCandidateResolver 生成,请参阅博客文章: 【小家Spring】Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注释原则

3、不要让 @Async 的Bean涉及循环依赖
显然,如果方案3如果能解决,那绝对是最优解。然而,它是 现实情况 最难达成的解决方案。
因为在 实际业务发展 在循环依赖、类内方法调用等情况下,这是不可避免的,除非重新设计代码结构,根据规范进行更改等等。 这种计划众说纷纭。~


为何@Transactional即使是循环依赖也不是问题?

最后,回答我的搭档问我的问题: 同为 创建动态代理对象, 同为 在类上标记注释。 / 方法,为什么 @Transactional 不会有这样的启动错误?

事实上,上一篇文章的后半部分已经解释了这个问题的答案,详见
【小家Spring我会在一篇文章中告诉你。Spring是如何使用"三级缓存"巧妙解决Bean关于循环依赖问题

尽管它们的原理是生成代理对象,但注释的使用方式几乎相同。so区别Spring对这两兄弟的分析是不同的,也就是说,他们的经纪人的创建方式不同:

  • @Transactional 使用自动代理创建器 AbstractAutoProxyCreator ,在上一篇文章中详细描述 getEarlyBeanReference() 因此,该方法为循环依赖提供了良好的支持
  • @Async 代理是使用创建的 AsyncAnnotationBeanPostProcessor 实现了一个单独的后处理器,它只在一个地方。 postProcessAfterInitialization() 代理对象的创建是实现的,因此如果它出现 循环依赖 如果这样做,您将报告如上所述的错误。~~~

so尽管从外观上看,这两个注释的实现方式相同,但在实现过程的细节上,两者之间的差异仍然非常明显。在了解了执行的差异之后,自然不难理解为什么会出现错误以及是否报告错误。~



最后,在理解原则的基础上,我们还应注意以下几点case(加深理解),如果出现以下情况,事实上,启动不报告错误,可以正常work的:

与上面的例子相比:唯一的区别是 @Async 写在bean B上而A未写入(以上已写入bean A上而B未写入)

@Service
public class A implements AInterface{
    @Autowired
    private BInterface b;
    @Override
    public void funA() {
    }
}

@Service
public class B implements BInterface {
    @Autowired
    private AInterface a;
    @Async // 写在B的方法上  这样B最终将创建代理对象。
    @Override
    public void funB() {
        a.funA();
    }
}

注:如果正常Spring容器将首先初始化。A,start肯定没有错,这是我上面所说的结论:在这种情况下,默认值是可以的work的

你也可以通过猜来猜,A和B不是互惠关系,处理结果和Bean初始化顺序相关。

至于Spring对Bean实例化,初始化顺序,如果没有特殊干预,它与类名字母排序有关。~

为了说明这个问题,我首先手动干预。Spring容器初始化B(这里的方案是使用 @DependsOn("b") ):

@DependsOn("b")
@Service
public class A implements AInterface { ... }

这种干预将确保B肯定在A在之前初始化,然后启动也将报告 同样错误 (当然,这里报告的错误信息是bean b):

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name b: Bean with name b has been injected into other beans [a] 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.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
    ...

如果技术敏感性方面的小伙伴发现了,这可以为我们提供解决方案。 你靠自己 问题的另一个想法,我们可以考虑介入吗Bean实现正常启动目的的初始化顺序?
理论上可行,但在个人的实践过程中 不太建议 这样做(如果有更好的计划)~



总结

虽然 Spring 官员们也不建议循环依赖,但一种是理想情况,另一种是现实情况。它们之间存在差距和差异。
实际使用,尤其是 业务开发 中心周期依赖可以说是几乎不可避免的,所以知道它,但知道它,然后,可以完全理解,遇到问题不再困惑。

使用 AopContext.currentProxy(); 方式解决 类似的方法调用 由于这种方法也是一个很大的话题,它仅限于篇幅和聆听 紧邻的 下文分解~

The last如果你觉得这篇文章对你有帮助,不妨点赞。当然,分享你的朋友圈,让更多的小伙伴看到 作者本人允许~


关注A哥

Author

A哥(YourBatman)

个人站点

www.yourbatman.cn

E-mail

yourbatman@qq.com

微 信

fsx641385712

活跃平台

公众号

BAT乌托邦(ID:BAT-utopia)

知识星球

BAT的乌托邦

每日文章推荐

每日文章推荐

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除