002.Spring 使用 Async 导致循环创建异常

1. 场景前提

假设有这样一个场景:在一个被 Spring 容器管理的 SomeService 类中定义了两个方法,一个方法叫 doSomething(),另一个方法叫 sendMsg(),其中 sendMsg() 是处理短信业务的,现在在 doSomething() 中调用 sendMsg(),如何才能使短信业务变成异步呢

场景一:如果为同步处理情况下,可以直接使用 this.sendMsg() 进行调用

场景二:当把 sendMsg 改为异步后,考虑到 Spring 中做增强的是代理类而非方法,所以通过 @Autowired 把当前类注入到自己的属性中呢,结果抛出了 BeanCurrentlyInCreationException 异常

场景三:在网上一顿百度之后,大多数的解决方案都是加上 @Lazy 注解,于是加上之后项目稀里糊涂地又好了

场景四:加上 @Lazy 注解是否是最优的解法呢?其实更推荐将异步 @Async 的处理单独放到一个类中,以此解决循环引用,代码也变得更加优美

2. 环境搭建

代码已经上传至 https://github.com/masteryourself/diseases ,详见 diseases-spring/diseases-spring-async-cycle 工程

2.1 代码
1. SomeService
@Service
public class SomeService {

    /***********************************    场景一   ***********************************/
    /*public void doSomething() {
        System.out.println(Thread.currentThread().getName() + ":do doSomething finish");
        // 直接同步调用发短信方法
        this.sendMsg();
    }

    public void sendMsg() {
        System.out.println(Thread.currentThread().getName() + ":do sendMsg() method");
    }*/


    /***********************************    场景二   ***********************************/
    /**
     * 考虑到这里如果直接使用 this.sendMsg() 就不会有代理功能, 因为 Spring 增强的是代理类, 而非方法, this 是指对象本身
     * 所以自作聪明地使用了 {@link Autowired} 注入了代理对象, 然后使用这个对象去调用 sendMsg() 方法, 结果抛出了异常
     * 当此类中添加 {@link Async} 这种基于 {@link BeanPostProcessor} 去生成 aop 动态代理的组件时, 就会抛出循环引用异常
     */
    /*@Autowired
    private SomeService someService;

    public void doSomething() {
        System.out.println(Thread.currentThread().getName() + ":do doSomething finish");
        // 使用 someService 调用, 发现代理生效
        someService.sendMsg();
    }

    @Async
    public void sendMsg() {
        System.out.println(Thread.currentThread().getName() + ":do sendMsg() method");
    }*/


    /***********************************    场景三   ***********************************/
    /**
     * 使用 {@link Lazy} 避免
     * @Lazy 可以理解为在需要 bean 的时候才会去获取真正的 bean 对象, 而不是启动时候就解决其依赖问题, 这样自然就避免了循环依赖
     */
    /*@Lazy
    @Autowired
    private SomeService someServiceProxy;

    public void doSomething() {
        System.out.println(Thread.currentThread().getName() + ":do doSomething finish");
        // 使用 someService 调用, 发现代理生效
        someServiceProxy.sendMsg();
    }

    @Async
    public void sendMsg() {
        System.out.println(Thread.currentThread().getName() + ":do sendMsg() method");
    }*/

    /***********************************    场景四   ***********************************/
    /**
     * 重新定义一个组件, 把代理对象和本身分开, 勿要自作聪明, 在自己的类中注入自己
     */
    @Autowired
    private SomeServiceAsyncHandler someServiceAsyncHandler;

    @Async
    public void doSomething() {
        System.out.println(Thread.currentThread().getName() + ":do doSomething finish");
        someServiceAsyncHandler.sendMsg();
    }

}
2. SomeServiceAsyncHandler
@Service
public class SomeServiceAsyncHandler {

    public void sendMsg() {
        System.out.println(Thread.currentThread().getName() + ":do sendMsg() method");
    }

}

3. 异常剖析

3.1 错误日志

使用场景二的代码启动后将会抛出如下异常

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'asyncCycleApplication': Unsatisfied dependency expressed through field 'someService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'someService': Bean with name 'someService' has been injected into other beans [someService] 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.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$78/1764696127.getObject(Unknown Source) ~[na:na]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at pers.masteryourself.diseases.spring.async.cycle.AsyncCycleApplication.main(AsyncCycleApplication.java:35) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'someService': Bean with name 'someService' has been injected into other beans [someService] 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) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$78/1764696127.getObject(Unknown Source) ~[na:na]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1247) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	... 20 common frames omitted
3.2 异常详解
3.2.1 EnableAsync 注解

@EnableAsync -> AsyncConfigurationSelector -> ProxyAsyncConfiguration -> AsyncAnnotationBeanPostProcessor

@EnableAsync 注解实际上就是给容器中导入组件,最终会导入 AsyncAnnotationBeanPostProcessor 组件,它实际上是一个 BeanPostProcessor 类型组件,所以重点应该关注 postProcessAfterInitialization() 方法,它实际上就是帮容器中的 bean 做代理,然后返回代理对象到容器中

3.2.2 doCreateBean() 方法

根据异常信息可以定位到这个方法,下面简单分析一下这个方法

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
		throws BeanCreationException {

	// Instantiate the bean.
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	if (instanceWrapper == null) {
	    // 使用构造方法创建 bean 实例,经过包装后返回包装对象
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	// 获取 bean 对象,此时的 bean 还没有完成赋值操作,仅仅是构造出来的对象
	final Object bean = instanceWrapper.getWrappedInstance();
	
	...

	// Initialize the bean instance.
	Object exposedObject = bean;
	try {
	    // 给属性赋值
		populateBean(beanName, mbd, instanceWrapper);
		
		// 执行 bean 的初始化,在这里会执行 BeanPostProcessor 的 postProcessAfterInitialization()
		// 由于此类被标注了 @Async 注解,所以这里的 AsyncAnnotationBeanPostProcessor 组件会生效,然后返回一个代理对象
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	catch (Throwable ex) {
		
		...
		
	}

	if (earlySingletonExposure) {
	    // 从容器中获取对象,可能是一级缓存、二级缓存,这里是从二级缓存中获取的原始对象
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
		    // 注意这里的判断,exposedObject 是代理对象,bean 是原始对象,自然是不相等的,进入下面的 else 流程
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
			// 这里有个 allowRawInjectionDespiteWrapping 属性开关,可以避免进入下面的校验
			// hasDependentBean(beanName) 即是判断是否依赖当前的 beanName
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
				for (String dependentBean : dependentBeans) {
					if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
						actualDependentBeans.add(dependentBean);
					}
				}
				// 对依赖信息进行检查,然后抛出异常,即 Spring 默认不希望我们这么做
				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.");
				}
			}
		}
	}

	...
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值