@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来存储这个配置类(配置类有fulllite的区别,暂时不聊),然后就迭代寻找有没有@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容器中了。接下来就是,他怎么工作?

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值