一.背景:spring提供了@Async異步注解,使得方法的調用可以異步的進行,下面代碼提供簡單的演示:
@Configuration @EnableAsync @ComponentScan("com.yang.xiao.hui.aop") public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class); MyAsync service = ctx.getBean(MyAsync.class); System.out.println(service.getClass()); service.async1(); System.out.println("目標方法執行完沒........."); } } @Component public class MyAsync { @Async public void async1() { try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello1:"+this.getClass()); } public void async2(){ System.out.println("hello2:"+this.getClass()); this.async1(); } }
上述代碼提供了最簡單的異步使用方式,如果是同步執行,那么控制台打印的順序應該是:
System.out.println("hello1:"+this.getClass())-------》 System.out.println("目標方法執行完沒.........");
然而控制台的打印剛好相反,證明異步的注解生效了:

二.原理分析
1.猜想:aync1()方法標注了@Async注解,該方法就異步執行了,那么該方法肯定是被攔截了,方法攔截肯定存在一個方法攔截器MethodInterceptor

方法攔截器是一個接口,對異步方法的攔截,肯定是該接口的一個實現類,如何找到它:
2.線索分析:我們的唯一條件是主啟動類上貼了一個@EnableAsync注解
點擊進去分析:

根據追蹤,我們發現,最終會往容器中注入 AsyncAnnotationBeanPostProcessor
分析其繼承體體系,發現其實現了BeanFactoryAware接口,實現該接口的類,spring容器在創建該bean時,會回調:void setBeanFactory(BeanFactory beanFactory) throws BeansException;

接着我們看看:

我們之后看看AsyncAnnotationAdvisor的創建過程:

接着看this.advice = buildAdvice(executor, exceptionHandler);

至此,我們終於找到方法攔截器了,為何是它,看看它的繼承體系:

3.驗證:既然找到了方法攔截器,那么我們就打斷點在攔截方法里,執行之前的測試代碼:攔截方法在它的父類中:AsyncExecutionInterceptor


原理非常簡單:1.獲取線程池 2.創建callable 3.執行線程
重點是線程池的獲取邏輯:

由上所得:線程池會首先通過用戶自己配置的為准:

所以,我們可以自定義線程池,然后注入spring中,將bean的名字放到@Async注解的value值即可

最后我們看看默認的線程池是怎么獲取的:
targetExecutor = this.defaultExecutor.get();

可以知道,他是通過調用getDefaultExecutor(this.beanFactory)


由此可見,會先獲取容器中TaskExecutor的線程池,獲取不到,就獲取指定beanName的線程池DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor";
我們看看默認的線程池

總結:@Async的異步線程池獲取順序:

三:學以致用

直接創建2個異步方法:

四:思考:如果一個類中,非異步方法調用了異步的方法,異步方法還會生效么:
@Component public class MyAsync { @Async public void async1() { try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello1:"+this.getClass()); } public void async2(){ System.out.println("hello2:"+this.getClass()); this.async1(); } }
@Configuration @EnableAsync @ComponentScan("com.yang.xiao.hui.aop") public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class); MyAsync service = ctx.getBean(MyAsync.class); System.out.println(service.getClass()); service.async2(); System.out.println("目標方法執行完沒........."); } }
如果異步生效,那么System.out.println("目標方法執行完沒.........");會先於System.out.println("hello1:"+this.getClass());執行
然而結果:

由此看到並不是同步執行,原因如下:
service.async2(); 方法調用:service是代理類,上圖打印的第一行可以知道,它是cglib代理,因此該方法本質調用的是代理類的aync2(),而該方法並沒有@aync注解也就沒有方法攔截器,因此
代理類執行async2()方法,本質最終是直接調用了被代理類的方法,所以上圖打印出的第二行可以知道:
this.getClass()==com.yang.xiao.hui.aop.MyAsync

由此可以知道,只要是代理類調用async1()方法,就可以讓異步調用生效了,如何獲取被代理類,既然asyn2()是代理類調用的,肯定會被攔截,我們debug進入


當this.advised.exposeProxy=true時,代理類設置到AopContext中


原來是設置到ThreadLocal中,那成立的條件又是啥

該值其實就是@EnableAspectJAutoProxy(exposeProxy = true)的值

默認情況是不會將代理類放到ThreadLocal中的,我們要自己開啟:

由此,改進我們的調用方法,通過ThreadLocal獲取代理類,然后調用目標方法


啟動測試:

居然報錯了。。。。。,這么說,@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)並沒有生效,那么我們看看該注解的配置原理是啥?



由此發現,它指對自動創建的的aop創建器生效,到底有哪些呢?

事務注解相關的就可以,但我們的是異步注解,根據之前源碼分析知道,異步是通過AsyncAnnotationBeanPostProcessor來實現的;

由此,我們是否可以通過獲取AsyncAnnotationBeanPostProcessor的beanDefiniton然后給它新增屬性,類型下面的實現:

具體改造如下:
@Component public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { BeanDefinition beanDefinition = registry.getBeanDefinition(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME); if(null!=beanDefinition){ beanDefinition.getPropertyValues().add("exposeProxy", Boolean.TRUE); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
此時啟動類的@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)可以不用了,再次測試:

上述就是同個類中調用異步方法不生效的解決方案
