动态代理仅拦截方法调用而非字段读写,须基于接口(JDK)或非final类(CGLIB);日志与权限逻辑统一在InvocationHandler.invoke中实现;代理生效前提为对象纳入Spring容器且调用走代理链。

动态代理不能直接为“所有对象变量”加日志,它只对**方法调用过程**生效,且必须满足接口或可继承类的前提。所谓“给变量加监控”,本质是拦截对该变量所承载对象的**方法调用**,而非监听字段读写。实战中,关键不是覆盖所有变量,而是让需要监控的业务对象走代理调用链。
必须基于接口或可继承类
Java JDK 动态代理只能代理接口(如 UserService),CGLIB 代理可处理类(要求类非 final、方法非 final)。如果目标类没实现接口,又不可继承,动态代理就无法生成有效代理对象。
- 推荐统一定义业务接口,这是最稳妥、最符合 Spring AOP 和 Proxy 机制的设计方式
- 避免直接 new 实现类后套代理:比如
new LoggingProxy(new UserServiceImpl()).addUser(...)—— 这种写法根本不会触发invoke(),代理形同虚设 - 确保被代理对象的方法声明在接口里;否则代理对象无法识别该方法,调用会抛
NoSuchMethodException
日志逻辑写在 InvocationHandler 的 invoke 中
所有日志行为(前置、返回、异常)都集中在一个 invoke() 方法里,无需分散到每个业务方法中:
- 调用前记录方法名、参数:
log.info("→ {}({})", method.getName(), Arrays.toString(args)) - 调用后记录返回值:
log.info("← {} → {}", method.getName(), result) - 异常时捕获并记录,但不吞掉:
log.error("✗ {} error", method.getName(), e); throw e; - 权限校验可放最前面:
if (!hasPermission(method)) throw new AccessDeniedException(...)
代理对象必须真正参与调用链
代理只有被下游代码直接调用时才起作用。常见失效场景:
- Spring Bean 没交给容器管理(比如手动 new 出来),@Aspect 切面或 Proxy 都不会生效
- 同类内 this.method() 调用:绕过代理,日志和校验全部跳过
- 未正确配置 @ComponentScan 或 @EnableAspectJAutoProxy,导致切面未注册
- 切点表达式写错,例如漏了
..(子包)或*(..)(任意参数),导致匹配不到目标方法
推荐结合 Spring AOP 使用(更工程化)
比起手写 Proxy.newProxyInstance,Spring AOP 提供更简洁、可复用的方式:
- 写一个
@Aspect类,用@Around("execution(* com.example.service..*.*(..))")匹配所有 service 方法 - 在环绕通知里统一做日志 + 权限检查:
proceed()前校验,后记录结果,异常单独 catch - 确保 service 类由
@Service声明,并被 Spring 扫描到——这是生效前提 - 若需从同类内部调用也走切面,可用
((MyService) AopContext.currentProxy()).doXxx(),但应优先考虑拆分逻辑或提取新服务

















