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

相关推荐
Zhou1411366 小时前
SpringBoot_03_Web开发
前端·spring boot·python
Zhou1411367 小时前
SpringBoot_02_自动配置原理
java·spring boot·后端
FYKJ_20107 小时前
SSM校园失物招领系统41452-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·typescript·spark
卓怡学长8 小时前
w198基于springboot始于足下健康打卡平台的设计与实现
java·spring boot·spring·intellij-idea
一条小小yu9 小时前
为什么使用springboot
java·spring boot·后端
今年下半年12 小时前
【springboot】对接海康威视视频监控技术文档
spring boot·视频监控·安防·海康
小蒜学长13 小时前
基于SpringBoot+Vue的小学数学智能出题系统(代码+数据库+LW)
java·数据库·spring boot·后端·智能出题系统
FYKJ_201014 小时前
springboot贺州地方新闻网站87812-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·django·课程设计
MZ_ZXD00115 小时前
【关注可白嫖源码】--课程设计--毕业设计-- 42557基于springboot的大学生体质健康管理系统
spring boot·课程设计·毕设·大作业·程序开发·程序定制·毕设代做
w***488216 小时前
SpringBoot整合easy-es
spring boot·后端·elasticsearch