Springboot:aop注解实现

概念

Aop原指面向切面编程。但在spring中对aop的实现指的是:

对方法执行前后加入其它逻辑代码,达到增强方法的目的

spring-aop的底层实现

一般实现aop,有两种方案:

  1. JDK动态代理
  2. cglib代理

spring中两者都可使用,默认使用JDK动态代理。具体如何实现不作讨论,本文仅讲如何使用

spring-aop基本术语

  1. 连接点(JoinPoint):能被增强(通知)的方法,指具有被增强的资格。
  2. 切入点(CutPoint):实际被增强的方法
  3. 通知(Advice):增强的代码

通知有5种通知:

  1. 前置通知(Before):被增强方法执行之前执行
  2. 后置通知(AfterReturning):被增强方法执行之后执行,出现异常就不执行了。
  3. 环绕通知(Around):方法执行前后都执行
  4. 异常通知(AfterThrowing):方法执行出现异常时执行
  5. 最终通知(After):被增强方法执行之后执行,不管是否有异常,都会执行。类似finally
  1. 切面(Aspect):切入点+通知,指通知织入到切入点的过程

切入点表达式:execution

切入点就是要增强的方法,因此需要一个类似正则那样的表达式来描述哪些方法要被增强,他就是execution表达式。语法如下:

execution([方法权限修饰符] [方法返回值] [类全路径][方法名称] ([参数列表]))

举例:

  1. 对 com.xx.User 的 add方法进行增强。

execution(* com.xx.User.add(..))

  1. 对 com.xx.User 的 所有方法进行增强。

execution(* com.xx.User.*(..))

  1. 对 com.xx包下的所有方法进行增强。

execution(* com.xx..(..))

小知识

相信大家在某些代码上看到并没有使用execution表达式,而是这种:

@Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.Log)")

此方式表示对加了Log注解的所有方法都进行增强。

springboot注解实现aop

  1. 创建一个类作为通知类,添加@Component和@Aspect注解。
  2. 配置切入点;编写一个无参的普通方法,添加@Pointcut
  3. 编写通知方法。

通知类实现如下:

@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方法中一般都会手动调用被增强方法的,因此执行顺序如下:

  1. 环绕通知前
  2. 前置通知
  3. 被增强方法
  4. 环绕通知后
  5. 最终通知
  6. 后置通知

上面是全部通知类型都有,且正常执行的流程下。现在稍作改动,在环绕通知中去掉对切入点的调用,则打印如下:

around() before proceed
around() after proceed methodName:test2, args:[], result:null
after() methodName:test2, args:[]
AfterReturning() methodName:test2, args:[], result:null

现在顺序是:

  1. 环绕通知前
  2. 环绕通知后
  3. 最终通知
  4. 后置通知

可以看到去掉对切入点的调用,前置通知和增强方法居然不执行了,但后置通知和最终通知任然执行。那如果我们把环绕通知代码去掉,打印结果会如何呢?如下:

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

执行顺序是:

  1. 环绕通知前
  2. 前置通知
  3. 方法体(一部分)
  4. 最终通知
  5. 异常通知

可以看到,有几个变化:

  1. 方法体还是执行,但只执行了异常前部分的代码
  2. 环绕通知后和后置通知不执行了,最终通知还是执行
  3. 异常通知出现了
posted @ 2023-01-18 13:50  爱编程DE文兄  阅读(684)  评论(0编辑  收藏  举报