本文中涉及到的完整代码存放于以下 GitHub 仓库中 LearningCode
1. 理论部分
代理模式(Proxy Pattern):给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代理模式也称委托模式。
1.1 结构与实现
代理模式包含以下 3 个角色:
- Subject(抽象主题角色):
- 职责:定义 RealSubject 和 Subject 的共用接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy。
- 实现:通常声明为接口或抽象类,也可以是具体类。
- Proxy(代理主题角色):
- 职责:包含了对 RealSubject 的引用,从而可以在任何时候操作 RealSubject;实现 Subject 中定义的接口,以便在任何时候替代 RealSubject;可以控制对 RealSubject 的使用,负责在需要的时候创建和删除 RealSubject,并对 RealSubject 的使用加以约束。
- 实现:通常声明为具体类。
- RealSubject(真实主题角色):
- 职责:定义 Proxy 所代表的真实对象,实现了 Subject 中定义的接口,编写了真实的业务逻辑,客户端可以通过 Proxy 间接调用 RealSubject 中定义的接口。
- 实现:通常声明为具体类。
代理模式的 UML 类图如下所示:
1.2 扩展:动态代理
传统代理模式的缺陷:在传统的代理模式中,Subject 和 RealSubject 都应该是事先已经存在的,代理类的接口和所代理方法都已明确指定,也称为静态代理。如果需要为不同的 RealSubject 提供代理类或者代理一个 RealSubject 中的不同方法,都需要增加新的代理类,这将导致系统中的类个数急剧增加。
**动态代理(Dynamic Proxy)**可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法。动态代理的实现依赖于语言特性,例如在 Java 中实现依赖于 Proxy 类。
1.3 优缺点与适用场景
代理模式具有以下优点:
- 能够协调调用者和被调用者,在一定程度上降低了系统的耦合。
- 客户端可以针对 Subject 进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
此外,不同类型的代理模式具有独特优点:
- 远程代理为位于两个不同地址空间的对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高了系统的整体运行效率。
- 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
- 缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
- 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
代理模式存在以下缺点:
- 由于在客户端和真实主题之间增加了代理对,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
- 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。
当需要在访问一个对象时进行一些控制或额外处理时,使用代理模式。下面介绍一些可以适用代理模式的常见情况:
- 远程代理(Remote Proxy):为一个位于不同地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中。
- 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 智能引用代理(Smart Reference Proxy):当一个对象被引用时提供一些额外的操作,例如将对象被调用的次数记录下来等。
2. 实现部分
以 Java 代码为例,演示代理模式的实现。
案例介绍:
2.1 静态代理
定义抽象主题角色——Searcher
public interface Searcher {
String doSearch(String userId, String keyword);
}
定义真实主题角色——RealSearcher
public class RealSearcher implements Searcher{
@Override
public String doSearch(String userId, String keyword) {
System.out.println("用户'" + userId + "'使用关键词'" + keyword + "'查询商务信息!");
return "返回具体内容";
}
}
定义代理主题角色——ProxySearcher
public class ProxySearcher implements Searcher{
private final RealSearcher searcher = new RealSearcher();
private AccessValidator validator;
private Logger logger;
@Override
public String doSearch(String userId, String keyword) {
if (validate(userId)) {
String result = searcher.doSearch(userId, keyword);
log(userId);
return result;
}else {
return null;
}
}
private boolean validate(String userId) {
validator = new AccessValidator();
return validator.validate(userId);
}
private void log(String userId) {
logger = new Logger();
logger.log(userId);
}
}
客户端调用:
public class Main {
public static void main(String[] args) {
// 可以使用环境变量优化 Searcher 实例的获取
Searcher searcher = new ProxySearcher();
System.out.println(searcher.doSearch("杨过", "玉女心经"));
}
}
完整的 UML 类图如下所示:
2.2 动态代理:JDK实现
创建代理工厂
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T getProxy(T t) {
return (T) Proxy.newProxyInstance(
t.getClass().getClassLoader(),
new Class[]{Searcher.class},
new InvocationHandler() {
private AccessValidator validator;
private Logger logger;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
if (method.getName().equals("doSearch") &&
Arrays.equals(method.getParameterTypes(), new Class[]{String.class, String.class})) {
String userId = (String) args[0];
if (validate(userId)) {
Object obj = method.invoke(t, args);
log(userId);
return obj;
}else {
return null;
}
}else {
return method.invoke(t,args);
}
}
private boolean validate(String userId) {
validator = new AccessValidator();
return validator.validate(userId);
}
private void log(String userId) {
logger = new Logger();
logger.log(userId);
}
}
);
}
}
客户端调用
public class Main {
public static void main(String[] args) {
Searcher searcher = ProxyFactory.getProxy(new RealSearcher());
System.out.println(searcher.doSearch("杨过", "玉女心经"));
}
}
2.3 动态代理:CGLIB 实现
基于 JDK 的动态代理要求被代理类至少有一个父接口,对于没有实现接口的类无能为力,而社区的 CGLIB 库支持对任何类进行代理。当然,final方法或者final 类也无能为力。
引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
修改代理工厂:
import java.lang.reflect.Method;
import java.util.Arrays;
public class ProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T getProxy(T t) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Searcher.class);
enhancer.setCallback(new MethodInterceptor() {
private AccessValidator validator;
private Logger logger;
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("doSearch") &&
Arrays.equals(method.getParameterTypes(), new Class[]{String.class, String.class})) {
String userId = (String) args[0];
if (validate(userId)) {
Object obj = method.invoke(t, args);
log(userId);
return obj;
}else {
return null;
}
}else {
return method.invoke(t,args);
}
}
private boolean validate(String userId) {
validator = new AccessValidator();
return validator.validate(userId);
}
private void log(String userId) {
logger = new Logger();
logger.log(userId);
}
});
return (T) enhancer.create();
}
}
3. 参考资料
学习视频:
- 设计模式快速入门 —— 图灵星球TuringPlanet —— 代理模式:https://www.bilibili.com/video/BV1xg4y1T7vh
- Java设计模式详解 —— 黑马程序员 —— 代理模式(P56 ~ P62):https://www.bilibili.com/video/BV1Np4y1z7BU?p=56
- Java设计模式 —— 尚硅谷 —— 代理模式(P91 ~ P95):https://www.bilibili.com/video/BV1G4411c7N4?p=91
学习读物:
- 《设计模式:可复用面向对象软件的基础》—— Erich Gamma 著 —— 李英军 译 —— 第 4.7 节(P155)
- 《Java 设计模式》 —— 刘伟 著 —— 第 15 章(P203)
- 《设计模式之美》—— 王争 著 —— 第 7.1 节(P209)
- 《设计模式之禅》 —— 第 2 版 —— 秦小波 著 —— 第 12 章(P113)
- 《图解设计模式》—— 结城浩 著 —— 杨文轩 译 —— 第 21 章(P249)
电子文献:
- 设计模式教程 —— 菜鸟教程 —— 代理模式:https://www.runoob.com/design-pattern/proxy-pattern.html
- 99+ 种软件模式 —— long2ge —— 代理模式:https://learnku.com/docs/99-software-pattern/proxy-pattern/11963