代理模式
从糖果机远程监控说起
公司在全国各地部署了上千台糖果机(GumballMachine),CEO 想在办公室的监视器上看到每台机器当前状态——剩余糖果数量、所在位置、当前状态。
问题是:GumballMachine 运行在不同的 JVM 进程(远程机器)上,你不能直接调用它的方法。
解决方案是创建一个**远程代理**:本地的 GumballMachineProxy 实现和 GumballMachine 相同的接口,当你调用 proxy.getCount() 时,它透明地通过网络转发给真正的机器,拿回结果。监视器(客户端)完全感知不到这个调用穿越了网络。
这是代理模式的第一个经典变体——远程代理。书中还介绍了**虚拟代理**(延迟创建开销大的对象)和**保护代理**(控制访问权限),三者结构相同,意图不同。
🔍 定义
代理模式(Proxy)为其他对象提供一种代理,以控制对这个对象的访问。代理与真实对象实现相同接口,对调用方完全透明。
| 代理类型 |
意图 |
典型场景 |
| 远程代理(Remote Proxy) |
控制对远程对象的访问 |
RPC、RMI、REST 客户端包装 |
| 虚拟代理(Virtual Proxy) |
延迟创建开销大的对象 |
图片懒加载、Hibernate 懒加载 |
| 保护代理(Protection Proxy) |
控制访问权限 |
Spring Security、权限拦截 |
| 缓存代理(Cache Proxy) |
缓存远程/开销大的结果 |
Spring @Cacheable |
⚠️ 不使用代理存在的问题
直接使用 UserService 时,每次都需要手动处理访问控制、日志记录和缓存:
| ProxyBadExample.java |
|---|
| package com.example.structural.proxy;
/**
* 代理模式 - 反例
* 问题:UserServiceImpl 混杂了缓存、鉴权、日志等横切关注点,职责不单一
*/
public class ProxyBadExample {
public static void main(String[] args) {
UserServiceImplBad service = new UserServiceImplBad();
System.out.println(service.findUser(1L));
System.out.println(service.findUser(1L)); // 没有缓存,再次查数据库 ❌
}
}
class UserBad {
private final Long id;
private final String name;
public UserBad(Long id, String name) { this.id = id; this.name = name; }
@Override public String toString() { return "User{id=" + id + ", name=" + name + "}"; }
}
// ❌ 业务逻辑与缓存/日志/鉴权混在一起
class UserServiceImplBad {
public UserBad findUser(Long id) {
// ❌ 缓存逻辑混入业务代码
System.out.println("[缓存检查] 无缓存(每次都要检查)");
// ❌ 日志混入业务代码
System.out.println("[日志] 开始查询用户 " + id);
long start = System.currentTimeMillis();
// 模拟数据库查询
UserBad user = queryFromDb(id);
long elapsed = System.currentTimeMillis() - start;
System.out.println("[日志] 查询完成,耗时 " + elapsed + "ms");
return user;
}
private UserBad queryFromDb(Long id) {
System.out.println("[数据库] SELECT * FROM user WHERE id = " + id);
return new UserBad(id, "张三");
}
}
|
🏗️ 设计模式结构说明
%%{init: {'themeVariables': {'noteBkgColor': 'transparent', 'noteBorderColor': '#768390'}}}%%
classDiagram
classDef default fill:transparent,stroke:#768390
class UserService {
<<interface>>
+getUserById(id Long) User
+updateUser(user User) void
}
class UserServiceImpl {
+getUserById(id Long) User
+updateUser(user User) void
}
class CachingUserServiceProxy {
-real: UserService
-cache: Map
+getUserById(id Long) User
+updateUser(user User) void
}
UserService <|.. UserServiceImpl
UserService <|.. CachingUserServiceProxy
CachingUserServiceProxy o--> UserService
Client ..> UserService
note for UserService "主体接口(Subject)"
note for UserServiceImpl "真实主体(RealSubject)"
note for CachingUserServiceProxy "代理(Proxy)"
note for Client "客户端(Client)"
代理(CachingUserServiceProxy)与真实对象实现相同接口,客户端只依赖接口,无感知地通过代理访问真实对象。
💻 设计模式举例说明
| ProxyExample.java |
|---|
| package com.example.structural.proxy;
import java.util.HashMap;
import java.util.Map;
/**
* 代理模式 - 正例
* CachingUserServiceProxy 在不修改 UserServiceImpl 的情况下添加缓存能力
*/
public class ProxyExample {
public static void main(String[] args) {
// ✅ 代理透明地为真实服务添加缓存
UserService service = new CachingUserServiceProxy(new UserServiceImpl());
System.out.println(service.findUser(1L)); // 第一次:查数据库
System.out.println(service.findUser(1L)); // 第二次:命中缓存 ✅
System.out.println(service.findUser(2L)); // 不同 ID:查数据库
}
}
// 用户领域对象
class User {
private final Long id;
private final String name;
public User(Long id, String name) { this.id = id; this.name = name; }
@Override public String toString() { return "User{id=" + id + ", name=" + name + "}"; }
}
// 服务接口
interface UserService {
User findUser(Long id);
}
// 真实对象:只专注于业务逻辑
class UserServiceImpl implements UserService {
@Override
public User findUser(Long id) {
System.out.println("[数据库] SELECT * FROM user WHERE id = " + id);
return new User(id, "张三" + id);
}
}
// ✅ 代理:在不修改 UserServiceImpl 的前提下添加缓存
class CachingUserServiceProxy implements UserService {
private final UserService target; // 持有真实对象的引用
private final Map<Long, User> cache = new HashMap<>();
public CachingUserServiceProxy(UserService target) { this.target = target; }
@Override
public User findUser(Long id) {
// 前置处理:检查缓存
if (cache.containsKey(id)) {
System.out.println("[缓存] 命中 user:" + id + " ✅");
return cache.get(id);
}
// 委托真实对象处理
User user = target.findUser(id);
// 后置处理:写入缓存
cache.put(id, user);
System.out.println("[缓存] 写入 user:" + id);
return user;
}
}
|
🔄 三种常见代理类型
| 类型 |
创建时机 |
是否需要接口 |
核心 API |
| 静态代理 |
编译期手动编写 |
✅ 需要 |
手动实现接口 |
| JDK 动态代理 |
运行时自动生成 |
✅ 需要 |
Proxy.newProxyInstance() + InvocationHandler |
| CGLIB 动态代理 |
运行时通过字节码生成子类 |
❌ 不需要 |
Enhancer + MethodInterceptor |
静态代理
代理类在**编译期**就已手动编写完成。优点是实现简单、直观;缺点是每新增一个接口方法,代理类也要同步修改,接口越多代理类越多。
| StaticProxyExample.java |
|---|
| package com.example.structural.proxy.static_proxy;
/**
* 静态代理示例
* <p>
* 代理类在编译期就已手动编写完成,每个接口都需要一个独立的代理类。
* 优点:实现简单、直观;缺点:接口越多代理类越多,维护成本随之增加。
*/
public class StaticProxyExample {
public static void main(String[] args) {
// 静态代理:编译期已确定代理类,运行时直接 new
OrderService service = new LoggingOrderProxy(new RealOrderService());
service.createOrder("商品A", 2);
service.cancelOrder(1001L);
}
}
interface OrderService {
Long createOrder(String product, int quantity);
void cancelOrder(Long orderId);
}
// 真实业务类,只专注于核心逻辑
class RealOrderService implements OrderService {
private long nextId = 1000L;
@Override
public Long createOrder(String product, int quantity) {
System.out.printf("[业务] 创建订单:%s x%d%n", product, quantity);
return ++nextId;
}
@Override
public void cancelOrder(Long orderId) {
System.out.println("[业务] 取消订单:" + orderId);
}
}
// ✅ 静态代理:手动编写,实现相同接口,织入日志横切逻辑
class LoggingOrderProxy implements OrderService {
private final OrderService target;
public LoggingOrderProxy(OrderService target) { this.target = target; }
@Override
public Long createOrder(String product, int quantity) {
System.out.printf("[日志] → createOrder(%s, %d)%n", product, quantity);
Long id = target.createOrder(product, quantity);
System.out.println("[日志] ← createOrder 返回 orderId=" + id);
return id;
}
@Override
public void cancelOrder(Long orderId) {
System.out.println("[日志] → cancelOrder(" + orderId + ")");
target.cancelOrder(orderId);
System.out.println("[日志] ← cancelOrder 完成");
}
}
|
JDK 动态代理
利用 java.lang.reflect.Proxy 在**运行时**自动生成代理类,无需为每个接口单独编写代理。所有方法调用都汇聚到 InvocationHandler.invoke() 统一拦截处理,一个 InvocationHandler 可复用于任意接口。
限制:被代理对象必须实现接口(代理类是接口的实现类,不是目标类的子类)。
| JdkDynamicProxyExample.java |
|---|
| package com.example.structural.proxy.jdk_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK 动态代理示例
* <p>
* 使用 java.lang.reflect.Proxy 在运行时自动生成代理类,无需为每个接口手动编写代理类。
* 核心:实现 InvocationHandler,所有方法调用都汇聚到 invoke() 统一处理。
* <p>
* 限制:被代理对象必须实现接口(代理类是接口的实现,不是目标类的子类)。
*/
public class JdkDynamicProxyExample {
public static void main(String[] args) {
OrderService real = new RealOrderService();
// ✅ 同一个 LoggingHandler 可代理任意接口,无需为每个接口单独写代理类
OrderService service = (OrderService) Proxy.newProxyInstance(
real.getClass().getClassLoader(), // 使用目标类的类加载器
new Class[]{OrderService.class}, // 代理的接口列表
new LoggingHandler(real) // 方法调用委托给 InvocationHandler
);
service.createOrder("商品B", 3);
service.cancelOrder(2001L);
}
}
interface OrderService {
Long createOrder(String product, int quantity);
void cancelOrder(Long orderId);
}
class RealOrderService implements OrderService {
private long nextId = 2000L;
@Override
public Long createOrder(String product, int quantity) {
System.out.printf("[业务] 创建订单:%s x%d%n", product, quantity);
return ++nextId;
}
@Override
public void cancelOrder(Long orderId) {
System.out.println("[业务] 取消订单:" + orderId);
}
}
// ✅ 通用拦截器:持有 Object 引用,可为任意类型的目标对象生成代理
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.printf("[日志] → %s(%s)%n", method.getName(), formatArgs(args));
// 反射调用真实方法
Object result = method.invoke(target, args);
// 后置日志
System.out.printf("[日志] ← %s 返回: %s%n", method.getName(), result);
return result;
}
private String formatArgs(Object[] args) {
if (args == null || args.length == 0) return "无参数";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < args.length; i++) {
if (i > 0) sb.append(", ");
sb.append(args[i]);
}
return sb.toString();
}
}
|
CGLIB 动态代理
CGLIB 通过字节码工具在**运行时**生成目标类的**子类**作为代理,无需目标类实现接口。Spring AOP 在目标类没有接口时默认使用此方式(@Transactional、@Cacheable 等本质上都是 CGLIB 代理)。
限制:无法代理 final 类或 final 方法(子类无法覆写)。Java 17+ 运行需要 --add-opens JVM 参数;Spring Boot 项目无需手动配置,框架已处理。
| CglibProxyExample.java |
|---|
| package com.example.structural.proxy.cglib_proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB 动态代理示例
* <p>
* CGLIB 通过字节码生成目标类的子类作为代理,无需目标类实现任何接口。
* Spring AOP 在目标类没有接口时默认使用此方式。
* <p>
* 限制:无法代理 final 类或 final 方法(子类无法覆写)。
* Java 17+ 运行需要 --add-opens java.base/java.lang=ALL-UNNAMED;Spring Boot 已自动处理。
*/
public class CglibProxyExample {
public static void main(String[] args) {
// ✅ CGLIB:直接代理普通类,无需接口——这是与 JDK 代理的核心区别
OrderService service = CglibProxyFactory.createLoggingProxy(new OrderService());
service.createOrder("商品C", 5);
service.cancelOrder(3001L);
}
}
// ✅ 普通类,无需实现任何接口
class OrderService {
private long nextId = 3000L;
public Long createOrder(String product, int quantity) {
System.out.printf("[业务] 创建订单:%s x%d%n", product, quantity);
return ++nextId;
}
public void cancelOrder(Long orderId) {
System.out.println("[业务] 取消订单:" + orderId);
}
}
// CGLIB 代理工厂:使用 Enhancer 生成继承目标类的子类
class CglibProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createLoggingProxy(T target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 继承目标类
enhancer.setCallback(new LoggingInterceptor());
// 生成子类实例(代理 IS-A 目标类,无需接口)
return (T) enhancer.create();
}
}
// 方法拦截器:拦截子类的所有方法调用,织入横切逻辑
class LoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.printf("[日志] → %s(%s)%n", method.getName(), formatArgs(args));
// invokeSuper 调用父类(原始类)的方法,不会触发再次拦截
Object result = proxy.invokeSuper(obj, args);
System.out.printf("[日志] ← %s 返回: %s%n", method.getName(), result);
return result;
}
private String formatArgs(Object[] args) {
if (args == null || args.length == 0) return "无参数";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < args.length; i++) {
if (i > 0) sb.append(", ");
sb.append(args[i]);
}
return sb.toString();
}
}
|
⚖️ 优缺点
优点:
- 在不修改真实对象的前提下,透明地添加访问控制、缓存、日志等横切逻辑
- 符合**开闭原则**:新增横切逻辑只需新增代理类
- 支持延迟初始化(虚拟代理)
缺点:
- 每个接口需要一个代理类(静态代理),代码量增多
- 动态代理增加了一定的反射开销
🔗 与其它模式的关系
相似模式防混淆:
| 模式 |
接口变化? |
对象生命周期 |
主要意图 |
| 代理(Proxy) |
❌ 不变 |
代理通常自行创建/管理真实对象 |
控制访问 |
| 装饰器(Decorator) |
❌ 不变 |
被装饰对象由客户端传入 |
动态增强功能 |
| 适配器(Adapter) |
✅ 改变 |
— |
兼容接口 |
🗂️ 应用场景
- 访问控制(保护代理):调用前检查权限
- 延迟加载(虚拟代理):大对象只在首次访问时才真正创建
- 缓存(缓存代理):对频繁访问的结果进行缓存
- Spring AOP:
@Transactional、@Cacheable、@Async 底层都是动态代理
- MyBatis:Mapper 接口没有实现类,调用时是 JDK 动态代理执行 SQL
🏭 工业视角
从"业务代码被污染"到动态代理的演进
王争在《设计模式之美》中用一个性能计数器(MetricsCollector)的例子清晰地展现了代理模式的动机:当监控、日志等非业务代码直接写在 UserController 里时,业务类的职责就被污染了,替换框架的成本也极高。
静态代理虽然能解耦,但有一个致命缺陷——接口有多少方法,代理类就要重写多少方法,50 个原始类就要写 50 个代理类。动态代理正是为消除这种模板式重复而生的:
| JDK 动态代理:一个 Handler 代理所有接口 |
|---|
| public class MetricsCollectorProxy {
private MetricsCollector metricsCollector = new MetricsCollector();
public Object createProxy(Object proxiedObject) {
Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
return Proxy.newProxyInstance(
proxiedObject.getClass().getClassLoader(),
interfaces,
(proxy, method, args) -> {
long start = System.currentTimeMillis();
Object result = method.invoke(proxiedObject, args);
long cost = System.currentTimeMillis() - start;
metricsCollector.recordRequest(
new RequestInfo(method.getName(), cost, start));
return result;
});
}
}
|
JDK 代理 vs CGLIB 代理的选择依据
JDK 动态代理要求目标类实现接口,代理对象是接口的实现类。CGLIB 通过字节码生成目标类的子类,无需接口,但无法代理 final 类/方法。Spring AOP 的默认策略是:有接口用 JDK 代理,无接口用 CGLIB。Spring Boot 2.x 起默认对所有 Bean 使用 CGLIB,可通过 spring.aop.proxy-target-class=false 切回 JDK 代理。
代理的本质:附加"与业务无关"的横切逻辑
理解代理模式的关键在于区分它和装饰器模式的**意图差异**:
| 模式 |
附加的逻辑与原始类的关系 |
典型场景 |
| 代理(Proxy) |
无关——监控、鉴权、限流、事务,这些与业务逻辑本身无关 |
Spring AOP、RPC stub |
| 装饰器(Decorator) |
相关——对原有功能的增强,如 BufferedInputStream 增强 read() |
Java IO、功能叠加 |
代理 ≠ 装饰器,虽然结构几乎相同
两者代码结构高度相似(都持有同接口的对象引用),但**意图截然不同**。代理关注"访问控制",被代理对象通常由代理自己创建/管理;装饰器关注"功能增强",被装饰对象由调用方传入。Spring AOP 的 @Transactional 是代理(事务与业务无关);Java IO 的 BufferedInputStream 是装饰器(缓冲是对读取功能的增强)。