GoF23:Proxy-代理
1、代理模式
1.1、场景
场景:客户不能或不想直接访问目标对象,需要一个代理来实现间接引用。
说明 | 示例 | |
---|---|---|
客户端 (Client) |
通过访问代理,间接访问真实主题的业务方法 | 找房子的人 |
抽象主题 (Subject) |
定义一个接口或抽象类,声明业务方法 | 租房业务 |
真实主题 (RealSubject) |
客户端实际访问的对象 | 房东 |
代理 (Proxy) |
引用真实主题,对其访问、控制和扩展 | 房屋中介 |
1.2、模式定义
Proxy Pattern
给某个对象提供一个代理,以控制对该对象的访问。
-
优点:
- 保护、扩展真实主题。
- 解耦:将客户端与真实主题分离,面向抽象编程。
-
缺点:
- 增加系统复杂度。
- 降低请求处理速度。
类型:
根据代理类的创建时期,分为静态代理和动态代理。
静态代理(Static) | 动态代理(Dynamic) | |
---|---|---|
创建时期 | 程序运行前 | 程序运行时 |
被代理对象 | 真实主题(类) | 抽象主题(接口) |
创建方式 | 给每个真实主题,手动创建对应的代理类 | 反射机制,运行时自动创建代理类 |
实现接口 | 抽象主题接口 | InvocationHandler 接口 |
获取方式 | new 实例化(或工厂模式) | InvocationHandler 的 getProxy() |
2、静态代理
Static Proxy:
程序运行前创建代理对象,代理一个真实主题类。
2.1、类图
- Subject(抽象主题)
- 作为 Proxy 和 RealSubject 的抽象类型,定义业务方法。
- 由于实现同一个抽象类型,Proxy 可以代替 RealSubject 的使用。
- 作为 Proxy 和 RealSubject 的抽象类型,定义业务方法。
- RealSubject(真实主题)
- 实现抽象主题,是客户最终真实访问的目标对象。
- 被 Proxy 代理,控制访问。
- 实现抽象主题,是客户最终真实访问的目标对象。
- Proxy(代理主题)
- 实现抽象主题,需要手动为真实主题类创建对应的代理类。
- 持有 RealSubject 的引用,控制其访问,也可负责其创建和销毁。
- 在
request()
中调用真实主题的方法,并进行增强。
- 实现抽象主题,需要手动为真实主题类创建对应的代理类。
2.2、实现
抽象主题
定义客户需要的业务方法
public interface Subject {
void request();
}
真实主题
实现 Subject 接口的业务方法
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("真实对象:访问真实主题的业务方法...");
}
}
代理类
实现 Subject 接口,引用真实对象
-
request():内部调用真实主题的业务方法,在调用前后可进行扩展。
-
preRequest():扩展功能——预处理(如验证)。
-
postRequest():扩展功能——后置处理(如日志)。
public class Proxy implements Subject { private Subject realSubject; @Override public void request() { setRealSubject(); // 设置真实对象 preRequest(); realSubject.request(); // 真实主题的业务方法 postRequest(); } private void setRealSubject() { if (realSubject == null) { this.realSubject = new RealSubject(); } } private void preRequest() { System.out.println("代理对象:预处理..."); } private void postRequest() { System.out.println("代理对象:后续处理..."); } }
客户端
创建代理对象,调用业务方法
public class Client {
public static void main(String[] args) {
// 创建代理对象
Proxy proxy = new Proxy();
// 调用业务方法
proxy.request();
}
}
3、动态代理
分析:静态代理的缺点
- 类的数量过多:需要为每个真实主题定义一个代理类,导致类的数量翻倍。
- 使用不灵活:必须先有真实主题,才能创建代理类。
Dynamic Proxy:
程序运行时动态创建代理类,代替一个抽象主题。
-
底层技术:👉 反射
-
应用:Spring AOP
-
实现方式:
-
若被代理对象有实现接口,则默认使用 JDK 代理,此时可强制使用 CGLIB 代理。
-
若被代理对象没有实现接口,则必须使用 CGLIB 代理。
JDK 代理 CGLIB 代理 使用前提 目标对象有实现接口 目标对象非 final 修饰 技术 反射机制,生成实现类 asm 开源包,修改字节码创建子类 代理实现 实现接口 继承类并重写方法
-
3.1、类图
相比静态代理,区别在于 Proxy 和 InvocationHandler。
-
Proxy(代理主题):
-
无需手动创建,由 InvocationHandler 利用反射机制创建。
-
不再持有 RealSubject 的引用。
-
对
request()
的方法调用,由 InvocationHandler 的invoke()
响应处理。
-
-
InvocationHandler(调用处理器):
- 持有 RealSubject 的引用。
- 利用反射机制,运行时、动态地为真实主题创建代理类。
- 响应代理类的方法调用。
3.2、实现
抽象主题
定义客户需要的业务方法
public interface Subject {
void request();
}
真实主题
实现 Subject 接口的业务方法
public class RealSubject1 implements Subject {
@Override
public void request() {
System.out.println("真实对象1:访问真实主题的业务方法...");
}
}
InvocationHandler
创建一个类,实现 InnovationHandler 接口。
-
成员变量:定义要代理的抽象主题,通过构造器或 setter 注入。
-
invoke():负责响应代理类的方法调用,方法参数如下
- proxy:代理类
- method:代理类要调用的方法
- args:待调用方法的参数
-
getProxy():提供获取代理对象的方法。
-
扩展功能
public class DynamicProxyHandler implements InvocationHandler { // 抽象主题 private Subject subject; public void setSubject(Subject subject) { this.subject = subject; } // 响应调用 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { preRequest(); Object result = method.invoke(subject, args); postRequest(); return result; } // 获取代理对象 public Subject getProxy() { return (Subject) Proxy .newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); } // 扩展功能 private void preRequest() { System.out.println("代理:预处理"); } private void postRequest() { System.out.println("代理:后续处理"); } }
客户端
创建调用处理器,设置真实主题;
获取代理对象,调用业务方法。
public class Client {
public static void main(String[] args) {
// 创建调用处理器,设置真实主题
DynamicProxyHandler handler = new DynamicProxyHandler();
handler.setObject(new RealSubject1());
// 获取代理对象
Subject proxy = (Subject) handler.getProxy();
// 调用业务方法
proxy.request();
}
}
4、代理工厂
代理模式结合工厂模式,
可以将代理类的创建和使用分离。
示例:针对上文动态代理的案例,引入简单工厂。
-
DynamicProxyHandler 类:去掉
getProxy()
方法。public class DynamicProxyHandler implements InvocationHandler { // 抽象主题 private Subject subject; public void setSubject(Subject subject) {...} // 响应调用 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...} // 获取代理对象 // public Subject getProxy() { // return (Subject) Proxy // .newProxyInstance(object.getClass().getClassLoader(), // object.getClass().getInterfaces(), // this); // } // 扩展功能 private void preRequest() {...} private void postRequest() {...} }
-
创建简单工厂:即原来
getProxy()
的逻辑。public class DynamicProxyFactory { public Subject getProxy(Subject subject) { return (Subject) Proxy .newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new DynamicProxyHandler(subject)); } }
-
客户端:不依赖于 InvocationHandler,直接通过工厂获取代理对象。
public class Client { public static void main(String[] args) { // 从工厂中获取代理对象 RealSubject subject = new RealSubject(); Subject proxy = getProxy(subject); proxy.request(); } }
5、常见应用场景
以下是代理模式的不同场景,本质都是为了保护和增强真实主题。
XX 代理 | 说明 | 示例 |
---|---|---|
远程 | 控制访问远程对象 | 图片代理 |
虚拟 | 控制访问创建开销大的资源 | |
保护 | 基于权限控制对资源的访问 | |
防火墙 | 控制访问网络资源,避免侵害 | 防火墙系统 |
智能引用 | 主题被引用时进行额外动作 | 计算引用次数 |
同步 | 多线程下为主题提供安全访问 | |
缓存代理 | 为开销大的运算结果提供暂时缓存; 允许多个客户共享结果,以减少计算或延迟。 |
Web 服务器代理 |
Copy-On-Write | 延迟对象的复制,直到客户真正需要 | Java 1.5 的 CopyOnWriteArrayList |
复杂隐藏代理 (aka 外观代理) |
隐藏一个类的复杂集合的复杂度,并控制访问 |
- 远程代理
- 虚拟代理
- 保护代理