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 方法,正常打印出日志了。

相关推荐
ahauedu2 小时前
Spring Boot 2.7+ 中 RedisConnectionFactory Autowire 警告的深度解析
java·spring boot·后端
摇滚侠3 小时前
Spring Boot 3零基础教程,深度理解 Spring Boot 自动配置原理,笔记11
spring boot·笔记·后端
爆更小哇3 小时前
统一功能处理
java·spring boot
hweiyu007 小时前
Spring Boot 项目集成 Gradle:构建、测试、打包全流程教程
java·spring boot·后端·gradle
一勺菠萝丶7 小时前
Spring Boot 项目启动报错:`Could not resolve type id ... no such class found` 终极解决方案!
java·spring boot·后端
摇滚侠11 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯13 小时前
Spring Boot测试框架详解
java·spring boot·后端
程序员小凯15 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
程序媛徐师姐16 小时前
Java基于SpringBoot的茶叶商城系统,附源码+文档说明
java·spring boot·java springboot·茶叶商城系统·java茶叶商城系统·茶叶·java茶叶商城
计算机毕业设计木哥18 小时前
计算机毕业设计选题推荐:基于SpringBoot和Vue的快递物流仓库管理系统【源码+文档+调试】
java·vue.js·spring boot·后端·课程设计