jdk proxy通过接口代理实现无侵入日志监控,仅支持public方法拦截;需定义接口、实现invocationhandler、用proxy.newproxyinstance创建代理,不适用无接口或非public方法场景。
java原生代理(jdk proxy)能实现在不修改业务代码的前提下,对目标对象的方法调用进行拦截,非常适合轻量级 aop 日志监控——比如记录方法入参、返回值、执行耗时、异常信息等。关键在于:它仅支持接口代理,必须有明确的接口定义,且只能拦截 public 方法。
一、前置条件:定义统一接口与实现类
代理机制依赖接口,所以第一步是把需要监控的业务逻辑抽象成接口:
// 日志监控目标接口
public interface UserService {
String getUserById(Long id);
void updateUser(User user);
}
// 实现类(纯业务,无日志侵入)
public class UserServiceImpl implements UserService {
@Override
public String getUserById(Long id) {
return "User{id=" + id + ", name='Alice'}";
}
@Override
public void updateUser(User user) {
System.out.println("Updated: " + user);
}
}
二、编写日志拦截器:InvocationHandler 实现
核心逻辑封装在 InvocationHandler 中,通过 method.invoke() 控制原方法执行时机,并在前后插入日志:
public class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法前:打印类名、方法名、参数
System.out.println("[BEFORE] " + method.getDeclaringClass().getSimpleName()
+ "." + method.getName() + "(" + Arrays.toString(args) + ")");
long start = System.currentTimeMillis();
Object result = null;
try {
result = method.invoke(target, args); // 执行原方法
long cost = System.currentTimeMillis() - start;
System.out.println("[AFTER] " + method.getName() + " → " + result + " (cost: " + cost + "ms)");
} catch (Exception e) {
long cost = System.currentTimeMillis() - start;
System.err.println("[EXCEPTION] " + method.getName() + " failed: " + e.getCause());
throw e;
}
return result;
}
}
三、创建代理对象并使用
使用 Proxy.newProxyInstance() 创建代理实例,传入类加载器、接口数组和 handler:
public class AopDemo {
public static void main(String[] args) {
// 原始目标对象
UserService userService = new UserServiceImpl();
// 创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LoggingInvocationHandler(userService)
);
// 调用代理对象方法 → 日志自动输出
proxy.getUserById(1001L);
proxy.updateUser(new User("Bob", 28));
}
}
运行后控制台将输出类似:
[BEFORE] UserService.getUserById([1001])
[AFTER] getUserById → User{id=1001, name='Alice'} (cost: 1ms)
[BEFORE] UserService.updateUser([User{name='Bob', age=28}])
Updated: User{name='Bob', age=28}
[AFTER] updateUser → null (cost: 2ms)
四、注意事项与局限性
- 只适用于实现了接口的类;若目标类无接口,需改用 CGLIB(但那是字节码增强,不属于“原生代理”)
- 无法代理
static、final或private方法 - 代理对象是接口类型,不能强转为具体实现类(如
(UserServiceImpl) proxy会 ClassCastException) - 参数数组
args是原始传入值,若方法内修改了可变对象(如 List、Map),日志中看到的是调用前的快照 - 如需深度打印复杂对象,建议集成 Jackson 或 Gson 的
writeValueAsString做格式化,避免toString()空或混乱
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











