深入理解Spring系列之十五:@Async实现原理

对于异步方法调用,从Spring3开始提供了@Async注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。

遇到开发人员只会简单的使用@Async注解,而不知其实现原理,更糟糕的是有时会错误的使用。本篇将深入源码分析@Async注解背后的实现原理,避免错误使用。

本文关键词:异步、线程池、代理

@Async

以下是@Async注解的源码,从源码中看到它可以被标注在类或方法上,用于实现方法的异步执行。当被标注在类上时,表明类中的所有方法都被指定的异步执行器执行。

640?wx_fmt=png

实现原理

为了更基础的分析异步调用背后的实现原理,这里选择使用xml配置文件的方式。使用xml配置文件方式时,一般会配置如下元素:

<task:annotation-driven executor="myExecutor" exception-handler="exceptionHandler"/>	
<task:executor id="myExecutor" pool-size="5"/>	
<bean id="exceptionHandler" class="com.abc.MyAsynExceptionHandler"/>

请记住上面的三个配置,下文将围绕这三个配置进行深入分析。

这里就从task标签解析开始。一般对于这种标签的解析都会有相应的NamespaceHandler,根据Spring命名的套路查找TaskNamespaceHandler类,具体代码如下:

640?wx_fmt=png

图中标出了task标签下annotation-driven和executor元素的处理类。

先分析一下executor元素,其对应的处理类是ExecutorBeanDefinitionParser,下图是这个类的继承关系图。

640?wx_fmt=png

根据前面几篇篇幅可以知道,这个类用于解析和定义单个BeanDefinition,从parse方法追踪具体的实现逻辑,发现这个类主要是根据executor元素中的配置,例如pool-size创建一个TaskExecutorFactoryBean对象,而在TaskExecutorFactoryBean中间接使用ThreadPoolExecutor创建了一个线程池,这个线程池会在annotation-driven元素解析类中用到。640?wx_fmt=png

接着分析annotation-driven元素处理类AnnotationDrivenBeanDefinitionParser,其实现了BeanDefinitionParser,关系图如下:

640?wx_fmt=png

这个类的parse方法体内容比较多,这个只关注重点代码,具体代码如下:

640?wx_fmt=png

默认情况下,如果没有配置mode属性,其值默认是proxy,继续执行会创建一个AsyncAnnotationBeanPostProcessor,然后解析executor属性值和exception-handler属性值并将其设置到AsyncAnnotationBeanPostProcessor中。

AsyncAnnotationBeanPostProcessor类关系图如下所示:

640?wx_fmt=png

从图中可以看到,AsyncAnnotationBeanPostProcessor间接实现了BeanFactoryAware接口,所以它在被实例化的时候会执行setBeanFactory方法,查看这个方法的源码如下:

640?wx_fmt=png

重点关注红框中的代码,这里使用上面提到的executor元素解析得到的线程池和异常处理创建通知,使用@Async注解创建切入点,具体代码如下:

640?wx_fmt=png

进入构建通知的方法buildAdvice,

640?wx_fmt=png

查看AnnotationAsyncExecutionInterceptor源码,可以发现它继承和实现接口关系图如下:

640?wx_fmt=png

可以看到AnnotationAsyncExecutionInterceptor间接实现了MethodInterceptor接口,而MethodInterceptor是AOP中切入点的处理器,处理器中最终被调用的是invoke方法,下面是invoke方法的源码:

640?wx_fmt=png

doSubmit方法源码如下,可以看到真正执行的地方是提交给线程池执行的,实现了异步执行。

640?wx_fmt=png

上面的分析得到了@Async注解的切入点、切入点的处理器,必然会根据切入点创建代理,才能最终执行到MethodInterceptor的invoke方法,实现异步执行。继续分析,AsyncAnnotationBeanPostProcessor类间接实现了BeanPostProcessor接口,也就是说在bean初始化之前和之后会分别执行postProcessBeforeInitialization方法和postProcessAfterInitialization方法,而AsyncAnnotationBeanPostProcessor类的这两个方法是从AbstractAdvisingBeanPostProcessor类中继承来的,这里重点分析postProcessAfterInitialization方法,具体代码如下:

640?wx_fmt=png

创建代理可以使用Cglib或JDK动态代理,具体代码如下:

640?wx_fmt=png

这里选择JdkDynamicAopProxy深入分析,代理的创建这里就不深入分析了,这里重点关注一下调用代理时真正执行的invoke方法,方法体内容比较多,这里看一下如下重要代码:

640?wx_fmt=png

640?wx_fmt=png

总结

Spring容器启动初始化bean时,判断类中是否使用了@Async注解,创建切入点和切入点处理器,根据切入点创建代理,在调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池,实现异步执行。

所以,需要注意的一个错误用法是,如果A类的a方法(没有标注@Async)调用它自己的b方法(标注@Async)是不会异步执行的,因为从a方法进入调用的都是它本身,不会进入代理。 

往期精彩内容


觉得有收获,诚邀关注、点赞、转发

640?wx_fmt=png

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值