一、代理模式简介
代理模式(Proxy Pattern) 是一种结构型设计模式,其核心思想是通过代理对象来间接访问真实对象,从而实现对真实对象的控制和扩展。代理模式通常用于延迟加载、权限控制、日志记录、性能监控等场景。
代理模式是软件设计模式中的一种,具体属于结构型设计模式。
- 软件设计模式: 针对软件开发过程中常见问题的通用解决方案
- 结构型设计模式:结构型设计模式关注如何组织和组合类或对象,以便在不改变其功能的情况下提高系统的灵活性或可维护性。它们主要涉及对象之间的关系和如何将这些对象组合起来使用。
- 代理模式作为结构型模式的一种,它是通过引入一个代理对象来控制对原始对象的访问,从而在不改变原始对象的前提下,增加额外的功能。代理对象和原始对象通常都实现同一个接口,因此它们在结构上是相似的,可以互换。
代理模式是非常常见的模式,在生活中的例子也非常多,例如你不好意思向你关系不太好朋友帮个忙,这时需要找一个和它关系好的应一个朋友帮忙转达,这个中间朋友就是代理对象。例如购买火车票不一定要去火车站买,可以通过12306网站或者去火车票代售点买。
二、发展
代理模式(Proxy Pattern)作为一种设计模式,起源于软件工程中的结构型模式。它通过引入代理对象来控制对某个对象的访问。代理模式的应用在软件开发中经历了几个重要的发展阶段,并随着技术和需求的演变,逐渐形成了多种变体。以下是代理模式在软件开发中的发展历程:
1. 早期的代理模式(20世纪60-80年代)
在计算机科学的早期,代理模式的基本思想最早出现在操作系统和硬件资源管理领域。在资源有限的计算环境中,操作系统常常通过代理来管理硬件设备的访问,避免直接操作硬件资源。例如,通过“虚拟设备驱动程序”来管理硬件设备访问。
随着面向对象编程(OOP)的发展,代理模式开始成为软件设计中的一个重要概念。虽然这个时期的代理模式没有像现代设计模式那样被广泛讨论,但其核心思想开始在一些小规模软件项目中使用,主要是通过引入中间层对象来代理资源访问,控制访问逻辑和延迟初始化。
2. 设计模式的正式引入(1980年代末)
1987年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 发表了《Design Patterns: Elements of Reusable Object-Oriented Software》一书,首次正式将代理模式作为一种标准的设计模式提出。这本书被誉为“设计模式的圣经”,代理模式成为四大经典结构型设计模式之一。
代理模式被定义为:通过引入代理对象来控制对真实对象的访问。代理对象可以在客户端和真实对象之间充当中介角色,代理模式根据不同的需求有不同的实现方式:
- 虚拟代理:延迟实例化和加载目标对象,直到真正需要时才进行创建。
- 远程代理:用于控制访问远程对象,常见于分布式系统中的客户端-服务器通信。
- 保护代理:通过控制权限来管理对目标对象的访问,常用于安全性管理。
- 智能代理:除了正常的代理行为外,还能够提供额外的功能,例如计数、缓存等。
3. 分布式系统中的代理应用(1990年代)
随着计算机网络的普及和分布式计算的兴起,代理模式的应用逐渐扩展到远程通信和网络中。在这个时期,代理模式主要用于:
- 远程代理:在分布式系统中,代理模式被广泛应用于远程过程调用(RPC)系统中。远程代理在客户端和服务器之间充当中介,帮助客户端与远程对象进行通信,隐藏了网络通信的复杂性。
- Web代理:随着Web应用的快速发展,Web代理(例如HTTP代理)开始流行。代理模式在网络请求的转发、缓存、负载均衡等方面发挥重要作用。
4. 面向切面编程与动态代理(2000年代)
进入21世纪后,代理模式在现代编程语言中的应用得到了进一步的拓展,尤其是在面向切面编程(AOP)中。AOP框架常常通过动态代理来实现横切关注点的分离,如事务管理、日志记录和安全验证等功能。
-
动态代理:在Java等语言中,代理模式得到了进一步的应用,尤其是在动态代理方面。Java中的
java.lang.reflect.Proxy类允许在运行时创建代理对象,通过反射机制在代理对象上进行方法调用。动态代理的一个典型应用场景是AOP(面向切面编程),如Spring框架中的动态代理实现。 -
静态代理与动态代理:
- 静态代理:代理类在编译时就已经确定,代理类和目标类具有相同的接口。
- 动态代理:代理类在运行时动态生成,可以在不修改目标类的情况下,动态地增强目标对象的行为。
5. 云计算与微服务架构中的代理模式(2010年代至今)
随着云计算、微服务架构和容器技术的迅猛发展,代理模式在现代软件架构中的应用愈发广泛,主要体现在以下几个方面:
- API网关:在微服务架构中,API网关常常作为反向代理,管理客户端和多个后端微服务之间的通信。API网关负责路由请求、负载均衡、认证、授权等功能,它充当了多个微服务的入口。
- 服务网格:服务网格(如Istio)利用代理模式来增强微服务之间的通信,提供流量管理、安全性、监控和故障恢复等功能。服务网格中的代理(sidecar代理)部署在每个微服务旁边,拦截并管理进入和离开的流量。
- 反向代理与负载均衡:反向代理用于将客户端的请求转发到后端服务器,并对请求进行负载均衡,提升系统的可靠性和性能。
- 缓存代理:在Web应用中,代理模式还被广泛用于缓存,减少对目标服务器的请求,提高系统响应速度。例如,CDN(内容分发网络)就是利用代理缓存来加速全球用户的访问速度。
三、特点
3.1 优点
- 增加额外功能:代理模式可以在不修改真实对象的情况下为其增加额外的功能,如日志记录、事务处理、安全控制等。
- 解耦:客户端通过代理对象访问真实对象,使得客户端与真实对象之间的耦合度降低。
- 延迟加载:通过代理类可以控制目标对象的创建时机,延迟对象的初始化。
3.2 缺点
- 增加复杂性:代理模式通过增加代理对象,可能会使系统结构更加复杂。
- 性能开销:使用代理模式时,额外的代理层会带来一定的性能开销,尤其是在方法调用较为频繁时。
四、功能
- 代理模式通过为真实对象(通常被称为
RealSubject)创建一个代理对象(Proxy),来控制对该对象的访问。代理对象通常会做一些附加的操作,例如:权限控制、延迟加载、缓存、日志记录等,最后通过代理对象将请求转发到真实对象。
举个例子:
假设有一个 Subject 接口,RealSubject 是它的一个实现类,Proxy 也是 Subject 的一个实现类。
RealSubject:实际的业务逻辑执行者,例如在数据库中查询数据。Proxy:它代理RealSubject,比如可以添加一些附加操作(如检查权限、日志记录等),然后将请求转发给RealSubject。
五、分类
- 虚拟代理(Virtual Proxy):用于延迟加载对象,只有在真正需要时才实例化目标对象。常用于需要大量资源的对象,比如大图片或视频文件。
- 远程代理(Remote Proxy):用于对象位于不同地址空间(通常在不同服务器)时,代理对象代表远程对象进行访问。例如,RMI(Remote Method Invocation)或Web服务中的代理。
- 保护代理(Protective Proxy):控制访问权限,保护目标对象。例如,限制某些用户访问敏感操作或数据。
- 智能代理(Smart Proxy):增强目标对象的功能,如引用计数、缓存等,或提供其他附加服务。
六、代理模式的结构
代理模式一般包含以下角色:
- Subject(抽象主题) :定义了真实对象和代理对象的共同接口,声明代理对象和真实对象都应该实现的方法。
- RealSubject(真实主题) :实现了
Subject 接口,是真正执行任务的对象。 - Proxy(代理对象) :持有一个
RealSubject 对象的引用,通过代理对象来间接调用RealSubject 的方法,可以在方法执行前后添加额外的逻辑。 - Client(客户端) :通过代理对象来调用真实对象的方法。
七、实现方式
在 Java 中,代理模式主要有两种实现方式:
- 静态代理(Static Proxy)
- 动态代理(Dynamic Proxy)
7.1 静态代理(Static Proxy)
静态代理是通过在编译时生成代理类来实现的。代理类需要实现与目标类相同的接口,代理类内部持有目标类的实例,并通过代理类调用目标类的方法。
静态代理是指代理类和真实类在编译时就已经确定,代理类和真实类实现相同的接口,代理类持有真实对象的引用,并在代理方法中转发对真实对象方法的调用。静态代理的实现通常需要手动编写代理类。
实现步骤:
- 创建一个接口
Subject,目标类RealSubject 实现这个接口。 - 创建一个代理类
Proxy,它也实现Subject 接口,并持有RealSubject 的实例。 - 在代理类中,控制对目标对象的访问,可能会进行一些附加操作(如日志、权限控制等)。
示例代码(静态代理):
// 1. 定义接口
public interface Subject {
void request();
}
// 2. 目标类实现接口
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Request made to RealSubject.");
}
}
// 3. 代理类实现接口,并持有目标类的引用
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
// 可以在此添加附加操作
System.out.println("Proxy: Before delegating to RealSubject.");
realSubject.request();
System.out.println("Proxy: After delegating to RealSubject.");
}
}
// 4. 客户端代码
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.request();
}
}
输出:
Proxy: Before delegating to RealSubject.
Request made to RealSubject.
Proxy: After delegating to RealSubject.
7.2 动态代理(Dynamic Proxy)
动态代理是在运行时创建代理对象,不需要在编译时生成代理类。Java 提供了两种动态代理方式:
- JDK 动态代理(基于接口的代理)
- CGLIB 动态代理(基于类的代理)
JDK 动态代理
动态代理是通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口来动态创建代理对象的。在运行时,可以生成代理类,并将目标对象传递给代理类来实现代理功能。动态代理无需提前定义代理类,提供了更高的灵活性。
实现步骤:
- 定义一个接口
Subject。 - 实现目标类
RealSubject,并实现该接口。 - 创建一个
InvocationHandler,它在目标方法调用前后进行处理。 - 使用
Proxy.newProxyInstance() 动态创建代理类。
示例代码(动态代理):
import java.lang.reflect.*;
// 1. 定义接口
public interface Subject {
void request();
}
// 2. 目标类实现接口
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Request made to RealSubject.");
}
}
// 3. 动态代理处理器
class ProxyHandler implements InvocationHandler {
private Object target;
public ProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理方法前的附加操作
System.out.println("Proxy: Before invoking method.");
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 代理方法后的附加操作
System.out.println("Proxy: After invoking method.");
return result;
}
}
// 4. 客户端代码
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
// 创建动态代理实例
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
new ProxyHandler(realSubject));
proxy.request(); // 调用代理方法
}
}
输出:
Proxy: Before invoking method.
Request made to RealSubject.
Proxy: After invoking method.
八、应用场景
代理模式可以应用于许多不同的场景,以下是一些常见的应用场景:
1. 远程代理(Remote Proxy)
- 分布式系统:当客户端和服务器端位于不同的地址空间时,可以使用远程代理来封装网络通信细节,使得客户端就像直接调用本地方法一样简单。
- RMI(远程方法调用) :Java RMI就是一个典型的远程代理应用案例,它允许一个Java虚拟机上的对象调用另一个Java虚拟机上的对象的方法。
2. 虚拟代理(Virtual Proxy)
- 延迟加载:用于按需创建开销较大的对象。例如,在图形界面中,如果某个组件只有在用户滚动到特定位置时才可见,那么可以在实际显示之前不创建该组件,而是在需要时通过代理创建并初始化。
- 图像加载:对于大型图片或从网络加载的图片,可以通过代理模式先展示一个小图标或占位符,然后在后台加载真实的图片,加载完成后替换占位符。
3. 保护代理(Protection Proxy)
- 权限控制:在某些情况下,可能希望根据用户的身份或角色限制对特定资源的访问。保护代理可以在请求到达目标对象之前检查用户的权限,并决定是否允许执行操作。
4. 智能引用(Smart Reference)
- 计数器:智能引用代理可以在每次访问对象时增加计数,以跟踪对象被引用的次数,这对于垃圾回收机制特别有用。
- 日志记录:每当对象被访问时,代理可以记录访问信息,如时间戳、访问者身份等,这有助于审计和调试。
5. 缓存代理(Cache Proxy)
- 性能优化:缓存代理可以在第一次请求后保存结果,后续相同的请求可以直接返回缓存的数据,而不是每次都重新计算或查询数据库,从而提高响应速度。
6. 防火墙代理(Firewall Proxy)
- 安全过滤:防火墙代理可以用来防止非法数据进入内部系统,确保只有经过验证的数据才能传递给真实对象。
7. 复杂对象的简化接口
- API封装:有时候,真实对象提供的接口可能过于复杂或不适合外部使用。此时,可以使用代理来提供一个更简洁、更容易使用的接口给客户端。
8. AOP(面向切面编程)
- 横切关注点:AOP框架通常使用代理模式来实现对业务逻辑的增强,比如事务管理、日志记录、性能监控等功能,这些功能不需要修改原始业务代码即可实现。
通过这些应用场景可以看出,代理模式是一个非常灵活的设计模式,它可以有效地解耦系统组件之间的依赖关系,同时还能为对象交互提供更多的控制和灵活性。