Java动态代理结合InvocationHandler可实现零侵入通用日志切面,通过实现invoke()统一记录方法名、参数、耗时及返回值/异常,代理对象仅支持接口,需用Proxy.newProxyInstance创建,生产中建议接入SLF4J并支持注解控制。

Java动态代理结合InvocationHandler,可以优雅地实现通用日志切面,无需修改业务代码,也不依赖Spring AOP。
理解核心机制:InvocationHandler 是关键
动态代理的本质是运行时生成代理类,拦截方法调用。你需要实现InvocationHandler接口,在invoke()方法中统一添加日志逻辑——比如记录方法名、参数、耗时和返回值(或异常)。
- 代理对象只对接口生效(JDK原生代理限制),目标类必须至少实现一个接口
-
invoke()会在每次方法调用时触发,你在这里做前置日志(进入)、后置日志(正常返回)和异常日志(抛出异常) - 通过
method.getName()、Arrays.toString(args)、System.nanoTime()等可快速获取上下文信息
编写通用日志处理器 LogInvocationHandler
封装日志逻辑,支持可选开关、日志级别控制和耗时统计:
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
private final boolean enableLog;
<pre class="brush:php;toolbar:false;">public LogInvocationHandler(Object target, boolean enableLog) {
this.target = target;
this.enableLog = enableLog;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!enableLog || "toString".equals(method.getName())) {
return method.invoke(target, args);
}
long start = System.nanoTime();
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
String argsStr = Arrays.toString(args);
try {
System.out.printf("[LOG] → %s(%s)%n", methodName, argsStr);
Object result = method.invoke(target, args);
long cost = (System.nanoTime() - start) / 1_000_000;
System.out.printf("[LOG] ← %s returns %s (cost: %d ms)%n", methodName, result, cost);
return result;
} catch (InvocationTargetException e) {
long cost = (System.nanoTime() - start) / 1_000_000;
Throwable cause = e.getCause();
System.out.printf("[LOG] ✗ %s throws %s (cost: %d ms)%n", methodName, cause.getClass().getSimpleName(), cost);
throw cause;
}
}}
立即学习“Java免费学习笔记(深入)”;
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
创建代理实例并使用
用Proxy.newProxyInstance()生成代理对象,传入类加载器、接口数组和你的处理器:
- 确保目标对象实现了接口(例如
UserService接口) - 代理对象类型是该接口,不能强转为具体实现类
- 示例调用:
UserService proxy = (UserService) Proxy.newProxyInstance(...)
这样调用proxy.login("alice", "123")就会自动打印进出日志和耗时,业务代码零侵入。
进阶建议:提升实用性
生产环境可进一步优化:
- 替换
System.out为SLF4J或Logback,支持配置化日志级别(如DEBUG打参、INFO只打方法名) - 用
ThreadLocal记录调用链ID,方便日志追踪 - 通过注解(如
@Loggable)控制是否启用日志,避免全局开关粒度太粗 - 若需代理类而非接口,可切换为CGLIB(需额外依赖,不基于接口,但生成子类)
不复杂但容易忽略:日志切面本质是横切逻辑的提取,重点在于拦截时机的准确性和上下文信息的完整性,而非技术炫技。

















