Spring AOP 拦截元注解

什么是元注解(meta annotation)

元注解是可以注解到注解上的注解,它的作用和目的就是给其他普通的标签进行解释说明的。

Java 默认的元注解包含:

  • @Retention
  • @Target
  • @Document
  • @Inherit
  • @Repeatable

什么是组合注解(composed annotation)

我们定义一个注解 @OperationLog,当类或者方法上添加了 @OperationLog 注解时,我们将通过 Aspect 打印方法调用的日志。

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {

}

假设,我们经常需要给 Controller 的 handler 方法同时加 @RequestMapping 和 @OperationLog 注解。

那么,我们可以定义一个新注解 @Operation,在 @Operation 上添加 @RequestMapping 和 @OperationLog 作为的元注解,这样就可以只添加一个 @Operation 注解代替原来的两个注解了,这种方式就叫组合注解。

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping
@OperationLog
public @interface Operation {

    @AliasFor(annotation = RequestMapping.class)
    String[] value() default {};

    @AliasFor(annotation = RequestMapping.class)
    String[] path() default {};
}

AOP 如何拦截指定注解

假设,我们有一个 Controller 类如下

java 复制代码
@RestController
public class TestController {

    @RequestMapping("/test")
    @OperationLog
    public String test() {
        System.out.println("on test");
        return "test message";
    }
}

定义一个 OperationLogAspect 类用于拦截带有 @OperationLog 的类和方法

java 复制代码
@Component
@Aspect
public class OperationLogAspect {

    @Pointcut("@within(OperationLog) || @annotation(OperationLog)")
    public void log() {

    }

    @Before("log()")
    public void beforeLog(JoinPoint joinPoint) {
        System.out.println("execute operation: " + joinPoint.getSignature());
    }
}

当我们访问 /test 的时候,就会被拦截并进入 beforeLog 方法,打印 operation 日志如下:

log 复制代码
execute operation: void cn.forhot2000.TestController.test()
on test

AOP 如何拦截注解中包含指定元注解

那么,如果我们使用组合注解 @Operation 会这么样呢?

修改 TestController 的注解

java 复制代码
@RestController
public class TestController {

    @Operation("/test")
    public String test() {
        System.out.println("on test");
        return "test message";
    }
}

现在,我们访问 /test 的时候,发现并没有拦截并进入 beforeLog 方法。

因为 @within 和 @annotation 都是按注解类的类名去匹配的,并不能主动地去匹配注解的元注解。

所幸还有替代方案,使用 within(@(@OperationLog *) *) 来匹配类注解的元注解,使用 execution(@(OperationLog *) * *(..)) 来匹配方法注解的元注解,修改下 OperationLogAspect 类:

java 复制代码
@Component
@Aspect
public class OperationLogAspect {

    @Pointcut("within(@OperationLog *) || " +
            "within(@(OperationLog *) *) || " +
            "execution(@OperationLog * *(..)) || " +
            "execution(@(OperationLog *) * *(..))")
    public void log() {

    }

    @Before("log()")
    public void beforeLog(JoinPoint joinPoint) {
        System.out.println("execute operation: " + joinPoint.getSignature());
    }
}

上面的代码中 Pointcut 表达式说明:

  • within(@OperationLog *) 匹配带有 @OperationLog 注解的类,相当于 @within(OperationLog)
  • within(@(OperationLog *) *) 匹配注解中带有 @OperationLog 元注解的类
  • execution(@OperationLog * *(..)) 匹配带有 @OperationLog 注解的方法,相当于 @annotation(OperationLog)
  • execution(@(OperationLog *) * *(..)) 匹配注解中带有 @OperationLog 元注解的方法

现在,我们再访问 /test 的时候,发现又可以拦截进入 beforeLog 方法,正常打印出日志了。

相关推荐
wuxuanok13 分钟前
SpringBoot -原理篇
java·spring boot·spring
云动雨颤17 分钟前
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
数据库·spring boot·tomcat
二饭18 分钟前
Spring Boot 项目启动报错:MongoSocketOpenException 连接被拒绝排查日记
java·spring boot·后端
xkroy3 小时前
用户登录
spring boot
lssjzmn3 小时前
基于Spring Boot与Micrometer的系统参数监控指南
java·spring boot·数据可视化
BXCQ_xuan3 小时前
软件工程实践四:MyBatis-Plus 教程(连接、分页、查询)
spring boot·mysql·json·mybatis
小枫编程3 小时前
Spring Boot 与前端文件上传跨域问题:Multipart、CORS 与网关配置
前端·spring boot·后端
一又四分之一.4 小时前
spring、springboot、springCloud
spring boot·spring·spring cloud
小枫编程5 小时前
Spring Boot 调度任务在分布式环境下的坑:任务重复执行与一致性保证
spring boot·分布式·后端
Java水解5 小时前
spring中的@SpringBootTest注解详解
spring boot·后端