文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring处理@Async导致的循环依赖失败问题的方案详解

2024-04-02 19:55

关注

简介

说明

本文介绍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
@SpringBootApplication
public 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;
 
@Component
public 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;
 
@Component
public 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;
 
@RestController
public 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成员上边

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;
 
@Component
public 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;
 
@Component
public 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;
 
@Component
public 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;
 
@Component
public 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()实现了对代理对象的创建,因此若它被循环依赖了,就会报错

详解

处理@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导致的循环依赖失败问题的方案详解的详细内容,更多关于Spring 循环依赖失败的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯