如何使用 AspectJ 实现对带自定义注解字段的读写操作进行切面拦截

本文详解如何通过 aspectj 的 `get()` 和 `set()` 切点表达式,结合自定义注解(如 `@monitor`),精准拦截被标注字段的直接读写访问,并提供可运行的完整示例与关键注意事项。

在基于 Spring 或纯 AspectJ 的 AOP 开发中,开发者常希望对特定字段的访问行为(如读取或赋值)进行统一监控、日志记录或权限校验。但需注意:Java 字节码层面不为字段访问生成独立方法,因此无法像拦截 getter/setter 那样用 execution() 直接匹配——必须使用 AspectJ 特有的 get() 与 set() 切点指示符

✅ 正确做法:使用 get(@Annotation Type Field) 和 set(@Annotation Type Field)

以下是一个最小可验证示例(MCVE),展示如何实现对 @Monitor 标注字段的读写拦截:

1. 定义自定义注解

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {}

2. 标注目标字段的类

public class Point {
    @Monitor
    private int x;

    public int getX() { return x; }
    public void setX(int v) { x = v; }

    public static void main(String[] args) {
        Point p = new Point();
        // ① 通过 getter/setter 间接访问 → 触发切点(因内部含字段读写字节码)
        p.setX(11);
        System.out.println(p.getX());

        // ② 直接字段访问(包内/反射等)→ 同样触发切点
        p.x = 22;
        System.out.println(p.x);
    }
}

3. 编写切面逻辑(关键!)

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MonitorAspect {

    @Around("get(@Monitor * *)")
    public Object onFieldRead(ProceedingJoinPoint jp) throws Throwable {
        System.out.printf("[READ]  %s%n", jp.getSignature());
        return jp.proceed();
    }

    @Around("set(@Monitor * *)")
    public Object onFieldWrite(ProceedingJoinPoint jp) throws Throwable {
        System.out.printf("[WRITE] %s%n", jp.getSignature());
        return jp.proceed();
    }
}
? 切点语法说明: get(@Monitor * *):匹配所有被 @Monitor 注解的任意类型、任意名称字段的 读取操作(即 field 读指令); set(@Monitor * *):匹配所有被 @Monitor 注解的字段的 写入操作(即 putfield/putstatic 指令)。 * * 分别代表“任意类型”和“任意字段名”,支持通配。

? 运行输出示例:

[WRITE] set(int Point.x)
[READ]  get(int Point.x)
11
[WRITE] set(int Point.x)
[READ]  get(int Point.x)
22

可见:无论通过 getter/setter 还是直接字段赋值,只要字节码中出现对应字段的 get/set 指令,均会被捕获。

⚠️ 重要注意事项

  • 不拦截 getter/setter 方法本身:此方案拦截的是字段访问动作,而非 getX() 或 setX() 方法执行。若需拦截方法调用,请改用 execution(* *(..)) 并结合 @annotation + 方法签名匹配。
  • 需启用编译时织入(CTW)或加载时织入(LTW):get()/set() 切点属于 AspectJ 特有语义,标准 Spring AOP(基于代理)不支持。必须使用 ajc 编译器或配置 LTW。
  • 字段必须具有运行时保留策略(@Retention(RUNTIME)),否则织入时无法识别注解。
  • 访问控制不影响织入:即使字段是 private,AspectJ 仍可在字节码层拦截其读写(无需 AccessibleObject.setAccessible(true))。

✅ 总结

通过 @A

round("get(@Monitor * *)") 和 @Around("set(@Monitor * *)"),你可以优雅地实现对标注字段的底层读写行为进行横切处理。该方案轻量、精准,适用于审计日志、敏感字段访问控制、性能统计等场景。务必确保项目已正确集成 AspectJ 工具链,并理解其与 Spring AOP 的能力边界。