JDK动态代理实现日志与耗时切面需接口先行、代理拦截、逻辑内聚:目标类必须实现接口,InvocationHandler统一处理入参、计时(System.nanoTime)、结果/异常安全打印,Proxy.newProxyInstance三参数须准确,轻量集成无需Spring。

用 JDK 动态代理加行为接口实现运行期日志与耗时切面,关键在于“接口先行、代理拦截、逻辑内聚”,不改业务代码,也不依赖 Spring,就能统一管控方法入口、执行、返回和异常全过程。
必须基于接口设计
JDK 动态代理只认接口,目标类必须实现至少一个接口。这不是限制,而是契约约束——它倒逼你面向接口编程,让日志切面有明确的拦截边界。
- 定义清晰的行为接口(如 UserOperation、OrderService),方法签名即切点契约
- 真实实现类(如 UserServiceImpl)只专注业务逻辑,不感知日志或耗时
- 若已有类未实现接口,优先重构为接口+实现,而非强行套用 CGLIB(会引入字节码依赖和继承限制)
InvocationHandler 封装统一切面逻辑
所有日志、计时、异常捕获都收口在 invoke() 方法里,避免分散硬编码。核心是三段式处理:进入前记录参数与时间戳,执行后计算耗时并格式化结果,异常时捕获堆栈但不吞掉原异常。
- 用 System.nanoTime() 获取纳秒级起止时间,比 System.currentTimeMillis() 更精准,适合性能敏感场景
- 对 args 和 result 做安全打印:null 判空、字符串截断(如长度超 50 截为 “…”,防日志刷屏)
- 异常要 throw e.getCause() 或 re-throw InvocationTargetException.getTargetException(),保证原有调用链不被破坏
Proxy.newProxyInstance 构建代理实例
创建代理对象时三个参数缺一不可,且需注意加载器与接口的一致性,否则运行时报 NoClassDefFoundError 或 ClassCastException。
- ClassLoader:传目标对象的 getClass().getClassLoader(),确保代理类能访问其依赖类型
- interfaces:传 target.getClass().getInterfaces(),不要漏掉多个接口,也不要传 target.getClass()(那是类,不是接口)
- InvocationHandler:复用同一个 handler 实例,可注入日志器(如 SLF4J Logger)、开关配置、采样率等上下文
轻量集成与上线建议
这种切面适合中小规模系统或嵌入式模块,部署时只需替换 Bean 创建方式,无需额外框架。
- 开发期:用 System.out.println 快速验证切面是否生效,确认 method.getName()、args、耗时输出符合预期
- 上线前:把日志输出桥接到 SLF4J 或 Log4j2,按包路径或方法名设置日志级别(如 DEBUG 打印参数,INFO 仅记耗时)
- 压测时:通过配置开关控制是否启用耗时统计,避免高并发下 nanoTime 频繁调用带来微小开销累积

















