9.1简介
在本课中,我们将讨论一个结构模式,代理模式。 代理模式为另一个对象提供代理或占位符来控制对其的访问。
代理模式具有许多不同的变体。 一些重要的变体是远程代理,虚拟代理和保护代理。 在本课中,我们将更多地了解这些变体,我们将用Java实现它们中的每一个。 但在我们这样做之前,让我们了解更多关于代理模式的内容。
9.2什么是代理模式
代理模式用于创建一个代表性的对象,用于控制对另一个对象的访问,这对于创建或需要保护而言可能是远程的,昂贵的。
控制访问对象的一个原因是延迟其创建和初始化的全部成本,直到我们实际需要使用它为止。 另一个原因可能是作为一个居住在不同JVM中的对象的当地代表。 代理在控制对原始对象的访问方面非常有用,特别是当对象应具有不同的访问权限时。
在代理模式中,客户端不直接与原始对象通话,而是将其委托给调用原始对象方法的代理对象。 重要的一点是,客户端不知道代理服务器,代理服务器作为客户端的原始对象。 但是这种方法有很多变化,我们很快就会看到。
让我们看看代理模式及其重要参与者的结构。
•Proxy:
1a。 保留一个引用,让代理访问真正的主题。 如果RealSubject和Subject接口相同,Proxy可能会引用一个Subject。
1b。 提供与主题相同的界面,以便代理可以替代真实主题。
1c。 控制对真实主题的访问,并可能负责创建和删除它。
•Subject:
2a。 定义RealSubject和Proxy的通用接口,以便可以在RealSubject所期望的任何位置使用代理。
•RealSubject:
3a。 定义代理所代表的真实对象。
代理模式有三种主要的变体:
•远程代理为不同地址空间中的对象提供本地代理。
•虚拟代理按需创建昂贵的对象。
•保护代理控制对原始对象的访问。 当对象应具有不同的访问权限时,保护代理非常有用。
我们将在下一节中逐一讨论这些代理。
9.3远程代理
有一家比萨公司,在不同地点设有分店。公司的所有者从公司的工作人员那里得到各个销售点的每日报告。 Pizza公司支持的当前应用程序是桌面应用程序,而不是Web应用程序。因此,店主必须要求员工生成报告并将其发送给他。但是现在,主人想要自己生成并检查报告,以便在任何人无需任何帮助的情况下,只要他想要就能生成报告。店主希望你为他开发一个应用程序。
这里的问题是所有的应用程序都运行在它们各自的JVM上,Report Checker应用程序(我们将尽快设计)应该在所有者的本地系统中运行。生成报告所需的对象不在所有者的系统JVM中,并且不能直接调用远程对象。
远程代理被用来解决这个问题。我们知道报告是由用户生成的,因此有一个对象需要生成报告。我们所需要的只是联系驻留在远程位置的对象,以获得我们想要的结果。远程代理服务器充当远程对象的本地代表。远程对象是位于不同JVM的堆中的对象。您可以将方法调用到将该调用转发给远程对象的本地对象。
您的客户端对象就像进行远程方法调用一样。但它调用堆本地代理对象上的方法来处理网络通信的所有低级细节。
Java支持使用RMI驻留在两个不同位置(或两个不同的JVM)的两个对象之间的通信。 RMI是远程方法调用,用于构建客户端和服务帮助对象,直至使用与远程服务相同的方法创建客户端帮助对象。使用RMI,您不必自己编写任何网络或I / O代码。通过您的客户端,您可以调用远程方法,就像在客户端本地JVM中运行的对象的常规方法调用一样。
RMI还提供运行基础结构以使其全部工作,其中包括客户端可用于查找和访问远程对象的查找服务。 RMI调用和本地方法调用之间有一个区别。客户端帮助程序通过网络发送方法调用,因此存在涉及RMI调用的网络和I / O。
现在我们来看看代码。我们有一个接口ReportGenerator及其具体实现ReportGener atorImpl已经在不同位置的JVM上运行。首先要创建一个远程服务,我们需要更改代码。
ReportGenerator接口现在看起来像这样:
package com.javacodegeeks.patterns.proxypattern.remoteproxy;import java.rmi.Remote;
import java.rmi.RemoteException;
public interface ReportGenerator extends Remote{
public String generateDailyReport() throws RemoteException;
}
这是一个定义客户端可以远程调用的方法的远程接口。 这是客户将用作服务的类类型。 存根和实际服务都将执行此操作。 接口中的方法返回一个String对象。 您可以返回方法中的任何对象; 这个对象将通过从服务器到客户端的线路发送,所以它必须是可序列化的。 请注意,此接口中的所有方法都应抛出RemoteException。
package com.javacodegeeks.patterns.proxypattern.remoteproxy;
import java.rmi.Naming;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;import java.util.Date;
public class ReportGeneratorImpl extends UnicastRemoteObject implements ReportGenerator{
private static final long serialVersionUID = 3107413009881629428L;
protected ReportGeneratorImpl() throws RemoteException {
}
@Overridepublic String generateDailyReport() throws RemoteException {
StringBuilder sb = new StringBuilder();
sb.append("********************Location X Daily Report********************");
sb.append("\\n Location ID: 012");
sb.append("\\n Today’s Date: "+new Date());
sb.append("\\n Total Pizza’s Sell: 112");
sb.append("\\n Total Price: $2534");
sb.append("\\n Net Profit: $1985");
sb.append("\\n***************************************************************");
return sb.toString();
}public static void main(String[] args) {
try {
ReportGenerator reportGenerator = new ReportGeneratorImpl();
Naming.rebind("PizzaCoRemoteGenerator", reportGenerator);
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面的类是真正的工作的远程实现。这是客户想要调用方法的对象。该类扩展了UnicastRemoteObject,为了作为远程服务对象工作,您的对象需要一些与远程相关的功能。最简单的方法是从java.rmi.server包中扩展UnicastRemoteObject,并让该类为您完成工作。
UnicastRemoteObject类构造函数抛出一个RemoteException,因此您需要编写一个声明RemoteException的无参数构造函数。
要使远程服务可用于客户端,您需要向RMI注册表注册该服务。您可以通过实例化并将其放入RMI注册表中来实现此目的。当您注册实现对象时,RMI系统实际上将存根放入注册表中,因为这是客户端需要的。 Naming.rebind方法用于注册对象。它有两个参数,第一个是用于命名服务的字符串,而另一个参数是由客户端获取以使用该服务的对象。
现在,要创建存根,需要在远程实现类上运行rmic。 rmic工具随Java软件开发小孩一起提供,需要一个服务实现并创建一个新的存根。您应打开命令提示符(cmd)并从远程实施所在的目录运行rmic。
下一步是运行rmiregistry,启动一个终端并启动注册表,只需键入命令rmiregistry。但一定要从可以访问你的类的目录中启动它。
最后一步是启动服务,即运行您的远程类的具体实现,在这种情况下,类是ReportG eneratorImpl。
到目前为止,我们已经创建并运行了一项服务。现在,让我们看看客户端将如何使用它。披萨公司所有者的报告申请将使用该服务来生成和检查报告。我们需要将接口ReportGenerator和存根提供给将使用该服务的客户端。您可以简单地交付该服务中所需的存根和任何其他类或接口。
package com.javacodegeeks.patterns.proxypattern.remoteproxy;import java.rmi.Naming;public class ReportGeneratorClient {
public static void main(String[] args) {
new ReportGeneratorClient().generateReport();
}
public void generateReport(){
try {
ReportGenerator reportGenerator = (ReportGenerator)Naming.lookup("rmi://127.0.0.1/PizzaCoRemoteGenerator");
System.out.println(reportGenerator.generateDailyReport());
} catch (Exception e) {
}
e.printStackTrace();
}
}
上面的程序将有如下结果:
上面的类进行命名查找并检索用于生成每日报告的对象。 您需要提供主机名的IP和用于绑定服务的名称。 它的其余部分看起来像一个普通的旧方法调用。
总而言之,远程代理服务器充当位于不同JVM中的对象的本地代理。 对代理的方法调用导致呼叫通过有线传输,远程调用,结果返回给代理,然后返回给客户端。