动态代理就像是一位神奇的魔术师,它能悄无声息地在你的代码运行时,为你的对象添加各种功能,而这一切,你不需要修改任何已有的业务代码!它广泛应用于日志记录、权限控制、事务管理、远程调用(RPC)等场景,是现代Java框架(如Spring AOP、MyBatis)的核心技术之一。
本文将带你深入理解动态代理的本质,掌握JDK动态代理的使用方法,并通过实战案例体会其“无侵入式增强”的魅力。
想象一下,你想出国旅游,但你不会外语,也不熟悉签证流程。这时,你可以找一家旅行社——你只需告诉他们你的需求,他们就会帮你安排行程、预订酒店、办理签证。
在这个过程中:
在软件开发中,代理模式(Proxy Pattern) 正是这种“中间人”思想的体现。它为某个对象提供一个代理,以控制对原对象的访问,并在访问前后添加额外逻辑,例如:
静态代理需要我们提前编写一个代理类,它实现与目标对象相同的接口,并在方法中调用目标对象的方法,同时添加额外逻辑。
// 代理类
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void createUser(String username, String password) {
System.out.println("开始执行方法:createUser");
long start = System.currentTimeMillis();
target.createUser(username, password); // 调用真实对象
long end = System.currentTimeMillis();
System.out.println("方法执行结束,耗时:" + (end - start) + "ms");
}
// deleteUser 同理...
}❌ 缺点:每个接口都需要写一个代理类,代码重复,维护成本高。
动态代理 的魔力在于:在程序运行时,由JVM动态生成代理类和代理对象,无需手动编写。它真正实现了“一次编写,处处生效”。
Java 实现动态代理主要有三种方式:
方式 | 原理 | 依赖 |
|---|---|---|
JDK 动态代理 | 基于接口,使用 java.lang.reflect.Proxy | JDK 内置 |
CGLIB 动态代理 | 基于继承,通过字节码生成子类 | 第三方库 |
ASM | 直接操作字节码,最底层最灵活 | 第三方库 |
✅ 本文重点讲解使用最广泛的 JDK 动态代理。
JDK 动态代理的核心是以下三个组件:
java.lang.reflect.InvocationHandler 接口定义代理逻辑的处理者。你需要实现它的 invoke() 方法,在其中编写“方法调用前/后”的增强逻辑。
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}java.lang.reflect.Method 类表示一个具体的方法对象。通过它可以在运行时调用目标方法(反射机制)。
java.lang.reflect.Proxy 类JDK 提供的工具类,用于动态生成代理类并创建代理实例。
核心方法:
public static Object newProxyInstance(
ClassLoader loader, // 类加载器
Class<?>[] interfaces, // 目标对象实现的接口数组
InvocationHandler h // 代理逻辑处理器
)假设我们有一个用户服务,现在想在不修改原有代码的前提下,为所有方法添加执行时间日志功能。
// 用户服务接口
public interface UserService {
void createUser(String username, String password);
void deleteUser(String username);
}
// 业务实现类(无需任何改动!)
public class UserServiceImpl implements UserService {
@Override
public void createUser(String username, String password) {
System.out.println("✅ 创建用户:" + username);
simulateDelay(); // 模拟业务耗时
}
@Override
public void deleteUser(String username) {
System.out.println("❌ 删除用户:" + username);
simulateDelay();
}
private void simulateDelay() {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}InvocationHandler 实现类import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 日志代理处理器
public class LogInvocationHandler implements InvocationHandler {
private final Object target; // 目标对象
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 【前置增强】方法执行前
System.out.println("🎬 开始执行方法:" + methodName);
long startTime = System.nanoTime();
// 【核心逻辑】通过反射调用目标方法
Object result = method.invoke(target, args);
// 【后置增强】方法执行后
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // 毫秒
System.out.println("🏁 方法执行结束:" + methodName + ",耗时:" + duration + "ms");
return result; // 返回原方法结果
}
}import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// 1. 创建目标对象
UserService userService = new UserServiceImpl();
// 2. 创建代理处理器
LogInvocationHandler handler = new LogInvocationHandler(userService);
// 3. 动态生成代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(), // 类加载器
userService.getClass().getInterfaces(), // 实现的接口
handler // 代理逻辑
);
// 4. 调用代理对象的方法(自动触发日志)
proxy.createUser("zhangsan", "123456");
proxy.deleteUser("lisi");
}
}🎬 开始执行方法:createUser
✅ 创建用户:zhangsan
🏁 方法执行结束:createUser,耗时:104ms
🎬 开始执行方法:deleteUser
❌ 删除用户:lisi
🏁 方法执行结束:deleteUser,耗时:103ms✅ 成功!无需修改
UserServiceImpl,日志功能自动生效。
invoke() 方法invoke() 是动态代理的“心脏”,它在每次调用代理对象的方法时被触发。
public Object invoke(Object proxy, Method method, Object[] args)参数 | 说明 |
|---|---|
proxy | 当前生成的代理对象本身 |
method | 被调用的方法对象(反射) |
args | 方法的参数数组 |
💡 你可以通过
method.getName()判断当前调用的是哪个方法,从而实现差异化处理,比如只对save*方法加事务,对query*方法加缓存。
当你调用 Proxy.newProxyInstance() 时,JVM 会:
UserServiceProxy0)InvocationHandler.invoke()🔍 你可以通过
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true参数将生成的代理类.class文件保存到磁盘查看。
场景 | 说明 |
|---|---|
AOP(面向切面编程) | Spring AOP 的核心实现机制 |
事务管理 | 在方法前后自动开启/提交事务 |
RPC 框架 | 如 Dubbo、gRPC,客户端通过代理调用远程服务 |
缓存增强 | 查询前查缓存,查询后更新缓存 |
权限校验 | 调用前检查用户是否有权限 |
Mock 测试 | Mockito 等测试框架使用代理生成 Mock 对象 |
优点 | 说明 |
|---|---|
✅ 无侵入性 | 不修改原有业务代码,符合开闭原则 |
✅ 高度解耦 | 增强逻辑与业务逻辑分离 |
✅ 灵活复用 | 同一套代理逻辑可用于多个目标对象 |
✅ 易于维护 | 增强逻辑集中管理 |
缺点 | 说明 |
|---|---|
⚠️ 性能开销 | 反射调用比直接调用慢 |
⚠️ 只能代理接口 | JDK 动态代理要求目标类必须实现接口 |
⚠️ 调试困难 | 代理对象是运行时生成的,堆栈信息复杂 |
⚠️ 不支持 final 类/方法 | 无法继承或重写 |
💡 补充说明:如果目标类没有实现接口,可以使用 CGLIB(基于继承生成子类)或 AspectJ(编译期织入)来实现。
InvocationHandler:可设计通用的 AbstractAspectHandler 抽象类。invoke() 中的异常要妥善处理,避免吞掉原异常。动态代理是Java语言“元编程”能力的体现,它让程序具备了在运行时自我增强的能力。掌握JDK动态代理,不仅能帮助你理解Spring AOP等框架的底层原理,也能让你在设计系统时,写出更加灵活、可扩展、低耦合的代码。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。