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

相关推荐
JH30739 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_124987075312 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_13 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_8187320613 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
汤姆yu16 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶16 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip17 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
JavaGuide18 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot
figo10tf18 小时前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
zhangyi_viva18 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端