这是一份非常详细、实用、通俗易懂且权威全面的 Java 动态代理指南。
Java 动态代理完全指南:原理、实战与经典案例
目录
- 概述
- 1.1 什么是代理模式?
- 1.2 静态代理 vs 动态代理
- 1.3 Java 动态代理的核心价值与应用场景
- 核心机制:
Proxy类与InvocationHandler接口- 2.1
java.lang.reflect.Proxy类详解 - 2.2
java.lang.reflect.InvocationHandler接口详解 - 2.3 动态代理的创建过程剖析
- 2.1
- 实战演练:基础用法
- 3.1 创建一个简单的动态代理:日志记录示例
- 3.2 代码详解与在 IDE 中运行
- 进阶理解
- 4.1 动态代理的原理:运行时生成代理类
- 4.2 动态代理的局限性
- 4.3 性能考量
- 最佳实践
- 5.1 接口设计建议
- 5.2
InvocationHandler实现技巧 - 5.3 异常处理
- 经典实战案例
- 6.1 案例一:数据库事务控制 (模拟)
- 6.2 案例二:RPC 框架中的远程方法调用 (模拟)
- 6.3 案例三:Spring AOP 风格的日志与性能监控
- 总结
1. 概述
1.1 什么是代理模式?
想象一下,你想找明星拍广告,但你不会直接去找明星本人,而是通过他的经纪人 。经纪人就相当于明星的代理。代理模式(Proxy Pattern)就是这种思想在编程中的应用。
- 目标对象 (Real Subject):真正执行业务逻辑的对象(如明星)。
- 代理对象 (Proxy):代替目标对象处理请求的对象(如经纪人)。客户端通常直接与代理对象交互。
- 作用 :代理对象可以在调用目标对象的方法之前 或之后 ,添加额外的操作(如安全检查、日志记录、事务管理等),而无需修改目标对象本身的代码 。这遵循了开闭原则(对扩展开放,对修改关闭)。
1.2 静态代理 vs 动态代理
- 静态代理 :
- 手动编写代理类:程序员需要为每个需要代理的接口或类,显式地编写一个实现了相同接口或继承了相同父类的代理类。
- 缺点:如果有很多类需要代理,或者接口方法很多,编写和维护代理类会非常繁琐。
- 动态代理 :
- 运行时 生成代理类:程序在运行时,根据指定的接口(或一组接口),动态地创建代理类的字节码并加载到 JVM 中。
- 核心机制 :通过
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。 - 优点 :一个通用的
InvocationHandler实现可以服务于多个不同的接口,大大减少代码量,提高灵活性。
1.3 Java 动态代理的核心价值与应用场景
- 核心价值 :提供一种在运行时动态创建代理对象的能力,无需编写具体的代理类代码,增强了程序的灵活性和可扩展性。
- 应用场景 :
- AOP(面向切面编程):如日志记录、性能监控、事务管理、安全检查等。这是动态代理最经典的应用。
- 远程方法调用(RPC):客户端调用代理对象的方法时,代理对象负责将调用信息(方法名、参数等)序列化并通过网络发送到服务端,并将服务端返回的结果反序列化后返回给客户端。
- 延迟初始化:代理对象在第一次被调用时才真正创建或加载目标对象。
- 访问控制:在调用目标方法前进行权限校验。
- 缓存:代理对象可以缓存目标方法的调用结果。
2. 核心机制:Proxy 类与 InvocationHandler 接口
Java 动态代理的核心依赖于 JDK 提供的两个关键组件:
2.1 java.lang.reflect.Proxy 类
Proxy 类是用于创建动态代理类和实例的工厂类。它提供了一组静态方法来生成代理对象。
-
核心方法 :
javapublic static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentExceptionloader:定义代理类的类加载器 。通常使用目标接口的类加载器(targetInterface.getClassLoader())。interfaces:代理类需要实现的接口列表 。这是一个Class对象数组。非常重要:动态代理只能基于接口!不能基于类!h:InvocationHandler接口的实现对象。所有对代理对象的方法调用都会转发 给这个InvocationHandler的invoke方法处理。- 返回值:一个实现了指定接口列表的代理对象实例。
2.2 java.lang.reflect.InvocationHandler 接口
InvocationHandler 是一个调用处理器接口。它只有一个方法需要实现:
java
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
proxy:代理对象本身(即Proxy.newProxyInstance返回的对象)。注意: 在invoke方法内部,如果调用proxy对象的方法(如proxy.toString()),且这个方法不是Object类的方法,会再次触发invoke调用,可能导致无限递归。调用proxy的Object方法(如hashCode,equals,toString)通常是安全的,因为它们不会被代理。method:客户端正在调用的代理对象上的方法 对应的Method对象。通过它,你可以知道客户端调用了哪个方法。args:客户端调用方法时传递的参数值数组 。如果方法没有参数,则为null。- 返回值 :代理方法应该返回的值。这通常是调用目标对象对应方法的结果(
method.invoke(target, args)),但也可能是你自己构造的结果。 - 职责 :
InvocationHandler的invoke方法负责处理 所有对代理对象方法的调用。在这里,你可以:- 在调用目标方法前执行操作(如日志、权限检查)。
- 调用目标对象的方法(这是关键步骤)。
- 在调用目标方法后执行操作(如日志、结果处理)。
- 处理异常。
2.3 动态代理的创建过程剖析
- 定义接口:首先需要一个或多个接口,定义代理对象应该具备的行为。
- 实现目标对象:编写实现了这些接口的具体类(目标对象)。
- 实现
InvocationHandler:编写一个类实现InvocationHandler接口。在这个类的invoke方法中,定义代理逻辑(如调用目标对象的方法、添加额外操作)。 - 创建代理对象 :调用
Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)。- JVM 在运行时,根据指定的接口列表 (
interfaces),动态生成一个新的类(代理类)的字节码。这个类实现了所有指定的接口。 - 这个生成的代理类内部,会持有你提供的
InvocationHandler对象 (h)。 - 代理类中每个接口方法的实现,本质上都是直接调用
h.invoke(this, method, args)。
- JVM 在运行时,根据指定的接口列表 (
- 使用代理对象 :客户端代码获得代理对象后,将其当作普通接口实现类来使用。当调用其方法时,请求被转发到
InvocationHandler的invoke方法,由它决定如何处理(通常最终会调用真实的目标对象方法)。
3. 实战演练:基础用法
3.1 创建一个简单的动态代理:日志记录示例
让我们用一个最简单的例子演示动态代理:在方法调用前后打印日志。
步骤 1:定义接口 (UserService)
java
public interface UserService {
void addUser(String username);
void deleteUser(String username);
String getUser(String username);
}
步骤 2:实现目标对象 (UserServiceImpl)
java
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("添加用户: " + username);
// 模拟实际添加操作
}
@Override
public void deleteUser(String username) {
System.out.println("删除用户: " + username);
// 模拟实际删除操作
}
@Override
public String getUser(String username) {
System.out.println("获取用户: " + username);
// 模拟实际查询操作
return "用户信息: " + username;
}
}
步骤 3:实现 InvocationHandler (LoggingHandler)
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LoggingHandler implements InvocationHandler {
private final Object target; // 被代理的目标对象
public LoggingHandler(Object target) {
this.target = target; // 构造函数传入真实对象
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前打印日志
System.out.println("[日志] 开始执行方法: " + method.getName() + ", 参数: " + (args != null ? java.util.Arrays.toString(args) : "无"));
// 调用目标对象上的原始方法
Object result = method.invoke(target, args);
// 方法调用后打印日志 (注意:如果方法返回void,result为null)
System.out.println("[日志] 方法执行完成: " + method.getName() + ", 返回结果: " + (result != null ? result.toString() : "void"));
// 将调用结果返回给调用者
return result;
}
}
步骤 4:创建并使用代理对象 (Main)
java
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
public static void main(String[] args) {
// 1. 创建真实目标对象
UserService realService = new UserServiceImpl();
// 2. 创建 InvocationHandler,传入真实对象
InvocationHandler handler = new LoggingHandler(realService);
// 3. 使用 Proxy 创建代理对象
// 注意:这里转换成了 UserService 接口类型
UserService proxyService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(), // 类加载器
new Class<?>[]{UserService.class}, // 代理类要实现的接口列表
handler); // 调用处理器
// 4. 客户端使用代理对象
proxyService.addUser("张三");
proxyService.deleteUser("李四");
String userInfo = proxyService.getUser("王五");
System.out.println("客户端收到的用户信息: " + userInfo);
// 5. (可选) 查看代理类的名字 (通常包含 $Proxy 字样)
System.out.println("代理对象的类名: " + proxyService.getClass().getName());
}
}
3.2 代码详解与在 IDE 中运行
- 接口 (
UserService): 定义了业务操作。 - 目标对象 (
UserServiceImpl): 实现了接口,包含实际的业务逻辑。 - 调用处理器 (
LoggingHandler) :- 持有一个
target对象引用(这里是UserServiceImpl实例)。 - 在
invoke方法中:- 打印方法开始执行的日志(包含方法名和参数)。
- 使用反射
method.invoke(target, args)调用目标对象上的原始方法。 - 打印方法执行完成的日志(包含方法名和返回值)。
- 将原始方法的返回值返回给代理方法的调用者。
- 持有一个
- 主类,创建代理 (
DynamicProxyDemo) :- 创建真实对象
realService。 - 创建
LoggingHandler,将realService传入。 - 调用
Proxy.newProxyInstance创建代理对象proxyService。注意转换类型为接口UserService。 - 使用
proxyService调用方法。这些调用会被LoggingHandler拦截并添加日志。
- 创建真实对象
- 在 IDE 中运行 :
- 将上述四个类 (
UserService,UserServiceImpl,LoggingHandler,DynamicProxyDemo) 复制到你的 Java 项目中。 - 运行
DynamicProxyDemo的main方法。 - 观察控制台输出,你会看到每次方法调用前后都添加了日志信息。
- 将上述四个类 (
预期输出示例:
[日志] 开始执行方法: addUser, 参数: [张三]
添加用户: 张三
[日志] 方法执行完成: addUser, 返回结果: void
[日志] 开始执行方法: deleteUser, 参数: [李四]
删除用户: 李四
[日志] 方法执行完成: deleteUser, 返回结果: void
[日志] 开始执行方法: getUser, 参数: [王五]
获取用户: 王五
[日志] 方法执行完成: getUser, 返回结果: 用户信息: 王五
客户端收到的用户信息: 用户信息: 王五
代理对象的类名: com.sun.proxy.$Proxy0
4. 进阶理解
4.1 动态代理的原理:运行时生成代理类
Proxy.newProxyInstance 方法在运行时执行了以下关键步骤:
-
类加载器检查 :验证给定的
ClassLoader和interfaces是否能一起工作。 -
代理类查找或生成 :
- 首先尝试从缓存(通常是
loader的缓存)中查找是否已生成过实现了这些interfaces的代理类。 - 如果没有,则使用
ProxyGenerator(或类似机制)动态生成代理类的字节码 (byte[])。
- 首先尝试从缓存(通常是
-
字节码加载 :使用提供的
ClassLoader将生成的字节码加载到 JVM 中,定义一个新的Class对象(代理类)。 -
实例化代理对象 :
- 通过反射获取代理类的构造函数(该构造函数需要
InvocationHandler参数)。 - 使用
InvocationHandler实例 (h) 调用构造函数,创建代理对象实例。
- 通过反射获取代理类的构造函数(该构造函数需要
-
代理类结构 :生成的代理类大致如下(伪代码):
javapublic final class $Proxy0 extends Proxy implements UserService { // 实现指定的接口 private static Method m1, m2, m3; // 对应接口方法的 Method 对象 static { try { m1 = Class.forName("UserService").getMethod("addUser", String.class); m2 = ... // deleteUser m3 = ... // getUser } catch (Exception e) { ... } } public $Proxy0(InvocationHandler h) { super(h); // 调用 Proxy 的构造函数 } @Override public void addUser(String username) { try { super.h.invoke(this, m1, new Object[]{username}); // 调用处理器 } catch (Throwable e) { ... } } // 其他方法类似... }- 代理类继承自
java.lang.reflect.Proxy。 - 实现了你指定的所有接口 (
UserService)。 - 每个接口方法的实现,都是直接调用
InvocationHandler的invoke方法 (super.h.invoke(...)),并将proxy(自身)、Method对象、参数数组传递过去。
- 代理类继承自
4.2 动态代理的局限性
- 只能基于接口 :这是 JDK 动态代理最大的限制。代理类必须实现一个或多个接口。它不能代理一个没有实现任何接口的普通类 。如果需要代理类,可以考虑使用 CGLIB 、Byte Buddy 或 Javassist 等第三方字节码操作库。
- 接口方法必须是 public :JDK 动态代理只能代理接口中的
public方法。protected、private或包级私有的方法无法被代理。 - 性能开销 :反射调用 (
method.invoke()) 比直接调用方法慢。虽然现代 JVM 对反射进行了优化,但在极端性能敏感的场景仍需注意。动态生成类本身也有开销,但通常在应用启动时一次性完成,影响较小。 - 代理对象类型 :代理对象是
Proxy的子类 ($Proxy0等),不是目标类的子类。因此不能直接将其强制转换为目标类类型(只能转换为接口类型)。
4.3 性能考量
- 在绝大多数应用场景中,JDK 动态代理的性能开销是可以接受的。
- 如果确实遇到性能瓶颈:
- 考虑是否过度使用了代理。
- 评估使用第三方库(如 CGLIB)是否更适合(CGLIB 通过生成目标类的子类来代理,避免了部分反射调用,但创建代理对象可能更慢)。
- 对于非常高频的调用,可以考虑在
InvocationHandler内部对某些方法做特殊处理(如直接调用目标对象的方法,避免反射),但这会牺牲通用性。
5. 最佳实践
5.1 接口设计建议
- 职责单一 :接口应专注于一组紧密相关的功能。这样
InvocationHandler的实现逻辑会更清晰。 - 避免暴露过多方法 :代理所有接口方法。如果接口中有大量方法,而你的
InvocationHandler只关心其中几个,会导致不必要的调用转发。可以考虑重构接口。 - 清晰的命名 :方法名应准确反映其功能,便于在
InvocationHandler中识别和处理。
5.2 InvocationHandler 实现技巧
-
保持轻量 :
invoke方法中的额外逻辑(如日志、校验)应尽量高效。避免在此处执行耗时的 I/O 或复杂计算。 -
区分方法处理 :根据
method.getName()或method对象本身,对不同的方法执行不同的代理逻辑。例如,只对update*方法添加事务控制。java@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.startsWith("update") || methodName.startsWith("delete")) { // 事务相关逻辑 return doInTransaction(() -> method.invoke(target, args)); } else { // 普通调用 return method.invoke(target, args); } } -
处理
Object方法 :对于toString,hashCode,equals等Object类的方法,通常需要特殊处理以避免无限递归或错误行为。常见的做法是:toString():可以返回代理对象和真实对象的组合信息。hashCode():可以返回真实对象的hashCode()或代理对象的System.identityHashCode(proxy)。equals():比较代理对象时,通常应比较它们包装的真实对象是否相同。
java@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Class<?> declaringClass = method.getDeclaringClass(); // 处理 Object 类的方法 if (declaringClass == Object.class) { if ("equals".equals(methodName)) { Object arg = args[0]; // 如果参数也是代理,比较其目标对象;否则直接比较 return (proxy == arg) || (arg instanceof Proxy && this.target.equals(Proxy.getInvocationHandler(arg).getTarget())); } else if ("hashCode".equals(methodName)) { return System.identityHashCode(proxy); // 或 target.hashCode(); } else if ("toString".equals(methodName)) { return "Proxy[" + target.toString() + "]"; } } // ... 其他方法处理 }(注意:
getTarget()需要在自定义InvocationHandler中提供访问target的方法) -
封装目标对象访问 :在自定义
InvocationHandler中提供一个方法来获取其持有的目标对象(如getTarget()),方便调试或特殊场景使用。
5.3 异常处理
-
传递原始异常 :
method.invoke(target, args)会抛出InvocationTargetException。这个异常的getCause()方法包含了目标方法本身抛出的原始异常(如SQLException,IOException)。通常你应该在InvocationHandler的invoke方法中捕获InvocationTargetException,并将getCause()抛给上层调用者,这样客户端看到的是目标方法抛出的异常类型。java@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // ... 前置操作 Object result = method.invoke(target, args); // ... 后置操作 return result; } catch (InvocationTargetException e) { // 抛出目标方法产生的原始异常 throw e.getCause(); } // 处理其他异常... } -
包装异常:如果需要,你也可以将原始异常包装成自定义的业务异常再抛出。
-
记录异常 :在
InvocationHandler中统一记录方法调用异常是一个常见做法(AOP 异常日志)。
6. 经典实战案例
下面提供三个更贴近实际应用场景的详细案例。每个案例都包含完整可运行的代码。
6.1 案例一:数据库事务控制 (模拟)
场景 :在方法执行前后管理数据库事务(开启、提交/回滚)。这里简化了数据库操作,使用 System.out.println 模拟。
接口 (AccountService)
java
public interface AccountService {
void transfer(String fromAccount, String toAccount, double amount);
}
目标对象 (AccountServiceImpl)
java
public class AccountServiceImpl implements AccountService {
@Override
public void transfer(String fromAccount, String toAccount, double amount) {
// 模拟实际的转账数据库操作
System.out.println("执行转账: " + fromAccount + " -> " + toAccount + ", 金额: " + amount);
// 这里可能抛出 RuntimeException 模拟失败
if (amount > 1000) {
throw new RuntimeException("转账金额过大!");
}
}
}
事务管理器 (TransactionManager) - 模拟
java
public class TransactionManager {
public void begin() {
System.out.println("事务管理器: 开启事务");
}
public void commit() {
System.out.println("事务管理器: 提交事务");
}
public void rollback() {
System.out.println("事务管理器: 回滚事务");
}
}
事务控制的 InvocationHandler (TransactionHandler)
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TransactionHandler implements InvocationHandler {
private final Object target; // 真实对象
private final TransactionManager transactionManager; // 事务管理器
public TransactionHandler(Object target, TransactionManager transactionManager) {
this.target = target;
this.transactionManager = transactionManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 开启事务
transactionManager.begin();
// 执行目标方法
result = method.invoke(target, args);
// 提交事务
transactionManager.commit();
} catch (Exception e) {
// 出现异常,回滚事务
transactionManager.rollback();
// 抛出原始异常
throw e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e;
}
return result;
}
}
客户端使用 (TransactionDemo)
java
import java.lang.reflect.Proxy;
public class TransactionDemo {
public static void main(String[] args) {
// 创建真实对象和事务管理器
AccountService realService = new AccountServiceImpl();
TransactionManager txManager = new TransactionManager();
// 创建 TransactionHandler
InvocationHandler handler = new TransactionHandler(realService, txManager);
// 创建代理对象
AccountService proxyService = (AccountService) Proxy.newProxyInstance(
AccountService.class.getClassLoader(),
new Class<?>[]{AccountService.class},
handler);
// 使用代理对象进行转账 (正常)
try {
proxyService.transfer("A001", "B002", 500.0);
} catch (Exception e) {
System.out.println("转账异常: " + e.getMessage());
}
// 使用代理对象进行转账 (会失败并回滚)
try {
proxyService.transfer("A001", "B002", 2000.0);
} catch (Exception e) {
System.out.println("转账异常: " + e.getMessage());
}
}
}
预期输出:
事务管理器: 开启事务
执行转账: A001 -> B002, 金额: 500.0
事务管理器: 提交事务
事务管理器: 开启事务
执行转账: A001 -> B002, 金额: 2000.0
事务管理器: 回滚事务
转账异常: 转账金额过大!
6.2 案例二:RPC 框架中的远程方法调用 (模拟)
场景:客户端调用代理对象的方法,代理对象负责将方法名、参数等信息序列化并通过网络发送给远程服务端,接收结果并返回给客户端。这里简化了网络通信和序列化。
服务接口 (UserService - 同案例1)
java
public interface UserService {
void addUser(String username);
void deleteUser(String username);
String getUser(String username);
}
远程调用处理器 (RpcInvocationHandler)
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class RpcInvocationHandler implements InvocationHandler {
// 模拟远程服务地址
private final String serverAddress;
public RpcInvocationHandler(String serverAddress) {
this.serverAddress = serverAddress;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 构建 RPC 请求信息 (简化)
String request = buildRpcRequest(method, args);
System.out.println("[客户端] 发送 RPC 请求到 " + serverAddress + ": " + request);
// 2. 模拟网络发送请求并接收响应 (这里简化,直接返回一个结果字符串)
// 在实际 RPC 框架中,这里会使用网络库发送序列化后的数据包
String response = "[服务端响应] 方法: " + method.getName() + ", 结果: OK";
System.out.println("[客户端] 收到 RPC 响应: " + response);
// 3. 解析响应并返回结果 (简化)
// 对于非 void 方法,需要根据响应构造返回值
if (method.getReturnType() == void.class) {
return null;
} else {
return response;
}
}
private String buildRpcRequest(Method method, Object[] args) {
return "RPC调用: 接口=" + method.getDeclaringClass().getSimpleName() +
", 方法=" + method.getName() +
", 参数=" + java.util.Arrays.toString(args);
}
}
客户端使用 (RpcClientDemo)
java
import java.lang.reflect.Proxy;
public class RpcClientDemo {
public static void main(String[] args) {
// 创建 RpcInvocationHandler,指定远程服务地址
InvocationHandler handler = new RpcInvocationHandler("http://rpc-server:8080");
// 创建代理对象
UserService proxyService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[]{UserService.class},
handler);
// 客户端像调用本地方法一样使用代理对象
proxyService.addUser("远程用户");
String user = proxyService.getUser("远程用户");
System.out.println("客户端收到远程用户信息: " + user);
}
}
预期输出:
[客户端] 发送 RPC 请求到 http://rpc-server:8080: RPC调用: 接口=UserService, 方法=addUser, 参数=[远程用户]
[客户端] 收到 RPC 响应: [服务端响应] 方法: addUser, 结果: OK
[客户端] 发送 RPC 请求到 http://rpc-server:8080: RPC调用: 接口=UserService, 方法=getUser, 参数=[远程用户]
[客户端] 收到 RPC 响应: [服务端响应] 方法: getUser, 结果: OK
客户端收到远程用户信息: [服务端响应] 方法: getUser, 结果: OK
6.3 案例三:Spring AOP 风格的日志与性能监控
场景:结合日志记录和性能监控(方法执行耗时)。
接口 (ProductService)
java
public interface ProductService {
Product getProductById(int id);
void updateProductPrice(int id, double newPrice);
}
实体类 (Product)
java
public class Product {
private int id;
private String name;
private double price;
public Product(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Product{id=" + id + ", name='" + name + '\'' + ", price=" + price + '}';
}
}
目标对象 (ProductServiceImpl)
java
public class ProductServiceImpl implements ProductService {
@Override
public Product getProductById(int id) {
// 模拟数据库查询耗时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new Product(id, "产品" + id, 100.0 * id);
}
@Override
public void updateProductPrice(int id, double newPrice) {
// 模拟更新操作耗时
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新产品 " + id + " 价格为: " + newPrice);
}
}
AOP 风格的 InvocationHandler (AopLoggingHandler)
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class AopLoggingHandler implements InvocationHandler {
private final Object target;
public AopLoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = method.getName();
// 记录方法开始
System.out.println("[AOP-Log] ==> 方法 " + methodName + " 开始执行. 参数: " + java.util.Arrays.toString(args));
Object result = null;
try {
// 执行目标方法
result = method.invoke(target, args);
return result;
} finally {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 记录方法结束和耗时
System.out.println("[AOP-Log] <== 方法 " + methodName + " 执行完成. 耗时: " + duration + "ms. " +
(method.getReturnType() == void.class ? "" : "结果: " + result));
}
}
}
客户端使用 (AopDemo)
java
import java.lang.reflect.Proxy;
public class AopDemo {
public static void main(String[] args) {
// 创建真实对象
ProductService realService = new ProductServiceImpl();
// 创建 AopLoggingHandler
InvocationHandler handler = new AopLoggingHandler(realService);
// 创建代理对象
ProductService proxyService = (ProductService) Proxy.newProxyInstance(
ProductService.class.getClassLoader(),
new Class<?>[]{ProductService.class},
handler);
// 使用代理对象
Product product = proxyService.getProductById(1);
System.out.println("获取到的产品: " + product);
proxyService.updateProductPrice(2, 250.0);
}
}
预期输出:
[AOP-Log] ==> 方法 getProductById 开始执行. 参数: [1]
[AOP-Log] <== 方法 getProductById 执行完成. 耗时: 100ms. 结果: Product{id=1, name='产品1', price=100.0}
获取到的产品: Product{id=1, name='产品1', price=100.0}
[AOP-Log] ==> 方法 updateProductPrice 开始执行. 参数: [2, 250.0]
更新产品 2 价格为: 250.0
[AOP-Log] <== 方法 updateProductPrice 执行完成. 耗时: 50ms.
7. 总结
Java 动态代理 (java.lang.reflect.Proxy 和 InvocationHandler) 是 Java 反射 API 提供的一个强大工具,用于在运行时动态创建实现了指定接口的代理对象。它的核心价值在于实现无侵入式 的方法拦截 和功能增强。通过本指南的学习和代码实践,你应该能够深入理解 Java 动态代理的原理,掌握其核心 API 的使用方法,并能在实际项目中应用它来解决诸如日志记录、事务管理、远程调用等常见问题。