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

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

https://www.cnblogs.com/yangxiaohui227/p/14831911.html
一.背景: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(“目标方法执行完没…”);
然而控制台的打印刚好相反,证明异步的注解生效了:
一文彻底讲透@Async注解的原理和使用方法_第1张图片
二.原理分析
1.猜想:aync1()方法标注了@Async注解,该方法就异步执行了,那么该方法肯定是被拦截了,方法拦截肯定存在一个方法拦截器MethodInterceptor
一文彻底讲透@Async注解的原理和使用方法_第2张图片

方法拦截器是一个接口,对异步方法的拦截,肯定是该接口的一个实现类,如何找到它:

2.线索分析:我们的唯一条件是主启动类上贴了一个@EnableAsync注解
点击进去分析:
一文彻底讲透@Async注解的原理和使用方法_第3张图片

根据追踪,我们发现,最终会往容器中注入 AsyncAnnotationBeanPostProcessor,
分析其继承体体系,发现其实现了BeanFactoryAware接口,实现该接口的类,spring容器在创建该bean时,会回调:void setBeanFactory(BeanFactory beanFactory) throws BeansException;
一文彻底讲透@Async注解的原理和使用方法_第4张图片

接着我们看看:
一文彻底讲透@Async注解的原理和使用方法_第5张图片
我们之后看看AsyncAnnotationAdvisor的创建过程:
一文彻底讲透@Async注解的原理和使用方法_第6张图片
接着看this.advice = buildAdvice(executor, exceptionHandler);
一文彻底讲透@Async注解的原理和使用方法_第7张图片
至此,我们终于找到方法拦截器了,为何是它,看看它的继承体系:
一文彻底讲透@Async注解的原理和使用方法_第8张图片
3.验证:既然找到了方法拦截器,那么我们就打断点在拦截方法里,执行之前的测试代码:拦截方法在它的父类中:AsyncExecutionInterceptor
一文彻底讲透@Async注解的原理和使用方法_第9张图片
一文彻底讲透@Async注解的原理和使用方法_第10张图片
原理非常简单:1.获取线程池 2.创建callable 3.执行线程
重点是线程池的获取逻辑:

一文彻底讲透@Async注解的原理和使用方法_第11张图片
由上所得:线程池会首先通过用户自己配置的为准:
一文彻底讲透@Async注解的原理和使用方法_第12张图片
所以,我们可以自定义线程池,然后注入spring中,将bean的名字放到@Async注解的value值即可
一文彻底讲透@Async注解的原理和使用方法_第13张图片
最后我们看看默认的线程池是怎么获取的:
targetExecutor = this.defaultExecutor.get();
一文彻底讲透@Async注解的原理和使用方法_第14张图片
可以知道,他是通过调用getDefaultExecutor(this.beanFactory)
一文彻底讲透@Async注解的原理和使用方法_第15张图片
由此可见,会先获取容器中TaskExecutor的线程池,获取不到,就获取指定beanName的线程池DEFAULT_TASK_EXECUTOR_BEAN_NAME = “taskExecutor”;

我们看看默认的线程池
一文彻底讲透@Async注解的原理和使用方法_第16张图片
总结:@Async的异步线程池获取顺序:
一文彻底讲透@Async注解的原理和使用方法_第17张图片
三:学以致用
一文彻底讲透@Async注解的原理和使用方法_第18张图片
直接创建2个异步方法:
一文彻底讲透@Async注解的原理和使用方法_第19张图片
四:思考:如果一个类中,非异步方法调用了异步的方法,异步方法还会生效么:

@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());执行

然而结果:
一文彻底讲透@Async注解的原理和使用方法_第20张图片
由此看到并不是同步执行,原因如下:
service.async2(); 方法调用:service是代理类,上图打印的第一行可以知道,它是cglib代理,因此该方法本质调用的是代理类的aync2(),而该方法并没有@aync注解也就没有方法拦截器,因此代理类执行async2()方法,本质最终是直接调用了被代理类的方法,所以上图打印出的第二行可以知道:

this.getClass()==com.yang.xiao.hui.aop.MyAsync
一文彻底讲透@Async注解的原理和使用方法_第21张图片
由此可以知道,只要是代理类调用async1()方法,就可以让异步调用生效了,如何获取被代理类,既然asyn2()是代理类调用的,肯定会被拦截,我们debug进入
一文彻底讲透@Async注解的原理和使用方法_第22张图片
当this.advised.exposeProxy=true时,代理类设置到AopContext中
一文彻底讲透@Async注解的原理和使用方法_第23张图片
原来是设置到ThreadLocal中,那成立的条件又是啥
一文彻底讲透@Async注解的原理和使用方法_第24张图片

该值其实就是@EnableAspectJAutoProxy(exposeProxy = true)的值
一文彻底讲透@Async注解的原理和使用方法_第25张图片
由此,改进我们的调用方法,通过ThreadLocal获取代理类,然后调用目标方法
一文彻底讲透@Async注解的原理和使用方法_第26张图片
启动测试:
一文彻底讲透@Async注解的原理和使用方法_第27张图片
居然报错了。。。。。,这么说,@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)并没有生效,那么我们看看该注解的配置原理是啥?

一文彻底讲透@Async注解的原理和使用方法_第28张图片
一文彻底讲透@Async注解的原理和使用方法_第29张图片
由此发现,它指对自动创建的的aop创建器生效,到底有哪些呢?
一文彻底讲透@Async注解的原理和使用方法_第30张图片
事务注解相关的就可以,但我们的是异步注解,根据之前源码分析知道,异步是通过AsyncAnnotationBeanPostProcessor来实现的;

一文彻底讲透@Async注解的原理和使用方法_第31张图片
由此,我们是否可以通过获取AsyncAnnotationBeanPostProcessor的beanDefiniton然后给它新增属性,类型下面的实现:

一文彻底讲透@Async注解的原理和使用方法_第32张图片
具体改造如下:

@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)可以不用了,再次测试:
一文彻底讲透@Async注解的原理和使用方法_第33张图片

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

你可能感兴趣的:(java)