代理模式:控制对象访问的中间层设计
一、模式核心:通过代理对象控制对目标对象的访问
在软件开发中,有时需要为对象添加一个 "代理" 来控制对它的访问,例如:
- 远程代理:访问远程对象时(如 RPC 调用),本地代理对象伪装成远程对象
- 虚拟代理:延迟加载目标对象(如图片未加载时显示占位符)
- 保护代理:控制对敏感对象的访问权限(如用户权限校验)
** 代理模式(Proxy Pattern)** 通过引入代理类,在不修改目标对象的前提下,为其添加额外的逻辑或控制访问,核心解决:
- 访问控制:在目标对象访问前后添加预处理逻辑
- 资源优化:延迟初始化昂贵的目标对象
- 职责分离:将非业务逻辑(如日志、事务)从目标对象中剥离
核心思想与 UML 类图(PlantUML 语法)
代理模式包含目标接口(Subject)、目标对象(RealSubject)和代理对象(Proxy),代理对象与目标对象实现相同接口,客户端通过代理间接访问目标:

二、核心实现:虚拟代理实现图片延迟加载
1. 定义目标接口(图片)
java
public interface Image {
void display(); // 显示图片
}
2. 实现真实主题(实际图片对象,加载耗时)
java
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName); // 模拟加载耗时操作
}
private void loadFromDisk(String fileName) {
System.out.println("加载图片:" + fileName);
try {
Thread.sleep(1000); // 模拟IO延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void display() {
System.out.println("显示图片:" + fileName);
}
}
3. 实现代理对象(延迟加载,先显示占位符)
java
public class ImageProxy implements Image {
private String fileName;
private RealImage realImage; // 真实对象延迟初始化
public ImageProxy(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
System.out.println("显示占位符..."); // 前置处理
realImage = new RealImage(fileName); // 首次调用时加载真实对象
}
realImage.display(); // 调用真实对象的显示方法
System.out.println("记录访问日志:" + fileName); // 后置处理
}
}
4. 客户端通过代理访问目标对象
java
public class ClientDemo {
public static void main(String[] args) {
// 使用代理对象,无需直接创建RealImage
Image image = new ImageProxy("large_image.jpg");
System.out.println("第一次显示图片:");
image.display(); // 触发延迟加载
System.out.println("\n第二次显示图片:");
image.display(); // 直接使用缓存的RealImage
}
}
输出结果:
plaintext
第一次显示图片:
显示占位符...
加载图片:large_image.jpg
显示图片:large_image.jpg
记录访问日志:large_image.jpg
第二次显示图片:
显示图片:large_image.jpg
记录访问日志:large_image.jpg
三、进阶:实现分布式代理与动态代理
1. 远程代理(模拟 RPC 调用)
java
// 远程服务接口(目标接口)
public interface RemoteService {
String process(String request);
}
// 本地代理对象(伪装成远程服务)
public class RemoteServiceProxy implements RemoteService {
private String serverUrl;
public RemoteServiceProxy(String serverUrl) {
this.serverUrl = serverUrl;
}
@Override
public String process(String request) {
// 通过HTTP/Netty发送请求到远程服务器
return sendRequestToServer(request);
}
private String sendRequestToServer(String request) {
System.out.println("发送远程请求:" + request + " 到 " + serverUrl);
// 实际实现需处理网络IO和序列化
return "远程响应:" + request.toUpperCase();
}
}
2. 动态代理(基于 Java 反射,无需手动编写代理类)
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object target; // 目标对象
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理前置处理:" + method.getName());
Object result = method.invoke(target, args); // 调用目标对象方法
System.out.println("动态代理后置处理:" + method.getName());
return result;
}
}
// 创建动态代理示例
public class DynamicProxyDemo {
public static void main(String[] args) {
RealImage realImage = new RealImage("small_image.jpg");
// 生成动态代理对象
Image proxy = (Image) Proxy.newProxyInstance(
Image.class.getClassLoader(),
new Class<?>[] { Image.class },
new DynamicProxyHandler(realImage)
);
proxy.display(); // 通过代理调用目标方法
}
}
3. 可视化代理流程
graph LR
A[客户端请求] --> B[代理对象接收请求]
B --> C[代理执行前置逻辑]
C --> D[调用真实对象]
D --> E[真实对象处理请求]
E --> F[代理执行后置逻辑]
F --> G[返回结果给客户端]
四、框架与源码中的代理模式实践
1. Spring AOP(动态代理实现切面编程)
- 通过
JdkDynamicAopProxy
或CglibAopProxy
生成代理对象,织入切面逻辑
java
// 代理对象调用目标方法时,自动执行@Before、@After等增强
@Service
public class UserService {
@Transactional
public void updateUser(User user) {
// 业务逻辑
}
}
2. MyBatis 映射器(Mapper Proxy)
MapperProxy
作为代理对象,将接口方法映射为 SQL 语句执行
java
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getUser(int id);
}
// MyBatis创建MapperProxy代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUser(1); // 代理对象处理SQL执行
3. JavaScript 代理(Proxy 对象)
- ES6 提供
Proxy
用于创建代理对象,拦截目标对象的操作
javascript
const realObject = { name: "Alice" };
const proxy = new Proxy(realObject, {
get(target, property) {
console.log(`访问属性:${property}`);
return target[property];
}
});
console.log(proxy.name); // 输出:访问属性:name Alice
五、避坑指南:正确使用代理模式的 3 个要点
1. 区分代理与装饰器模式
- 代理模式:代理对象控制对目标对象的访问(目标对象可能不存在或需控制访问)
- 装饰器模式:装饰对象增强目标对象的功能(目标对象必须存在且功能需扩展)
2. 处理代理对象的线程安全
- 若代理对象持有目标对象的引用,需确保多线程环境下的线程安全
java
public class ThreadSafeProxy implements Subject {
private RealSubject realSubject = new RealSubject();
private final Object lock = new Object();
@Override
public void request() {
synchronized (lock) {
realSubject.request();
}
}
}
3. 避免代理链过长
- 多层代理可能导致性能损耗和调试困难,建议:
- 合并同类代理(如日志代理与权限代理合并为一个复合代理)
- 使用 AOP 替代多层手动代理
4. 反模式:滥用代理导致复杂度上升
- 对于简单的功能增强,直接修改目标对象可能更高效,无需引入代理
六、总结:何时该用代理模式?
适用场景 | 核心特征 | 典型案例 |
---|---|---|
远程对象访问 | 目标对象位于远程服务器,需通过网络调用 | RPC 框架、微服务客户端 |
延迟加载与缓存 | 目标对象创建昂贵,需延迟初始化 | 图片 / 视频加载、大文件处理 |
权限控制与日志记录 | 需要在目标对象访问前后添加通用逻辑 | 权限系统、事务管理、性能监控 |
接口伪装与隔离 | 需要为复杂系统提供简单接口或伪装实现细节 | 模拟对象(测试场景)、遗留系统适配 |
代理模式通过 "中间层控制 + 解耦访问" 的设计,为对象访问添加了灵活的控制层,是构建可扩展、易维护系统的重要模式。下一篇我们将深入探讨策略模式,解析如何动态切换算法实现,敬请期待!
扩展思考:静态代理 vs 动态代理
类型 | 实现方式 | 灵活性 | 适用场景 |
---|---|---|---|
静态代理 | 手动编写代理类(编译期确定) | 低(代理类与目标类强绑定) | 简单场景、无需频繁修改代理逻辑 |
动态代理 | 通过反射或字节码生成(运行时创建) | 高(可动态适配多个目标类) | 框架级代理(如 AOP、ORM) |
理解这种差异,能帮助我们在不同场景下选择更合适的代理实现方式。