Spring中循环依赖和@Async异步同时使用存在的坑
Spring能够解决不是构造器注入导致的循环依赖。我最近在开发中遇到了一个问题,有两个模块,模块AA和模块BB,同时引用了公共模块C。C中有三个Bean,分别是A,B,C;他们之间存在了循环依赖(使用@Autowired注入属性)。在模块AA的启动类加了@EnableAsync注解能够正常启动,模块BB加@EnableAsync注解却启动失败了。报错的描述是循环依赖中的类A不能创建。对比了模块AA和模块BB,发现不同在于AA使用类B,模块BB没有使用A,B,C其中之一。且B没有加@Async注解。于是跟踪Spring源码,发现了其中的原因现在如下记录。注意:SpringBoot2.6以上版本要允许循环依赖要在application.properties文件中加入spring.main.allow-circular-references=true
@Async是Spring提供的异步执行的注解。要使用@Async必须在启动类加@EnableAsync注解。
@Async原理如下:
在@EnableAsync的定义中,
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync
导入了AsyncConfigurationSelector类,继承结构如下图:
在SpringBoot启动时会在自动配置解析类ConfigurationClassPostProcessor中获取ImportSelector的类并执行ImportSelector#selectImports方法。ImportSelector#selectImports的返回值表示自动配置的配置类,SpringBoot的自动配置原理就是这样做的。
AsyncConfigurationSelector类定义:
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync>
看出AsyncConfigurationSelector解析处理@EnableAsync注解的。现在看看@EnableAsync的定义:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
@Target,@Retention,@Documented是元注解。@EnableAsync 里面的定义,mode()默认是AdviceMode.PROXY,表示使用JDK动态代理。
现在来看AsyncConfigurationSelector的父类AdviceModeImportSelector,AdviceModeImportSelector实现了ImportSelector接口并定义了selectImports(AnnotationMetadata importingClassMetadata)方法:
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annType = GenericTypeResolver.resolveTypeArgument(this.getClass(), AdviceModeImportSelector.class);
Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (attributes == null) {
throw new IllegalArgumentException(String.format("@%s is not present on importing class '%s' as expected", annType.getSimpleName(), importingClassMetadata.getClassName()));
} else {
AdviceMode adviceMode = (AdviceMode)attributes.getEnum(this.getAdviceModeAttributeName());
String[] imports = this.selectImports(adviceMode);
if (imports == null) {
throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
} else {
return imports;
}
}
}
这里的annTyp就是@EnableAsync。AnnotationAttributes就是@EnableAsync定义的属性。就是@EnableAsync定义中的annotation(),mode()等。是非空的。获取annTyp里的AdviceMode属性,在@EnableAsync是AdviceMode.PROXY。AdviceMode定义如下:
public enum AdviceMode {
PROXY,
ASPECTJ;
private AdviceMode() {
}
}
PROXY表示JDK动态代理,ASPECTJ表示cglib动态代理。
回到AdviceModeImportSelector中,获取annTyp里的AdviceMode属性后调用selectImports(AdviceMode adviceMode)。selectImports(AdviceMode adviceMode)在AsyncConfigurationSelector中定义:
public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return new String[]{ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
default:
return null;
}
}
AdviceMode默认是AdviceMode.PROXY,selectImports(AdviceMode adviceMode)返回ProxyAsyncConfiguration的全限定类名。现在来看ProxyAsyncConfiguration:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
bpp.configure(this.executor, this.exceptionHandler);
Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
return bpp;
}
}
ProxyAsyncConfiguration是一个配置类且创建了AsyncAnnotationBeanPostProcessor这个Bean。AsyncAnnotationBeanPostProcessor继承结构如下:
主要关注ProxyProcessorSupport和BeanPostProcessor,ProxyProcessorSupport主要功能就是为Bean提供动态代理,BeanPostProcessor是Bean的后置处理器,可以在Bean的初始化时执行增强。
AsyncAnnotationBeanPostProcessor的父类AbstractAdvisingBeanPostProcessor实现了BeanPostProcessor的postProcessAfterInitialization(Object bean, String beanName)方法:
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor != null && !(bean instanceof AopInfrastructureBean)) {
if (bean instanceof Advised) {
Advised advised = (Advised)bean;
if (!advised.isFrozen() && this.isEligible(AopUtils.getTargetClass(bean))) {
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
} else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}
if (this.isEligible(bean, beanName)) {
ProxyFactory proxyFactory = this.prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
this.evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
this.customizeProxyFactory(proxyFactory);
ClassLoader classLoader = this.getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
classLoader = ((SmartClassLoader)classLoader).getOriginalClassLoader();
}
return proxyFactory.getProxy(classLoader);
} else {
return bean;
}
} else {
return bean;
}
}
默认情况下会进入第一个if,然后进入if (this.isEligible(bean, beanName))判断条件中进行动态代理的创建。advisor属性的初始化是在AsyncAnnotationBeanPostProcessor#setBeanFactory(BeanFactory beanFactory)方法中,在为BeanFactoryAware接口实现类设置BeanFactory时会调用setBeanFactory方法:
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType);
}
advisor.setBeanFactory(beanFactory);
this.advisor = advisor;
}
看看AsyncAnnotationAdvisor的构造函数:
public AsyncAnnotationAdvisor(
@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);
asyncAnnotationTypes.add(Async.class);
try {
asyncAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
// If EJB 3.1 API not present, simply ignore.
}
this.advice = buildAdvice(executor, exceptionHandler);
this.pointcut = buildPointcut(asyncAnnotationTypes);
}
主要添加了@Async和@Asynchronous注解,调用buildAdvice构造aop中的通知,buildPointcut构造切点。
现在回到AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization中,动态代理的主要代码如下:
ProxyFactory proxyFactory = this.prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
this.evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
this.customizeProxyFactory(proxyFactory);
ClassLoader classLoader = this.getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
classLoader = ((SmartClassLoader)classLoader).getOriginalClassLoader();
}
return proxyFactory.getProxy(classLoader);
调用prepareProxyFactory创建ProxyFactory ,为ProxyFactory设置通知,然后调用proxyFactory.getProxy创建代理。ProxyFactory主要关注ProxyFactory的父类ProxyCreatorSupport中的aopProxyFactory属性,aopProxyFactory默认是DefaultAopProxyFactory对象。 proxyFactory.getProxy过程主要是调用DefaultAopProxyFactory创建AopProxy,由AopProxy执行代理的实际创建。
那么为什么循环依赖和@Async同时使用会出问题?
例子:
@Component
public class A {
@Autowired
private B b;
@Async
public void helloA(){
}
}
@Component
public class B {
@Autowired
private C c;
// @Async
public void helloB() {
}
}
@Component
public class C {
@Autowired
private A a;
//@Async
public void helloC(){
}
}
现在启动就报错:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [c] 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.
源码分析
@Autowired是AutowiredAnnotationBeanPostProcessor处理的,AutowiredAnnotationBeanPostProcessor实现了SmartInstantiationAwareBeanPostProcessor接口,SmartInstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口。
SmartInstantiationAwareBeanPostProcessor接口是在AbstractAutowireCapableBeanFactory#doCreateBean创建Bean时调用populateBean填充Bean属性调用的,BeanPostProcessor接口是在:
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
调用initializeBean执行的。所以AutowiredAnnotationBeanPostProcessor早于AsyncAnnotationBeanPostProcessor执行。
1、为A调用populateBean设置属性b,此时会去创建B,B又依赖C,又会去创建C。此时A未被AsyncAnnotationBeanPostProcessor代理。
2、B和C创建好之后,将b填充到A的属性中,调用initializeBean初始化A,由AsyncAnnotationBeanPostProcessor对A进行代理。
3、initializeBean执行完之后,进入下面的循环依赖判断:
if (earlySingletonExposure) {
Object earlySingletonReference = this.getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
String[] dependentBeans = this.getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
String[] var12 = dependentBeans;
int var13 = dependentBeans.length;
for(int var14 = 0; var14 < var13; ++var14) {
String dependentBean = var12[var14];
if (!this.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.");
}
}
}
}
判断的逻辑主要是populateBean调用之前的对象和initializeBean之后的对象是否相等(earlySingletonReference是populateBean调用之前的对象),如果不相等且还存在依赖此Bean的其他Bean则表示存在循环依赖(表示单例作用域的Bean存在同一个类型的两个不同的Bean,此时一定存在循环依赖)。如果不存在循环依赖earlySingletonReference为null,如果在initializeBean执行时未代理Bean这个判断exposedObject == bean条件一定是true。
解决方法:
1、优化逻辑,去除循环依赖,决定业务是否复杂
2、在A中加@Lazy注解,如下:
@Component
public class A {
@Autowired
@Lazy
private B b;
@Async
public void helloA(){
}
}
3、不用加加@Lazy注解,但是优先使用循环依赖中不存在@Async的Bean。比如在其他Bean中加入:
@Autowired
private B b;
也能解决。只要是循环依赖中不由有@Async注解的Bean首先创建,就不会存在上面的问题。如首先创建B,在创建C,最后创建A。就不会有上面的问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通