使用SpringBoot框架的开发的小伙伴,通常都知道SpringBoot提供了一个非常实用的注解功能@Async
用来做轻量级的异步变成。例如如下的一个最小用例代码:
@EnableAsync
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Async
public void async() {
System.out.println("123456");
}
}
相信很多小伙伴到了这一步就停住了,内部深层次的实现都交给SpringBoot了。平时大家做项目都很忙,直接拿来用实现了就行了。不过,程序员的最基本的修养就是刨根问到底,底层实现的细节不清楚,写代码的时候心里是不是慌的很?那么我们就来扒一扒,到底SpringBoot做了些什么,能让如此简单的代码就能实现一个定时任务功能。
注解 @EnableAsync
用了SpringBoot久了,很多小伙伴会发现有这么一些有注解非常有规律,比如
@EnableAsync
@EnableCaching
@EnableTransactionManagement
这几个注解的作用都是开启XXX功能,命令也是清一色的@EnableXXX
。这其实是SpringBoot一种约定俗成的规范。光看一个注解看不出什么东西,我们跟踪进注解的定义代码看一看:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
}
可以看到除了元注解之外,还多了一个@Import(AsyncConfigurationSelector.class)
注解,奥秘就在这里,@Import
是SpringBoot框架提供的一个注解,具体的作用就是实例化某个类成一个Bean,并且纳入SpringBoot的IOC容器,这其实提供了一个格外的加载Bean入口。
我们点击AsyncConfigurationSelector
这个类,这个类不复杂,我们看到这个类继承AdviceModeImportSelector<EnableAsync>
并且覆盖了一个方法,从这个方法的返回值我们可以大概猜出,似乎是向SpringBoot注册一份配置类。并且从这个类中,我们看到了一些似乎非常熟悉的字眼:PROXY,ASPECTJ,Advice。小伙子,是不是想到了自己似懂非懂,接触过却又拎不清的概念:代理,切面?
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
@Override
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
AOP,代理
AOP和代理的概念其实网上已经很多文章解释了。通俗的讲,Java中的继承,是一种纵向扩展的机制,我们可以一层层的抽象,直到定义具体的类。而切面编程,更多是一种横向扩展机制。比如我们需要在某些方法中插入一些特定的代码(日志,监控参数等),不需要去修改继承链,也不需要去每一个方法添加(静态代理)。只需要定义好:
- 切面(Aspect)
- 切点(PointCut)
- 通知(Advice)
再运用SpringBoot提供的AOP工具,我们就能很方便的动态代理目标方法或类,实现特定的功能。比如,我们有一个简单的代码:
@Slf4j
@Aspect
@Component
public class OurAspect {
private final String POINT_CUT = "execution(public * zsh.demos.PATH.CLASS.*.*(..))";
//定义切点
@Pointcut(POINT_CUT)
public void ourPointCut() {
}
@Before(value = "ourPointCut()")
public void before(JoinPoint joinPoint){
log.info("@Before通知执行");
//获取目标方法参数信息
Object[] args = joinPoint.getArgs();
//aop代理对象
Object aThis = joinPoint.getThis();
log.info(aThis.toString());
//被代理对象
Object target = joinPoint.getTarget();
log.info(target.toString());
//获取连接点的方法签名对象
Signature signature = joinPoint.getSignature();
log.info(signature.toLongString());
log.info(signature.toShortString());
log.info(signature.toString());
//获取方法名
log.info(signature.getName());
//获取声明类型名
log.info(signature.getDeclaringTypeName());
//获取声明类型 方法所在类的class对象
log.info(signature.getDeclaringType().toString());
//和getDeclaringTypeName()一样
log.info(signature.getDeclaringType().getName());
//连接点类型
String kind = joinPoint.getKind();
log.info(kind);
//返回连接点方法所在类文件中的位置 打印报异常
SourceLocation sourceLocation = joinPoint.getSourceLocation();
///返回连接点静态部分
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
log.info(staticPart.toLongString());
log.info("before通知执行结束");
}
/**
* 后置返回
* 如果第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
* 参数为Object类型将匹配任何目标返回值
*/
@AfterReturning(value = POINT_CUT,returning = "result")
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object result){
log.info("第一个后置返回通知的返回值:"+result);
}
@AfterReturning(value = POINT_CUT,returning = "result",argNames = "result")
public void doAfterReturningAdvice2(String result){
log.info("第二个后置返回通知的返回值:"+result);
}
/**
* 后置异常通知
* 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
* throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = POINT_CUT,throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
log.info(joinPoint.getSignature().getName());
if(exception instanceof NullPointerException){
log.info("发生了空指针异常!!!!!");
}
}
@After(value = POINT_CUT)
public void doAfterAdvice(JoinPoint joinPoint){
log.info("后置通知执行了!");
}
/**
* 环绕通知:
* 注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用
*
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
log.info("@Around环绕通知:"+proceedingJoinPoint.getSignature().toString());
Object obj = null;
try {
obj = proceedingJoinPoint.proceed(); //可以加参数
log.info(obj.toString());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
log.info("@Around环绕通知执行结束");
return obj;
}
}
代码部分不解释了。我们需要了解的是,SpringBoot对切面的处理方式是使用动态代理去实现的,Java中有两种动态代理方式:JDK和Cglib。SpringBoot则默认使用的是Cglib方式去实现的动态代理。
@EnableAsync的实现方式
说了一下SpringBoot提供的AOP框架去实现切面之后,我们再去看看@Async是怎么实现的。通过selectImports方法我们得知SpringBoot会去加载这个配置类ProxyAsyncConfiguration
,而ProxyAsyncConfiguration
又会导出一个Async注解处理BeanAsyncAnnotationBeanPostProcessor
。而在这个Bean中,实现了一个重要的方法,在setBeanFactory中将advisor进行了注册。
public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
@Override
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType);
}
advisor.setBeanFactory(beanFactory);
this.advisor = advisor;
}
}
Adivsor是什么东西?Advisor其实就是切面的一个封装,包含了Advice,PointCut,所以,这个AsyncAnnotationAdvisor
中提供了Async的Advice和PointCut。而最终,被注册到SpringBoot的AOP中。生成Cglib代码。
下一步,我们将仿照Async的实现方式,不用@Aspect而采用自定义注解,来实现一个记录方法执行耗时功能。
网友评论