Spring中循环依赖和@Async异步同时使用存在的坑

发布时间 2023-03-30 23:20:33作者: shigp1

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源码,发现了其中的原因现在如下记录。

 
 

@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,此时一定存在循环依赖)。如果不存在循环依赖这个判断exposedObject == bean条件一定是true,如果在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。就不会有上面的问题。