代理模式:掌控对象访问的优雅之道
引言:设计模式的重要性
在软件开发中,设计模式是解决常见问题的可复用方案 ,它们如同建筑师的蓝图,为开发者提供了经过验证的最佳实践。在23种经典设计模式中,代理模式因其独特的访问控制能力 和增强功能特性而脱颖而出。代理模式不仅广泛应用于日常开发,更是构建高性能、可维护系统的关键组件。无论是实现延迟加载、访问控制,还是简化复杂系统结构,代理模式都发挥着不可替代的作用。
什么是代理模式?
定义与核心思想
代理模式(Proxy Pattern)是一种结构型设计模式 ,它提供了一个代理对象,用来控制对其他对象的访问 。代理模式的核心思想是:在不改变原始类的情况下,通过引入代理类来增强功能。
通俗地说,代理模式就像明星的经纪人:当你想联系明星时,你需要先通过经纪人。经纪人可以决定是否安排见面、过滤不合理的请求,甚至在必要时代为处理某些事务。
模式结构
代理模式包含三个核心角色:
- 抽象主题(Subject):定义真实主题和代理主题的公共接口
- 真实主题(Real Subject):实现具体业务逻辑的实际对象
- 代理(Proxy):持有对真实主题的引用,控制对真实主题的访问
持有引用 <<interface>> Subject +request() RealSubject +request() Proxy -realSubject: RealSubject +request()
代理模式的分类
1. 静态代理
在编译期就确定代理关系的模式,需要手动创建代理类。
特点:
- 实现简单直观
- 需要为每个被代理类创建代理类
- 代码冗余度高
2. 动态代理
在运行时动态生成代理类的模式,无需手动编写代理类。
细分类型:
- JDK动态代理:基于接口实现,使用Java反射机制
- CGLIB动态代理:基于字节码操作,可代理普通类
3. 按功能分类
- 远程代理:为不同地址空间的对象提供本地代表
- 虚拟代理:延迟创建开销大的对象
- 保护代理:控制对原始对象的访问权限
- 缓存代理:为开销大的运算结果提供缓存
- 智能引用代理:在对象被引用时执行额外操作
静态代理实现详解
场景:文件加载系统
考虑一个文件加载系统,我们需要在加载文件前进行权限验证,加载后记录日志。
类结构:
java
// 抽象主题:文件加载接口
public interface FileLoader {
void loadFile(String filename);
}
// 真实主题:实际文件加载器
public class RealFileLoader implements FileLoader {
@Override
public void loadFile(String filename) {
System.out.println("正在加载文件: " + filename);
// 实际的文件加载操作...
}
}
// 代理类:增强的文件加载器
public class FileLoaderProxy implements FileLoader {
private RealFileLoader realFileLoader;
public FileLoaderProxy() {
this.realFileLoader = new RealFileLoader();
}
@Override
public void loadFile(String filename) {
// 前置增强:权限验证
if (!checkAccess()) {
System.out.println("访问被拒绝!");
return;
}
// 调用真实对象的方法
realFileLoader.loadFile(filename);
// 后置增强:日志记录
logAccess(filename);
}
private boolean checkAccess() {
System.out.println("正在验证权限...");
// 实际权限验证逻辑
return true; // 简化为始终返回true
}
private void logAccess(String filename) {
System.out.println("文件访问已记录: " + filename);
}
}
客户端使用
java
public class Client {
public static void main(String[] args) {
FileLoader loader = new FileLoaderProxy();
loader.loadFile("重要文档.pdf");
}
}
输出:
正在验证权限...
正在加载文件: 重要文档.pdf
文件访问已记录: 重要文档.pdf
静态代理的优缺点
优点:
- 职责清晰,符合单一职责原则
- 在不修改目标对象的前提下扩展功能
- 开闭原则的良好实践
缺点:
- 代理类和目标类需实现相同接口
- 每个服务类都需要创建代理类,导致类数量增加
- 接口变更时,目标类和代理类都需要修改
动态代理深入解析
JDK动态代理
基于Java反射机制,在运行时动态创建代理类。
实现步骤:
- 定义
InvocationHandler
接口实现类 - 使用
Proxy.newProxyInstance()
创建代理对象 - 通过代理对象调用方法
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 调用处理器
public class LoggingHandler implements InvocationHandler {
private 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());
// 调用真实对象的方法
Object result = method.invoke(target, args);
// 后置处理
System.out.println("【日志】方法执行完成: " + method.getName());
return result;
}
// 创建代理对象
public static Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingHandler(target)
);
}
}
客户端使用:
java
public class Client {
public static void main(String[] args) {
FileLoader realLoader = new RealFileLoader();
FileLoader proxy = (FileLoader) LoggingHandler.createProxy(realLoader);
proxy.loadFile("年度报告.pdf");
}
}
输出:
【日志】开始执行方法: loadFile
正在加载文件: 年度报告.pdf
【日志】方法执行完成: loadFile
CGLIB动态代理
适用于没有实现接口的类,通过操作字节码生成子类代理。
添加Maven依赖:
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
实现代码:
java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
// 创建代理对象
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("【CGLIB代理】方法调用前: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("【CGLIB代理】方法调用后: " + method.getName());
return result;
}
}
JDK代理 vs CGLIB代理
特性 | JDK动态代理 | CGLIB代理 |
---|---|---|
实现基础 | Java反射机制 | 字节码操作 |
代理对象要求 | 必须实现接口 | 可代理普通类 |
性能 | 调用方法较慢 | 创建代理慢,执行快 |
依赖 | Java标准库 | 需要第三方库 |
方法拦截 | 通过InvocationHandler | 通过MethodInterceptor |
生成方式 | 运行时生成接口实现类 | 运行时生成目标类的子类 |
代理模式的应用场景
- 访问控制:控制对敏感对象的访问权限
- 延迟初始化:推迟创建开销大的对象
- 本地代理:代表远程对象(如RPC调用)
- 日志记录:方法调用前后自动记录日志
- 缓存代理:缓存结果避免重复计算
- 智能引用:对象引用计数,自动释放资源
- AOP实现:切面编程的基础
代理模式的优缺点
优点
- 职责清晰:真实对象只需关注核心逻辑
- 高扩展性:无需修改目标对象即可增强功能
- 访问控制:保护真实对象不被非法访问
- 性能优化:通过延迟加载提高系统响应速度
- 解耦合:客户端与真实对象解耦
缺点
- 系统复杂度增加:引入了额外的代理层
- 性能开销:代理调用增加处理时间(特别是动态代理)
- 实现复杂度:动态代理使用较复杂
- 代码可读性:可能降低代码的直观性
与其他模式的关系
代理 vs 装饰器
- 相似点:都通过组合增强对象功能
- 区别 :
- 代理控制访问,装饰器增加行为
- 代理通常预先确定关系,装饰器可动态组合
- 代理通常管理生命周期,装饰器只增加功能
代理 vs 适配器
- 适配器解决接口不兼容问题
- 代理保持接口一致,控制访问
代理 vs 门面
- 门面简化复杂系统的接口
- 代理控制对单个对象的访问
实际应用案例
Spring框架中的代理
Spring AOP的核心就是代理模式:
- JDK代理:用于接口实现的Bean
- CGLIB代理:用于普通类
- 通过
@Transactional
、@Cacheable
等注解实现声明式事务和缓存
MyBatis中的代理
- Mapper接口通过
MapperProxy
实现动态代理 - 将接口方法调用转换为SQL执行
RPC框架
- 远程服务调用通过本地代理对象实现
- 客户端像调用本地方法一样调用远程服务
总结
代理模式是对象访问控制的优雅解决方案,它通过引入代理层在不修改原始对象的前提下实现了功能的增强和访问的控制。无论是静态代理的直观简单,还是动态代理的灵活强大,代理模式都为我们提供了处理复杂场景的有效工具。
在现代框架中,代理模式无处不在:Spring AOP的切面编程、MyBatis的Mapper代理、RPC的远程调用等都深度依赖代理模式。掌握代理模式不仅能帮助我们理解这些框架的原理,更能提升我们设计高质量系统的能力。
选择代理类型时需权衡需求:简单场景可选静态代理;需要灵活扩展时,JDK代理适合接口实现,CGLIB代理适合普通类;特定功能场景可使用远程、虚拟等专用代理。
作为开发者,我们应当理解代理模式的本质------控制与增强,而非盲目套用。恰当地使用代理模式,可以让我们的系统更加灵活、安全和高效。