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.");
}
}
}
}
...
}