
本文介绍如何通过 aspectj 的 `get()` 和 `set()` 切点表达式,精准拦截被自定义注解(如 `@monitor`)标记的类成员变量的读写操作,并提供可运行的完整示例与关键注意事项。
在面向切面编程中,若希望对某个字段的所有读写访问(无论发生在 getter/setter 内部、普通方法中,还是直接访问)进行统一监控,仅靠拦截 getter/setter 方法执行是不够的——因为 Java 字节码层面的字段访问(getfield/putfield)并不经过方法调用。此时,AspectJ 提供了更底层的 get() 和 set() 切点(pointcut),可直接匹配字段访问行为,且支持结合注解进行条件筛选。
✅ 正确的切点写法
要拦截被 @Monitor 注解修饰的任意字段的读写操作,应使用如下 AspectJ 切点语法:
@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 注解修饰的字段的读取操作(即 getfield 字节码指令);
- set(@Monitor * *):匹配所有被 @Monitor 注解修饰的字段的写入操作(即 putfield 指令);
- * * 分别代表任意类型和任意名称的字段,等价于 @Monitor @interfaceType fieldName 的通配。
⚠️ 注意:@Around("annotation(Monitor) && ...") 是错误思路——annotation() 切点仅适用于连接点本身带有该注解(如方法、类、参数),而字段读写操作(get/set)本身不是注解目标,字段才是。因此必须使用 get(@Monitor ...) 这种“目标字段带注解”的语义。
? 完整可运行示例
首先定义注解(需 RUNTIME 保留策略):
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {}目标类(字段直接标注,无需 getter/setter 也生效):
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();
p.setX(11); // → 触发 set(int Point.x)
System.out.println(p.getX()); // → 触发 get(int Point.x)
p.x = 22; // → 直接触发 set(int Point.x)
System.out.println(p.x); // → 直接触发 get(int Point.x)
}
}切面实现(注意:需启用 AspectJ 编译时织入或加载时织入):
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();
}
}运行输出将清晰显示四次拦截(两次通过方法、两次直接访问):
[WRITE] set(int Point.x) [READ] get(int Point.x) 11 [WRITE] set(int Point.x) [READ] get(int Point.x) 22
? 补充说明与注意事项
- 织入方式要求:get()/set() 切点属于 field-access join points,仅支持 编译时织入(ajc) 或 加载时织入(LTW),标准 Spring AOP(基于代理)无法支持,因其不操作字节码层面的字段访问。
- 性能影响:字段级切点会增加所有匹配字段访问的开销,生产环境建议谨慎评估粒度,必要时配合 if() 切点条件过滤。
- 访问控制无关:即使字段是 private,AspectJ 仍可拦截其读写(依赖字节码增强),无需 AccessibleObject.setAccessible(true)。
- 扩展建议:如需区分 getter/setter 调用上下文,可结合 jp.getEnclosingStaticPart() 获取调用方方法签名(见原答案中 EnclosingStaticPart 示例);如需获取字段值,jp.getArgs() 对 set() 有效(含新值),jp.proceed() 后对 get() 可捕获返回值。
掌握 get(@Annotation ...) 与 set(@Annotation ...) 是解锁 AspectJ 字段级横切能力的关键——它让监控真正下沉到数据访问本质,而非停留于方法契约表面。










