1. 设计模式之代理模式:不修改原始对象的情况下,添加额外功能
1.1. 介绍
代理模式(Proxy Pattern)是一种结构型设计模式,它通过创建一个代理对象来控制对原始对象的访问。
代理对象可以在调用原始对象的方法前后添加额外的逻辑(如权限检查、缓存、日志记录等),而无需修改原始对象的代码。
代理模式解决的是在直接访问某些对象时可能遇到的问题,例如对象创建成本高、需要安全控制或远程访问等。
类型 | 作用 | 典型应用场景 |
---|---|---|
静态代理 | 手动编写代理类,直接引用原始对象。 | 简单场景,代理逻辑固定。 |
动态代理 | 运行时动态生成代理类(如JDK动态代理、CGLIB)。 | AOP(如Spring的@Transactional)。 |
虚拟代理 | 延迟加载原始对象(如大图片的按需加载)。 | 资源密集型对象的优化。 |
1.2. 优缺点及建议
优点
- 职责分离:代理模式将访问控制与业务逻辑分离。代理对象作为原始对象的“替身”,控制客户端对原始对象的访问。
- 扩展性:可以灵活地添加额外的功能或控制。在不修改原始对象的情况下,通过代理对象添加额外功能(如缓存、延迟加载、权限校验等)。
- 解耦:客户端只需与代理交互,无需直接依赖原始对象。
缺点
- 性能开销:增加了代理层可能会影响请求的处理速度。
- 实现复杂性:某些类型的代理模式实现起来可能较为复杂。
使用建议
- 根据具体需求选择合适的代理类型,如远程代理、虚拟代理、保护代理等。
- 确保代理类与真实对象接口一致,以便客户端透明地使用代理。
- 如果代理逻辑固定且简单,用静态代理。
- 如果需要动态扩展或AOP,用动态代理。
注意事项
- 与适配器模式的区别:适配器模式改变接口,而代理模式不改变接口。
- 与装饰器模式的区别:装饰器模式用于增强功能,代理模式用于控制访问。
对比维度 | 代理模式 | 装饰器模式 |
---|---|---|
目的 | 控制访问,增强非功能性逻辑(如权限、缓存)。 | 动态扩展对象的功能(如加糖、加冰)。 |
关系 | 代理类和原始类实现同一接口,但代理类知道原始类的存在。 | 装饰器和原始类实现同一接口,可嵌套多层。 |
典型应用 | 远程调用、延迟加载、AOP。 | IO流(如BufferedInputStream)。 |
1.3. 实现及相关代码
1.3.1. 静态代理实现
1. 定义接口(抽象主题)
// 抽象主题(接口)
public interface Image {
void display();
}
2. 实现真实主题(原始对象)
// 真实主题(原始对象)
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // 模拟耗时操作
}
private void loadFromDisk() {
System.out.println("加载图片: " + filename);
}
@Override
public void display() {
System.out.println("显示图片: " + filename);
}
}
3. 实现代理类(控制访问)
// 代理类(控制对RealImage的访问)
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
// 延迟加载
realImage = new RealImage(filename);
}
realImage.display();
}
}
4. 客户端调用
public class Client {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
image.display(); // 第一次调用会加载图片
image.display(); // 第二次直接显示(已缓存)
}
}
输出结果:
加载图片: test.jpg
显示图片: test.jpg
显示图片: test.jpg
1.3.2. 动态代理(JDK实现)
动态代理无需手动编写代理类,而是在运行时动态生成。
1. 定义InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ImageInvocationHandler implements InvocationHandler {
// 原始对象
private Object target;
public ImageInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理前置操作(如权限校验)");
// 调用原始方法
Object result = method.invoke(target, args);
System.out.println("代理后置操作(如日志记录)");
return result;
}
}
2. 生成动态代理对象
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
RealImage realImage = new RealImage("test.jpg");
Image proxyImage = (Image) Proxy.newProxyInstance(
realImage.getClass().getClassLoader(),
realImage.getClass().getInterfaces(),
new ImageInvocationHandler(realImage)
);
proxyImage.display();
}
}
输出结果:
代理前置操作(如权限校验)
加载图片: test.jpg
显示图片: test.jpg
代理后置操作(如日志记录)
1.4. 代理模式的应用场景
- 远程代理(如RPC调用)
- 客户端通过代理访问远程服务(如Dubbo的Stub)。
- 虚拟代理(延迟加载)
- 按需加载大资源(如图片、文件)。
- 保护代理(权限控制)
- 在访问敏感对象前进行权限校验。
- 缓存代理
- 缓存原始对象的结果(如Redis缓存代理数据库查询)。
- AOP编程
- Spring的
@Transactional
、@Cacheable
等基于动态代理实现。
- Spring的
1.5. 总结
- 核心价值:通过代理对象控制对原始对象的访问,实现解耦和功能增强。
- 静态代理:简单直接,但需手动编写代理类。
- 动态代理:灵活(JDK/CGLIB),适合AOP场景。
- 适用场景:权限控制、缓存、延迟加载、日志记录等。