一、理解代理模式本质
代理模式的核心思想是引入一个中间层——即代理对象,让它代替真实对象处理外部请求。这样做的好处是:无需改动原有代码,就能在目标方法执行前后插入额外逻辑。
生活化比喻:想象你是一位明星(真实对象),所有粉丝来信都由经纪人(代理对象)先过目筛选。经纪人可以在转交信件前进行内容审查、分类整理,甚至直接拒绝某些来信,而你只需要处理经过筛选后的邮件。
代理模式的价值:
- 解耦:调用方无需直接依赖目标对象
- 增强:在不修改源码的前提下扩展功能
本文深入讲解 Java 代理模式,涵盖静态代理与动态代理的核心原理及实现。静态代理需手动编写代理类,耦合度高;动态代理在运行时生成字节码,分为基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。文章通过 PaymentService 和 DataQueryService 等完整代码示例,演示了 InvocationHandler 和 MethodInterceptor 的使用流程,对比了两种动态代理的实现差异、性能表现及适用场景,并给出了 Spring AOP 中的实际应用建议。掌握代理模式是理解 Spring AOP、RPC 框架的基础。
代理模式的核心思想是引入一个中间层——即代理对象,让它代替真实对象处理外部请求。这样做的好处是:无需改动原有代码,就能在目标方法执行前后插入额外逻辑。
生活化比喻:想象你是一位明星(真实对象),所有粉丝来信都由经纪人(代理对象)先过目筛选。经纪人可以在转交信件前进行内容审查、分类整理,甚至直接拒绝某些来信,而你只需要处理经过筛选后的邮件。
代理模式的价值:
静态代理要求开发者手工编写代理类,在编译阶段代理类的代码就已完全确定。从 JVM 视角看,编译后会产出三个独立的 .class 文件:接口、实现类、代理类。
主要缺陷:
步骤一:定义业务接口
public interface MessageService {
String deliver(String content);
}
步骤二:实现真实业务
public class MessageServiceImpl implements MessageService {
@Override
public String deliver(String content) {
System.out.println("正在发送消息:" + content);
return content;
}
}
步骤三:手工编写代理类
public class MessageServiceProxy implements MessageService {
private final MessageService realService;
public MessageServiceProxy(MessageService service) {
this.realService = service;
}
@Override
public String deliver(String content) {
// 前置处理
System.out.println("[代理] 开始处理消息,内容长度:" + content.length());
// 调用真实对象
String result = realService.deliver(content);
// 后置处理
System.out.println("[代理] 消息处理完成,返回结果:" + result);
return result;
}
}
步骤四:客户端调用
public class Client {
public static void main(String[] args) {
MessageService real = new MessageServiceImpl();
MessageService proxy = new MessageServiceProxy(real);
proxy.deliver("Hello, Proxy Pattern!");
}
}
执行结果:
[代理] 开始处理消息,内容长度:22
正在发送消息:Hello, Proxy Pattern!
[代理] 消息处理完成,返回结果:Hello, Proxy Pattern!
动态代理将代理类的生成时机推迟到程序运行阶段,JVM 会在内存中动态构建代理类的字节码并加载。这彻底解决了静态代理"一个目标类对应一个代理类"的痛点。
技术价值:
Java 生态中主流的动态代理技术分为 JDK 内置实现 和 CGLIB 第三方库 两种。
JDK 动态代理依赖两个核心组件:
| 组件 | 类型 | 职责 |
|---|---|---|
java.lang.reflect.Proxy | 类 | 工厂类,负责生成代理对象 |
java.lang.reflect.InvocationHandler | 接口 | 回调接口,定义方法拦截逻辑 |
Proxy.newProxyInstance() 方法签名:
public static Object newProxyInstance(
ClassLoader loader, // 类加载器:用于加载动态生成的代理类
Class<?>[] interfaces, // 接口数组:代理类需要实现的接口列表
InvocationHandler h // 处理器:方法调用的实际处理者
)
InvocationHandler.invoke() 方法:
public Object invoke(
Object proxy, // 代理对象实例(慎用,避免循环调用)
Method method, // 被调用的方法反射对象
Object[] args // 方法参数数组
)
第一步:定义业务契约与实现
// 接口(JDK 动态代理必须基于接口)
public interface PaymentService {
boolean pay(String orderId, BigDecimal amount);
}
// 真实实现
public class PaymentServiceImpl implements PaymentService {
@Override
public boolean pay(String orderId, BigDecimal amount) {
System.out.printf("执行支付:订单 [%s], 金额 [¥%s]%n", orderId, amount);
return true;
}
}
第二步:实现调用处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
public class PerformanceInvocationHandler implements InvocationHandler {
private final Object targetObject;
public PerformanceInvocationHandler(Object target) {
this.targetObject = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.printf("[监控] 方法:%s, 参数:%s%n", method.getName(), Arrays.toString(args));
// 通过反射调用目标对象的真实方法
Object result = method.invoke(targetObject, args);
long cost = System.currentTimeMillis() - startTime;
System.out.printf("[监控] 执行耗时:%dms, 返回值:%s%n", cost, result);
return result;
}
}
第三步:创建代理工厂
import java.lang.reflect.Proxy;
public class ServiceProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new PerformanceInvocationHandler(target)
);
}
}
第四步:业务调用
public class Application {
public static void main(String[] args) {
PaymentService realService = new PaymentServiceImpl();
PaymentService proxy = ServiceProxyFactory.createProxy(realService);
proxy.pay("ORD-2024-001", new BigDecimal("199.99"));
}
}
输出:
[监控] 方法:pay, 参数:[ORD-2024-001, 199.99]
执行支付:订单 [ORD-2024-001], 金额 [¥199.99]
[监控] 执行耗时:12ms, 返回值:true
JDK 动态代理有一个先天限制:只能代理实现了接口的类。对于没有接口的遗留类或第三方类,CGLIB(Code Generation Library)提供了基于继承的解决方案。
核心机制:
关键组件:
| 组件 | 类型 | 职责 |
|---|---|---|
net.sf.cglib.proxy.Enhancer | 类 | 配置并生成代理类 |
net.sf.cglib.proxy.MethodInterceptor | 接口 | 定义方法拦截逻辑 |
net.sf.cglib.proxy.MethodProxy | 类 | 高性能方法调用(避免反射) |
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
场景:为无接口的类添加缓存功能
第一步:定义目标类(无接口)
public class DataQueryService {
// 模拟耗时查询
public String queryFromDatabase(String sql) {
System.out.println("执行数据库查询:" + sql);
try {
Thread.sleep(100); // 模拟耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Result of: " + sql;
}
public final String querySystemConfig(String key) {
// final 方法无法被代理
return "SystemConfig:" + key;
}
}
第二步:实现方法拦截器
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CacheMethodInterceptor implements MethodInterceptor {
private final Map<String, Object> cache = new HashMap<>();
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String cacheKey = method.getName() + ":" + args[0];
// 检查缓存
if (cache.containsKey(cacheKey)) {
System.out.println("[缓存] 命中:" + cacheKey);
return cache.get(cacheKey);
}
// 执行真实方法(使用 MethodProxy 避免反射开销)
System.out.println("[缓存] 未命中,执行真实方法:" + method.getName());
Object result = proxy.invokeSuper(obj, args);
// 存入缓存
cache.put(cacheKey, result);
System.out.println("[缓存] 已存储:" + cacheKey);
return result;
}
}
第三步:创建代理生成器
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyGenerator {
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> targetClass) {
Enhancer enhancer = new Enhancer();
// 设置父类(被代理的类)
enhancer.setSuperclass(targetClass);
// 设置回调(方法拦截器)
enhancer.setCallback(new CacheMethodInterceptor());
// 创建代理实例
return (T) enhancer.create();
}
}
第四步:客户端调用
public class CglibDemo {
public static void main(String[] args) {
DataQueryService proxy = CglibProxyGenerator.createProxy(DataQueryService.class);
// 第一次调用:执行真实方法
String result1 = proxy.queryFromDatabase("SELECT * FROM users");
System.out.println("结果 1: " + result1);
System.out.println("---");
// 第二次调用:命中缓存
String result2 = proxy.queryFromDatabase("SELECT * FROM users");
System.out.println("结果 2: " + result2);
// final 方法无法被代理,直接调用父类实现
System.out.println("---");
String config = proxy.querySystemConfig("timeout");
System.out.println("配置:" + config);
}
}
输出结果:
[缓存] 未命中,执行真实方法:queryFromDatabase
执行数据库查询:SELECT * FROM users
[缓存] 已存储:queryFromDatabase:SELECT * FROM users
结果 1: Result of: SELECT * FROM users
---
[缓存] 命中:queryFromDatabase:SELECT * FROM users
结果 2: Result of: SELECT * FROM users
---
配置:SystemConfig:timeout
重要提示:MethodProxy.invokeSuper() 是 CGLIB 的关键,它通过 FastClass 机制直接调用父类方法,避免了反射的性能损耗。如果误用 method.invoke(obj, args) 会导致循环调用(因为 obj 是代理对象,会再次触发拦截器)。
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现原理 | 实现接口(生成接口的实现类) | 继承类(生成目标类的子类) |
| 强制要求 | 目标类必须实现接口 | 目标类不能是 final 类 |
| 方法限制 | 接口定义的所有方法 | 非 final、非 private 方法 |
| 核心类 | Proxy、InvocationHandler | Enhancer、MethodInterceptor |
| 方法调用 | Java 反射(相对较慢) | FastClass 机制(方法索引,更快) |
| 包归属 | JDK 内置(java.lang.reflect) | 第三方库(net.sf.cglib) |
| 生成类名 | com.sun.proxy.$Proxy0 | Target$$EnhancerByCGLIB$$hash |
性能表现:
选型建议:
if (目标类实现了接口) {
// 优先使用 JDK 动态代理(Spring 默认策略)
} else {
// 使用 CGLIB 动态代理
}
// Spring 强制使用 CGLIB 的配置
@EnableAspectJAutoProxy(proxyTargetClass = true)
| 特性 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类生成时机 | 编译期(预先编写 .java 文件) | 运行期(内存中生成字节码) |
| 实现方式 | 手动编写代理类,需与目标类实现同一接口,一对一绑定 | 无需手动编写代理类,通过 Handler/Interceptor 封装增强逻辑,一对多复用 |
| 代码维护成本 | 高(接口变更需同步修改代理类) | 低(与接口解耦,逻辑复用) |
| 灵活性 | 低(一对一绑定) | 高(一个处理器服务多个目标类) |
| 编码工作量 | 代码量大(目标类越多,代理类越多),维护成本高;接口新增方法时,目标类与代理类需同步修改 | 代码量极少(通用增强逻辑可复用),维护性好;与接口解耦,接口变更不影响代理逻辑 |
| 学习曲线 | 平缓(纯 Java 语法) | 较陡(需理解反射/字节码) |
| 典型应用 | 设计模式学习、简单装饰场景 | 框架开发(AOP、RPC、ORM) |
Proxy + InvocationHandler,官方标准方案Enhancer + MethodInterceptor,解决无接口类的代理问题// 保存 JDK 动态代理类
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 保存 CGLIB 代理类
System.setProperty("cglib.debugLocation", "/path/to/output");
掌握代理模式不仅是理解 Spring AOP、MyBatis、Dubbo 等框架的基础,更是写出高扩展性、低耦合代码的重要技能。建议通过手写 RPC 框架或简易 AOP 来加深理解。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online