一文徹底講透@Async注解的原理和使用方法


一.背景: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)可以不用了,再次測試:

 

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

 
        

 

 
        
 
        

 

 

 

 
        

 

 
        
 
        

 

 

 

 

 

 

 

 

 

 
        
 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM