@Async报错探究一
问题引入
我有一个TaskService
:
@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private AlgorithmService algorithmService;
@Autowired
private RobotService robotService;
@Override
@Async
public void getTaskExecutionTime() {
try {
System.out.println(Thread.currentThread().getName() +" start task...");
System.out.println("step1...");
Thread.sleep(3000);
System.out.println("step2...");
Thread.sleep(3000);
System.out.println("step3...");
Thread.sleep(3000);
System.out.println("step4...");
Thread.sleep(3000);
System.out.println("step5...");
System.out.println(Thread.currentThread().getName() + " end task...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void getTaskInfo() {
System.out.println("this presents the task info...");
}
}
我又有一个RobotService
:
@Service
public class RobotServiceImpl implements RobotService {
@Autowired
private TaskService taskService;
@Override
public void processCommand() {
taskService.getTaskInfo();
}
}
显然,这两个service有着循环依赖,但是我当下版本的springboot是支持循环依赖的。
问题的关键是:我的TaskService
有一个异步方法:
@Async
public void getTaskExecutionTime() {
...}
此时,如果我的controller只有一个,并且长成这样:
@RestController
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping("/taskTime")
public Long taskTime(){
long start = System.currentTimeMillis();
taskService.getTaskExecutionTime();
long end = System.currentTimeMillis();
return end-start;
}
}
spring给我报这么一个错:
Unsatisfied dependency expressed through field 'taskService';
nested exception is org.springframework.beans.factory.
BeanCurrentlyInCreationException:
Error creating bean with name 'taskServiceImpl':
Bean with name 'taskServiceImpl' has been injected into other
beans [robotServiceImpl] 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.
他的意思就是taskServiceImpl
以原始的形态注入到了robotServiceImpl
,但最终被包装了,所以robotServiceImpl
里拿到的不是最终形态的taskServiceImpl
。
所谓的被包装,其实就是做了代理呗。那么,这一切都是怎么发生的呢?
我们一步步来看。
@Import的工作
我的启动类长这样:
@SpringBootApplication
@EnableAsync
public class AsyncMethodApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncMethodApplication.class, args);
}
@Bean
public Executor taskExecutor(){
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(5);
threadPoolTaskExecutor.setMaxPoolSize(5);
threadPoolTaskExecutor.setQueueCapacity(500);
threadPoolTaskExecutor.setThreadNamePrefix("mytask-");
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
@EnbaleAsync
注解导入了AsyncConfigurationSelector
。
追踪下去我们知道这东西其实就是一个ImportSelector
。
所以最后一定会走子类(AsyncConfigurationSelector
)的selectImports
。
大概了解这些,我们要知道@Import
注解是在哪里被解析的。其实,spring会先去读配置类的注解,比如@Configuration
,@Component
,@ComponentScan
等等标注的类,对于这些类,spring会用ConfigurationClass
对其进行建模。比如拿到某一个配置类,然后就会new一个ConfigurationClass
来存储这个配置类(配置类有full
和lite
的区别,暂时不聊),然后就迭代寻找有没有@Import
注解,有没有@Bean
注解等等。
那么这一切都是在哪个阶段发生的呢?
在实例化bean之前,BeanFactoryPostProcessor
会进行工作,以添加BeanDefinition
。有一个BeanFactoryPostProcessor
是我们要关注的,那就是ConfigurationClassPostProcessor
。
由于我们的启动类AsyncMethodApplication
就有@Configuration
注解(由@SpringBootApplication
提供的),所以我们就关注他,开始debug:
refresh
方法的这个阶段,就是ConfigurationClassPostProcessor
工作的地方。
为什么要进这个方法呢?因为我们要关注的ConfigurationClassPostProcessor
就是一个BeanDefinitionRegistryPostProcessor
:
接着往下走。
如果BeanDefinition
中有某个标记,那就说明他已经被处理过了(至于是哪个标记不用去管)。显然,我们的AsyncMethodApplication
还没有这个标记。
于是走else if
。
然后spring发现了@Configuration
注解,就标记一个full
(全配置类)。
然后,他搞了一个解析器,来解析我们的配置类AsyncMethodApplication
。
将AsyncMethodApplication
包装成一个ConfigurationClass
慢慢解析。
他这里有一个configClass
和一个sourceClass
,前者就是当前要解析的配置类,后者就是你是哪个配置类导进来的意思,由谁引入的。此时两者都是AsyncMethodApplication
。
通过ComponentScan
他发现了5个我写的业务类,并且都将他们当作配置类进行递归解析。这个我们不管。
接下来终于要解析@Import
了。
我们的AsyncConfigurationSelector
就是一个ImportSelector
。
他把AsyncConfigurationSelector
实例化了,然后调用selectImports
。
他返回了一个ProxyAsyncConfiguration
。这个东西必然是重点,我们等下看,先等方法走完。
我们又要进processImports
了(递归)。此时就有了一个导入的类,那么递归又是如何处理这个类的呢?
将其作为配置来处理,这是符合预期的,因为我们的ProxyAsyncConfiguration
就是个配置类。
又到了处理配置类的环节了,看你有没有@Import
啦,有没有@Bean
啦,有没有@ComponentScan
啦,处理接口啦。
添加了@Bean
注解的方法搞进来。
因为ProxyAsyncConfiguration
还有父类,所以还要处理一遍父类。
再次进入do while
。
最后将我们的ProxyAsyncConfiguration
放到一个已处理好的配置类这样一个map中。
组件存入map
从parse
方法出来后,我们有了许许多多的配置类,其他不管,我们只关注ProxyAsyncConfiguration
。
进到loadBeanDefinitions
方法去:
首先将自己(ProxyAsyncConfiguration
)放到beanDefinitionMap
中。
然后将@Bean
要注册的对象放到beanDefinitionMap
中:
所以忙活了半天,最终是要注册一个bpp:AsyncAnnotationBeanPostProcessor
。bpp的工作时机是在bean实例化后的初始化前后,我们先长个心眼。
refresh
的这个阶段会实例化bpp。这里我们不看了,就当他已经在spring容器中了。接下来就是,他怎么工作?