详解代理模式
引言
在生活中,当我们会遇到买房求职等问题,通常这些问题难以获得信息,比如房源信息或者岗位信息,我们就会去寻找中介,让中介帮忙找这些信息。在这过程中,中介就相当于代理,即我们可以通过代理来访问难以访问或不能访问的对象,这就是代理模式的核心思想。
1.概念
代理模式(Proxy Pattern):给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。
2.模式结构
3.模式分析
Subject:抽象主题角色,声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。核心代码如下:
public abstract class Subject {
public abstract void Request();
}
Proxy:代理主题角色,包含了对真实主题的引用,从而可以在任何时候操作真实主题对象。在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题。代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。核心代码如下:
public class Proxy extends Subject{
private RealSubject realSubject = new RealSubject();//维持一个真实主题对象的引用
public void PreRequest(){
//调用眞实主题对象的方法前需要做的事
}
@Override
public void Request(){
PreRequest();
realSubject.Request();//调用眞实主题对象的方法
PostRequest();
}
public void PostRequest(){
//调用眞实主题对象的方法后需要做的事
}
}
RealSubject:真实主题角色,定义了代理角色所代表的真实对象,在真实主题角色实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。核心代码如下:
public class RealSubject extends Subject{
@Override
public void Request(){
//业务方法具体实现代码
}
}
4.具体实例分析
Image:抽象主题角色,图片接口,提供了未实现的图片显示方法。具体代码如下:
// 抽象主题:图片接口
public interface Image {
public void display();
}
RealImage:真实主题角色,图片类,实现图片接口,并实现了其中的图片显示方法。具体代码如下:
// 真实主题:图片类
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
System.out.println("显示:" + filename);
}
}
ImageProxy:代理主题角色,图片显示的代理类,实现抽象主题Image类,内部维护了一个对真实主题对象的引用,同时利用多线程,当客户访问图片时,先显示原图的缩略图,并在后台启动线程(线程池),加载原图,待原图加载结束后,再显示原图。具体代码如下:
//图片代理类
class ImageProxy implements Image {
private RealImage realImage; // 对真实主题(图片)对象的引用
private String filename;
private ExecutorService executorService; // 线程池
private AtomicBoolean isLargeImageLoaded; // 原子布尔保证线程安全
public ImageProxy(String filename) {
this.filename = filename;
this.realImage = null;
this.executorService = Executors.newCachedThreadPool();
this.isLargeImageLoaded = new AtomicBoolean(false);
this.loadLargeImage(); // 启动加载原图
}
// 模拟加载过程
private void loadLargeImage() {
executorService.submit(() -> {
System.out.println("后端日志:开始加载原图:" + filename);
ImageLoader.loadImage(filename);
realImage = new RealImage(filename);
isLargeImageLoaded.set(true);
System.out.println("后端日志:原图加载完成:" + filename);
});
}
@Override
public void display() {
if (isLargeImageLoaded.get()) {
realImage.display(); // 原图加载完成,显示原图
} else {
// 如果大图还未加载完成,则显示小图的信息
System.out.println("显示缩略图信息: " + "small_" + filename);
}
executorService.shutdown();
}
}
ImageLoader:图片加载器类,图片加载需要一定的时间消耗,使用sleep()方法模拟。具体代码如下:
//图片加载器
class ImageLoader {
public static void loadImage(String filename) {
try {
Thread.sleep(3000); // 加载时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Client:客户端,模拟用户访问页面,多次访问,如果页面图片未加载完成,就先显示图片的缩略图,待加载完成后,页面再显示原图。具体代码如下:
// 客户端
public class Client {
public static void main(String[] args) {
ImageProxy imageProxy = new ImageProxy("image.jpg");
for (int i = 0; i < 6; i++) {
imageProxy.display();//原图未加载成功前显示缩略图,加载成功后显示原图
try {
Thread.sleep(1000);//模拟定时刷新
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行程序,结果如下:
在原图未加载完成时,前4次访问图片都是缩略图;原图加载完成后,后2次访问的都是原图。如果待页面加载时一并将图片加载并传输给前端显示页面,那么页面的加载速度将会大打折扣,而借助代理模式,让页面加载先显示缩略图,代理主题类在后台加载原图,这样页面整体显示效果将会对用户友好很多。
5.模式应用
5.1.SpringAOP
静态代理:在编译时就已经确定代理类和目标类的关系。在Java中,静态代理通常通过创建一个实现了相同接口的代理类来实现,该代理类在内部持有目标对象的引用,并在调用目标对象的方法前后添加额外的处理逻辑。比如上述具体实例就是静态代理。
动态代理:一种在运行时创建代理对象的技术,不需要事先为每个目标类编写代理类。在Java中,动态代理通常使用反射来实现,而SpringAOP采用的是动态代理,在Java8中有两种实现方式:
(1)JDK动态代理:Spring AOP使用java.lang.reflect.Proxy来创建代理对象,要求代理的目标必须实现一个接口。
(2)CGLIB代理:当目标对象没有实现接口时,Spring AOP会使用CGLIB库来创建代理对象,通过在运行时动态生成被代理类的子类来实现代理。
5.2.JavaRMI
RMI 是 Java 提供的一个完善的简单易用的远程方法调用框架,采用客户/服务器通信方式,在服务器上部署了提供各种服务的远程对象,客户端请求访问服务器上远程对象的方法,要求客户端与服务器端都是 Java 程序。
RMI 框架采用代理模式来负责客户与远程对象之间通过 Socket 进行通信的细节。RMI 框架为远程对象分别生成了客户端代理和服务器端代理。位于客户端的代理必被称为存根或桩(Stub),位于服务器端的代理类被称为骨架(Skeleton)。
5.3.EJB等分布式服务
EJB(Enterprise JavaBeans)是Java EE规范的一部分,它提供了一种构建分布式企业级应用的模型。EJB使用代理模式来实现远程通信:
EJB使用了RMI机制来实现远程通信。当EJB部署到EJB容器时,容器会为每个远程接口生成一个桩类。客户端使用这个桩类来调用远程EJB的方法,而这些调用实际上是通过RMI传输的。这样,EJB就可以在不同的Java虚拟机上运行,而客户端和服务器之间的通信就像调用本地对象一样。
5.4.游戏行业
在游戏中,可能会使用代理模式来处理游戏多开问题。通过为每个游戏账号分配不同的IP地址登录,可以避免因同一IP地址登录多个账号而导致的封号问题。
6.模式扩展
代理模式根据使用场景和需求的不同,又可以分为如下几类:
(1)远程代理(RemoteProxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
(2)虚拟代理(Virual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被创建。
(3)保护代理(ProtectProxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
(4)缓冲代理(CacheProxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
(5)智能引用代理(SmartReference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
(6)复制代理(Clone Proxy):维护一个可复制对象的副本,当访问副本时,代理控制对原始对象的访问。(虚拟代理的一种)
(7)防火墙代理(Firewall Proxy):保护目标不被恶意代码访问,通过代理来检查和过滤请求。
(8)同步代理(Synchronization Proxy):在多线程环境中,代理可以控制对原始对象的并发访问。
(9)网络代理(Network Proxy):作为客户端和服务器之间的中介,代理可以管理网络通信,提供缓存、过滤和日志记录等功能。
7.优缺点
代理模式的共同优点:
(1)能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
(2)客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
远程代理的优点:
(1)远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
虚拟代理的优点:
(1)虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
缓冲代理的优点:
(1)缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
保护代理的优点:
(1)保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
代理模式的主要缺点:
(1)由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
(2)实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。
8.适用情况
(1)远程代理适用于:客户端对象需要访问远程主机中的对象。
(2)虚拟代理适用于:需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间。例如一个对象需要很长时间才能完成加载时。
(3)缓冲代理适用于:需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
(4)保护代理适用于:需要控制对一个对象的访问,为不同用户提供不同级别的访问权限。
(5)智能引用代理适用于:需要为一个对象的访问(引用)提供一些额外的操作。