要实现基于接口的动态代理无侵入式注入日志,必须让业务类实现接口,利用 jdk proxy 与 invocationhandler 拦截方法调用;需定义清晰接口、实现类专注业务逻辑、日志处理器统一处理前后及异常时机,并注意空指针、受检异常、递归调用等避坑点。

要实现基于接口的动态代理为业务方法无侵入式注入日志,核心是利用 JDK 的 Proxy 和 InvocationHandler,在不修改原有实现类的前提下,拦截所有接口方法调用并统一添加日志逻辑。
必须满足的前提条件
JDK 动态代理只支持对接口代理,因此目标类必须实现至少一个接口。如果业务类是直接写死的普通类(未实现任何接口),则无法使用 JDK 代理——此时需改用 CGLIB 或字节码增强方案。
- 定义清晰的业务接口(如
Payment、UserService) - 业务实现类仅负责具体逻辑,不掺杂日志、监控等横切关注点
- 所有需要增强的方法都声明在接口中(JDK 代理只能代理接口方法)
编写统一的日志处理器
实现 InvocationHandler,在 invoke() 中插入日志时机:方法前、返回后、异常时。建议封装成可复用工具类,避免重复写日志格式。
- 记录方法名、参数值(注意敏感字段脱敏)、开始时间
- 捕获返回结果或异常,并打印耗时、状态、错误堆栈
- 使用
method.invoke(target, args)调用原始方法,确保业务逻辑不受影响 - 示例片段:LogFactory.logBefore(method.getName(), args); → 执行原方法 → LogFactory.logAfterReturn(method.getName(), result);
创建代理对象并交付使用
在需要启用日志的地方(如 Spring Bean 初始化、工厂方法、测试入口),用 Proxy.newProxyInstance() 构建代理实例,传入目标对象、接口数组和日志处理器。
- 类加载器建议用目标对象的
getClass().getClassLoader() - 接口数组传入目标对象
target.getClass().getInterfaces()即可 - 代理对象类型与接口一致,可直接赋值给接口变量,调用方完全无感知
- Spring 中可通过
@Bean方法返回代理对象,或配合@Aspect自动织入(底层仍是此机制)
注意事项与避坑点
看似简单,但几个细节常导致日志失效或抛异常:
- 目标对象不能为
null,否则method.invoke(null, ...)报 NPE - 若方法声明了受检异常(
throws Exception),代理中需原样抛出,不可吞掉 - 避免在
invoke中递归调用代理对象自身方法,会引发栈溢出 - 日志内容尽量轻量,避免在高并发场景下因日志 I/O 拖慢主流程











