
本文介绍如何通过自定义注解(如 `@monitor`)结合 aspectj 的 `get()` 和 `set()` 切点表达式,精准拦截被标注字段的读写操作,并在环绕通知中执行自定义逻辑。
在面向切面编程(AOP)实践中,有时需要对特定字段的访问行为进行统一监控或增强——例如审计赋值来源、记录访问时间、校验数据合法性等。与拦截 getter/setter 方法不同,直接拦截字段读写(field access) 能覆盖所有访问路径(包括反射、内部直接访问、甚至第三方库调用),更具底层控制力。AspectJ 提供了 get() 和 set() 这两个专用于字段访问的原生切点(primitive pointcut),配合运行时注解(@Retention(RUNTIME)),即可实现“注解即切点”的声明式 AOP。
✅ 前置准备
首先定义一个保留策略为 RUNTIME 的注解:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {}然后在目标类中对字段使用该注解:
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();
// 1. 通过 getter/setter 访问
p.setX(11);
System.out.println(p.getX());
// 2. 直接字段访问(非 public,但同一类内合法)
p.x = 22;
System.out.println(p.x);
}
}⚠️ 注意:get() 和 set() 切点仅对 编译期可见的字段访问 生效(即源码中明确出现 obj.field 或 obj.field = value)。它不适用于 Field.set() 等反射调用(需配合 call() 或 execution() + @Before("call(* java.lang.reflect.Field.set*(..))") 单独处理)。
✅ 编写 Aspect 实现字段级拦截
使用 @Around 通知分别匹配带 @Monitor 注解的字段读取与写入:
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 注解的字段的 读取操作(返回类型 *,声明类型 *);
- set(@Monitor * *):匹配任意被 @Monitor 注解的字段的 写入操作;
- jp.getSignature() 返回类似 get(int Point.x) 的字符串,清晰标识被拦截的字段及所属类。
运行上述 main 方法,输出如下:
[WRITE] set(int Point.x) [READ] get(int Point.x) 11 [WRITE] set(int Point.x) [READ] get(int Point.x) 22
可见:无论通过 setter/getter 还是直接字段赋值,只要源码中发生了对 @Monitor 字段的读写,均被成功捕获。
? 补充说明与最佳实践
- 区分 getter/setter 执行 vs 字段访问:若你只想拦截 getX()/setX() 方法本身(而非所有字段访问),应改用 execution(public int Point.getX()) 或更通用的 execution(* *(..)) && @annotation(Monitor) 配合方法参数解析,但此时需确保注解加在方法上而非字段。
- 获取字段值与上下文:在 onFieldRead 中,jp.proceed() 返回字段当前值;在 onFieldWrite 中,可通过 jp.getArgs()[0] 获取待写入的新值(set() 切点的唯一参数即新值)。
-
性能与范围控制:get()/set() 是编译期织入的底层切点,开销极小,但会匹配所有符合条件的访问。如需限定作用域(如仅拦截某包下类的字段),可增强切点表达式:
@Around("get(@Monitor * *) && within(com.example..*)") - 兼容性提示:确保项目已正确引入 AspectJ Weaver(如 Spring Boot 项目添加 spring-boot-starter-aop 并启用 @EnableAspectJAutoProxy),或使用 ajc 编译器进行编译时织入(CTW)。
通过本方案,你无需修改业务逻辑代码,仅靠注解 + Aspect 即可实现细粒度的字段访问治理,是构建可观测性、安全审计与数据合规能力的重要基础设施。










