文章目录
- 起因
- @Async作用
- 原理
- @EnableAsync
- AsyncAnnotationBeanPostProcessor
起因
作为一个菜鸟,总是会遇到各种匪夷所思的bug。今天,不出意外的话今天我又遇到了意外…bug…
我在调用同事的一个方法时,莫名奇妙的报了空指针,当前请求执行了一部分后,上下文中的用户信息居然丢失了!?
可是我明明是登录状态,怎么会?于是我进入同事写的方法,看到了一个注解:@Async
,这个没见过,所以我推测问题就出自这里。看来又该学习了。
@Async作用
其注释如下:
Annotation that marks a method as a candidate for asynchronous execution. Can also be used at the type level, in which case all of the type’s methods are considered as asynchronous. Note, however, that @Async is not supported on methods declared within a @Configuration class.
翻译:
将方法标记为异步执行的候选方法的注释。也可以在类型级别使用,在这种情况下,类型的所有方法都被视为异步的。但是,请注意,在@Configuration类中声明的方法上不支持@Async。
看的出来,这个注解可以用在类上,也可用在方法上。目的就是为了让调用的方法异步执行。
原理
注:以下对springboot的Bean加载过程有一定了解的话更容易理解。
@EnableAsync
在启动类上有@EnableAsync
注解,很明显是异步注解的开关注解。进入该注解:
这里有两个注意的位置:
- @Import(AsyncConfigurationSelector.class)
- AdviceMode mode() default AdviceMode.PROXY
这里导入了类 AsyncConfigurationSelector
,进入该类:
这里的方法selectImports()
在父类中调用(注:selectImports(AnnotationMetadata importingClassMetadata)
是在springboot启动过程中执行bean后置处理器时所最终调用):
可以看到这里获取了注解@EnableAsync
中的mode
属性,默认是PROXY
。因此在方法selectImports()
中返回了ProxyAsyncConfiguration
并注入容器中:
进入配置类ProxyAsyncConfiguration
:
这里配置了线程池以及异常处理器。
AsyncAnnotationBeanPostProcessor
对于BeanPostProcessor
相信大家不会太陌生,按照我粗浅的理解,BeanPostProcessor是Spring框架中的一个接口,在SpingBoot启动过程中,用于在Bean实例化和初始化的过程中对Bean进行后置处理。它提供了两个方法:postProcessBeforeInitialization和postProcessAfterInitialization。
postProcessBeforeInitialization方法在Bean的初始化之前被调用,可以对Bean进行一些预处理操作。例如,可以修改Bean的属性值或者进行一些必要的校验。
postProcessAfterInitialization方法在Bean的初始化之后被调用,可以对Bean进行一些后处理操作。例如,可以对Bean进行代理或者添加一些额外的功能。
继续回到类AsyncAnnotationBeanPostProcessor
,在其最下方的setBeanFactory
方法中,创建了一个切面对象并赋给AsyncAnnotationBeanPostProcessor
对象。
进入类AsyncAnnotationAdvisor
中,并查看构造方法,发现它专为注解Async
构造了切面(advisor:通知器)。
分别进入通知器的通知(advice,表示实际增强的逻辑入口)构造方法与切点(pointcut,表示哪些类或者哪些方法需要被拦截)构造方法一探究竟。
在AnnotationAsyncExecutionInterceptor
的invoke
方法中,定义了我们的方法异步执行的逻辑:
上面查看了类AsyncAnnotationAdvisor
中方法buildAdvice
,继续进入方法buildPointcut
看看:
这里就是把注解Async
定义到了切点。
那么问题来了,Spring是在什么时候根据什么来生成了具有以上切面的代理对象?
我们再次回到类AbstractAdvisingBeanPostProcessor
中,在方法postProcessAfterInitialization
中有这样一部分代码:
这里有个重要的方法isEligible
,将代码注释翻译过来:检查给定的类是否有资格向该后处理程序的通知器提供通知。
当重复判断的时候会直接返回,红框内的canApply
会判断切面是否应该应用到当前类,其逻辑如下:
在这里会获取目标类的所有方法,并逐个匹配,如果有方法具有Async
注解,则会返回true
,即可以构造对应的代理对象。
因此,当我们调用相关类的某个被Async
注解标注的方法时,是调用的具有切面定义的代理对象,并异步执行该方法。