Springboot:aop注解实现
概念
Aop原指面向切面编程。但在spring中对aop的实现指的是:
对方法执行前后加入其它逻辑代码,达到增强方法的目的
spring-aop的底层实现
一般实现aop,有两种方案:
- JDK动态代理
- cglib代理
spring中两者都可使用,默认使用JDK动态代理。具体如何实现不作讨论,本文仅讲如何使用
spring-aop基本术语
- 连接点(JoinPoint):能被增强(通知)的方法,指具有被增强的资格。
- 切入点(CutPoint):实际被增强的方法
- 通知(Advice):增强的代码
通知有5种通知:
- 前置通知(Before):被增强方法执行之前执行
- 后置通知(AfterReturning):被增强方法执行之后执行,出现异常就不执行了。
- 环绕通知(Around):方法执行前后都执行
- 异常通知(AfterThrowing):方法执行出现异常时执行
- 最终通知(After):被增强方法执行之后执行,不管是否有异常,都会执行。类似finally
- 切面(Aspect):切入点+通知,指通知织入到切入点的过程
切入点表达式:execution
切入点就是要增强的方法,因此需要一个类似正则那样的表达式来描述哪些方法要被增强,他就是execution表达式。语法如下:
execution([方法权限修饰符] [方法返回值] [类全路径][方法名称] ([参数列表]))
举例:
- 对 com.xx.User 的 add方法进行增强。
execution(* com.xx.User.add(..))
- 对 com.xx.User 的 所有方法进行增强。
execution(* com.xx.User.*(..))
- 对 com.xx包下的所有方法进行增强。
execution(* com.xx..(..))
小知识
相信大家在某些代码上看到并没有使用execution表达式,而是这种:
@Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.Log)")
此方式表示对加了Log注解的所有方法都进行增强。
springboot注解实现aop
- 创建一个类作为通知类,添加@Component和@Aspect注解。
- 配置切入点;编写一个无参的普通方法,添加@Pointcut
- 编写通知方法。
通知类实现如下:
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.ruoyi.xx.test.JobTestController.test2(..))")
public void point() { }
/**
* 前置通知
* 连接点之前执行
*/
@Before("point()")
public void before(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("before() methodName:" + methodName + ", args:" + Arrays.toString(args));
}
@Around("point()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("around() before proceed");
Object proceed = joinPoint.proceed();
System.out.println("around() after proceed methodName:" + methodName + ", args:" + Arrays.toString(args) + ", result:" + proceed);
return proceed;
}
/**
* 后置通知
* 连接点方法完成后执行
* 无论连接点执行成功与否该通知都会执行
*/
@After("point()")
public void after(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("after() methodName:" + methodName + ", args:" + Arrays.toString(args));
}
/**
* 连接点方法执行成功后执行
* 可以拿到返回结果
*/
@AfterReturning(value = "point()", returning = "result")
public void result(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("AfterReturning() methodName:" + methodName + ", args:" + Arrays.toString(args) + ", result:" + result);
}
/**
* 连接点方法执行异常后执行
* 可以拿到异常信息
*/
@AfterThrowing(value = "point()", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("afterThrowing() methodName:" + methodName + ", args:" + Arrays.toString(args) + ", exception:" + exception);
}
}
被加强方法:
public void test2() {
System.out.println("---------------------我是test2------------------------");
}
测试:
@PostMapping(value = "/test3")
@ResponseBody
public void test3() throws Exception {
JobTestController bean = SpringUtils.getBean(JobTestController.class);
bean.test2();
}
打印结果如下:
around() before proceed
before() methodName:test2, args:[]
---------------------我是test2------------------------
around() after proceed methodName:test2, args:[], result:null
after() methodName:test2, args:[]
AfterReturning() methodName:test2, args:[], result:null
注意,在around方法中一般都会手动调用被增强方法的,因此执行顺序如下:
- 环绕通知前
- 前置通知
- 被增强方法
- 环绕通知后
- 最终通知
- 后置通知
上面是全部通知类型都有,且正常执行的流程下。现在稍作改动,在环绕通知中去掉对切入点的调用,则打印如下:
around() before proceed
around() after proceed methodName:test2, args:[], result:null
after() methodName:test2, args:[]
AfterReturning() methodName:test2, args:[], result:null
现在顺序是:
- 环绕通知前
- 环绕通知后
- 最终通知
- 后置通知
可以看到去掉对切入点的调用,前置通知和增强方法居然不执行了,但后置通知和最终通知任然执行。那如果我们把环绕通知代码去掉,打印结果会如何呢?如下:
before() methodName:test2, args:[]
---------------------我是test2------------------------
after() methodName:test2, args:[]
AfterReturning() methodName:test2, args:[], result:null
可以看到,前置通知和增强方法执行了。
因此可以得到一个小结论:
如果使用了环绕通知,则必须对增强方法进行调用
最后大家可能还想看异常通知出现,那我们把通知类恢复到最刚开始的代码,然后在增强方法中制造异常,如下:
public void test2() {
System.out.println("---------------------我是test2------------------------");
throw new RuntimeException();
}
打印结果如下:
around() before proceed
before() methodName:test2, args:[]
---------------------我是test2------------------------
after() methodName:test2, args:[]
afterThrowing() methodName:test2, args:[], exception:java.lang.RuntimeException
执行顺序是:
- 环绕通知前
- 前置通知
- 方法体(一部分)
- 最终通知
- 异常通知
可以看到,有几个变化:
- 方法体还是执行,但只执行了异常前部分的代码
- 环绕通知后和后置通知不执行了,最终通知还是执行
- 异常通知出现了