一文彻底讲透@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)可以不用了,再次测试:

 

 上述就是同个类中调用异步方法不生效的解决方案


 



 

 

 


 



 

 

 

 

 

 

 

 

 



 

posted @ 2021-05-31 16:13  yangxiaohui227  阅读(10041)  评论(3编辑  收藏  举报