自定义注解实现AOP功能

116 阅读4分钟

一、介绍

1.1、大概流程:

创建切面类 -> 设置通知类型 -> 设置切入点

1.2、各种通知

  • 前置通知:使用 @Before 注解标识,在被代理的目标方法 执行
  • 返回通知:使用 @AfterReturning 注解标识,在被代理的目标方法 成功结束 后执行(寿终正寝
  • 异常通知:使用 @AfterThrowing 注解标识,在被代理的目标方法 异常结束 后执行(死于非命
  • 后置通知:使用 @After 注解标识,在被代理的目标方法 最终结束 后执行(盖棺定论
  • 环绕通知:使用 @Around 注解标识,使用try...catch...finally结构围绕 整个 被代理的目标方法,包括上面四种通知对应的所有位置

各种通知的执行顺序:

  • Spring版本5.3.x以前:

    • 前置通知
    • 目标操作
    • 后置通知
    • 返回通知或异常通知
  • Spring版本5.3.x以后:

    • 前置通知
    • 目标操作
    • 返回通知或异常通知
    • 后置通知
1.2.3、环绕通知

环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点

☆ 对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.

☆ 在环绕通知中 需要明确调用 ProceedingJoinPoint 的 proceed() 方法 来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.

☆ 注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常

1.4、切入点表达式语法

img025.png

切入点使用示例:
/**
 * 设置前置通知
 * @Before(value = "切入点表达式,用来配置切入点")
 * 切入点表达式:execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(参数列表))
 */
// 表示:访问修饰符为public 方法返回类型为int CalculatorImpl类下的所有方法 任意参数--------给这些方法设置切入点
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")


// 表示:任意的访问修饰符+任意的返回类型 annotation包中的所有类中的方法 任意参数--------给这些方法设置切入点
@Before("execution(* com.atguigu.aop.annotation.*.*(..))")

1.4.1、重用切入点表达式

①声明

@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}

②在同一个切面中使用

@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

③在不同切面中使用

@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
1.4.2、切面优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
使用@Order注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低
1.4.3、@Pointcut常用的两种方式
  • ① execution - 使用execution(方法表达式)匹配方法执行。
  • ② @annotation -
@Aspect
public class AspectTest12 {
    // 当被调用的目标方法上有@Ann12注解的时,会被beforeAdvice处理
    @Pointcut("@annotation(com.javacode2018.aop.demo9.test12.Ann12)")
    public void pc() {
    }

    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println(joinPoint);
    }
}

二、示例

1、添加依赖

<dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.2</version>
    </dependency>

    <!--spring aop依赖(☆)-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>6.0.2</version>
    </dependency>
    <!--spring aspects依赖(☆)-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.0.2</version>
    </dependency>

</dependencies>

2、创建切面类

// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
@Order(3) //指定切面的优先级. 值越小,优先级越高. 标注@Order的切面比不标注@Order切面的优先级高
public class LogAspect {
    
    @Pointcut("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public void pointcut() {
    }
    
    /**
     * 设置前置通知
     * @Before(value = "切入点表达式,用来配置切入点")
     * 切入点表达式:execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(参数列表))
     * 
     * 通知的方法中可以通过 JoinPoint 获取到切入点中的相关参数
     */
    @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        // 获取增强方法的名称
        String methodName = joinPoint.getSignature().getName();
        // 获取增强方法的参数列表
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名称:"+methodName+", 参数列表:" + args);
        // 输出-->>:Logger-->前置通知,方法名称:add, 参数列表:[2,3]
    }

    @After("pointcut()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名:"+methodName);
    }

    @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }

    @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }
    
    @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            // 调用目标方法
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
    
}