• 一文彻底讲透@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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    上述代码提供了最简单的异步使用方式,如果是同步执行,那么控制台打印的顺序应该是:
    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("目标方法执行完没.........");
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    如果异步生效,那么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获取代理类,然后调用目标方法
    在这里插入图片描述
    启动测试:
    在这里插入图片描述
    居然报错了。。。。。,这么说,@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 {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    此时启动类的@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)可以不用了,再次测试:
    在这里插入图片描述

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

  • 相关阅读:
    Xrdp+Cpolar实现远程访问Linux Kali桌面
    并发编程 | 对比Object类、ReentrantLock.newCondition、LockSupport类提供的等待唤醒机制
    springboot http添加请求头 添加请求证书
    Revit SDK:StairsAutomation
    51单片机智能电风扇控制系统proteus仿真设计( 仿真+程序+原理图+报告+讲解视频)
    第2周学习:卷积神经网络基础
    矩阵分析与应用+张贤达
    Delphi 高精度计时
    R语言奇异值分解
    命令执行漏洞——系统命令执行
  • 原文地址:https://blog.csdn.net/ym15229994318ym/article/details/125996147