前言
在我们使用Spring时,可能有前辈教导过我们,在bean中不要使用this来调用被@Async、@Transactional、@Cacheable等注解标注的方法,this下注解是不生效的。
那么大家可曾想过以下问题
- 为何致this调用的方法,注解会不生效
- 这些注解生效的原理又是什么
- 如果确实需要调用本类方法,且还需要注解生效,该怎么做?
- 代理是否可以做到this调用注解就直接生效?
通过本文,上面的疑问都可以解决,而且可以学到很多相关原理知识,信息量较大,那么就开始吧
现象
以@Async注解为例,@Async注解标记的方法,在执行时会被AOP处理为异步调用,调用此方法处直接返回,@Async标注的方法使用其他线程执行。
使用Spring Boot驱动
@SpringBootApplication@EnableAsyncpublic class Starter { public static void main(String[] args) { SpringApplication.run(Starter.class, args); }}@Componentpublic class AsyncService { public void async1() { System.out.println("1:" + Thread.currentThread().getName()); this.async2(); } @Async public void async2() { System.out.println("2:" + Thread.currentThread().getName()); }}@RunWith(SpringRunner.class) @SpringBootTest(classes = Starter.class)public class BaseTest { @Autowired AsyncService asyncService; @Test public void testAsync() { asyncService.async1(); asyncService.async2(); }}
输出内容为:
1:main2:main2:SimpleAsyncTaskExecutor-2
第一行第二行对应async1()方法,第三行对应async2()方法,可以看到直接使用asyncService.async2()调用时使用的线程为SimpleAsyncTaskExecutor,而在async1()方法中使用this调用,结果却是主线程,原调用线程一致。这说明@Async在this调用时没有生效。
思考&猜测
已知对于AOP动态代理,非接口的类使用的是基于CGLIB的动态代理,而CGLIB的动态代理,是基于现有类创建一个子类,并实例化子类对象。在调用动态代理对象方法时,都是先调用子类方法,子类方法中使用方法增强Advice或者拦截器MethodInterceptor处理子类方法调用后,选择性的决定是否执行父类方法。
那么假设在调用async1方法时,使用的是动态生成的子类的实例,那么this其实是基于动态代理的子类实例对象,this调用是可以被Advice或者MethodInterceptor等处理逻辑拦截的,那么为何理论和实际不同呢?
这里大胆推测一下,其实async1方法中的this不是动态代理的子类对象,而是原始的对象,故this调用无法通过动态代理来增强。
关于上面AOP动态代理使用CGLIB相关的只是,可以参考完全读懂Spring框架之AOP实现原理这篇文章。
下面开始详细分析。
源码调试分析原理
首先要弄清楚@Async是如何生效的:
1. 分析Async相关组件
从生效入口开始看,@EnableAsync注解上标注了@Import(AsyncConfigurationSelector.class)
@Import的作用是把后面的@Configuration类、ImportSelector类或者ImportBeanDefinitionRegistrar类中import的内容自动注册到ApplicationContext中。关于这三种可Import的类,这里先不详细说明,有兴趣的读者可以自行去Spring官网查看文档或者等待我的后续文章。
这里导入了AsyncConfigurationSelector,而AsyncConfigurationSelector在默