
本文介绍如何通过 aspectj 的 `get()` 和 `set()` 切点表达式,精准拦截被自定义注解(如 `@monitor`)标记的类成员变量的读写操作,并提供可运行的完整示例与关键注意事项。
在 Spring AOP 中,无法直接拦截字段访问(field access),因为其基于代理机制,仅支持方法调用拦截;而 AspectJ(尤其是编译时织入或加载时织入)则原生支持对字段读写(get() / set())的切点定义。这意味着你可以真正实现“只要某字段被 @Monitor 标记,无论它是在 getter/setter 中被访问,还是在任意位置被直接读写”,都能触发对应增强逻辑。
✅ 正确的切点写法
要匹配被 @Monitor 注解修饰的任意字段的读/写操作,应使用 AspectJ 特有的 get() 和 set() 切点指示符:
@Around("get(@Monitor * *)")
public Object onFieldRead(ProceedingJoinPoint jp) throws Throwable {
System.out.println("[READ] " + jp.getSignature());
return jp.proceed();
}
@Around("set(@Monitor * *)")
public Object onFieldWrite(ProceedingJoinPoint jp) throws Throwable {
System.out.println("[WRITE] " + jp.getSignature());
return jp.proceed();
}- get(@Monitor * *):匹配所有被 @Monitor 注解修饰的字段的读取操作(如 obj.x、return this.x;);
- set(@Monitor * *):匹配所有被 @Monitor 注解修饰的字段的写入操作(如 obj.x = 1、this.x = v;);
- 第一个 * 表示任意返回类型(即字段类型),第二个 * 表示任意字段名 —— 组合起来即“任意类型、任意名称、且带 @Monitor 注解的字段”。
⚠️ 注意:@annotation(Monitor) 是用于匹配被注解的方法/类型/参数,不能用于字段访问切点。字段级注解需结合 get() / set() 使用,语法为 get(@Monitor ...) 或 set(@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();
// → 间接访问:触发 setter/getter 方法执行(但字段访问仍被拦截)
p.setX(11);
System.out.println(p.getX()); // 输出 11
// → 直接访问:绕过方法,但仍被 get/set 切点捕获
p.x = 22;
System.out.println(p.x); // 输出 22
}
}3. AspectJ 切面(需启用 LTW 或 CTW)
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 onRead(ProceedingJoinPoint jp) throws Throwable {
System.out.printf("[FIELD READ] %s%n", jp.getSignature());
return jp.proceed();
}
@Around("set(@Monitor * *)")
public Object onWrite(ProceedingJoinPoint jp) throws Throwable {
System.out.printf("[FIELD WRITE] %s%n", jp.getSignature());
return jp.proceed();
}
}运行输出(清晰区分访问来源):
[FIELD WRITE] set(int Point.x) [FIELD READ] get(int Point.x) 11 [FIELD WRITE] set(int Point.x) [FIELD READ] get(int Point.x) 22
? 进阶提示与注意事项
- 织入方式要求:上述 get()/set() 切点不被 Spring AOP 支持,必须使用 AspectJ(推荐加载时织入 LTW,配合 aop.xml 和 JVM 参数 -javaagent:aspectjweaver.jar)。
- 访问可见性无关:即使字段是 private,AspectJ 仍可拦截其读写(底层通过字节码改写实现)。
- 性能影响:字段级拦截会增加运行时开销,建议仅对关键监控字段使用,并避免在高频循环中直接访问被监控字段。
- 获取上下文信息:可通过 jp.getThis() 获取目标对象、jp.getArgs()(对 set() 有效,返回新值数组)、jp.getSignature().getDeclaringType() 等进一步定制逻辑。
-
排除干扰:若只想拦截 getter/setter 内部的字段访问(而非任意位置),可组合 cflow() 切点,例如:
@Around("set(@Monitor * *) && cflow(execution(* *.get*(..)) || execution(* *.set*(..)))")
掌握 get(@Annotation ...) 和 set(@Annotation ...) 是解锁 AspectJ 字段级横切能力的关键。它让监控、审计、懒加载、属性变更通知等场景变得简洁而强大 —— 只需注解字段,无需修改业务代码。










