概述 优点 传递属性 服务器定义的策略 计算服务器 代理 面向对象的代码重用与设计模式 与现有服务器的连接 JDBC --连接数据库 JNI --本机方法 体系结构 保密与安全 防火墙 RMI应用在演变的企业中 结论 |
概述
Java Remote Method Invocation ( RMI -- Java远程方法调用)允许 您使用Java编写分布式对象。本文将介绍RMI的优点以及 如何将其连接到现有的和原有的系统中,以及与用Java 编写的组件的连接。
RMI为采用Java对象的分布式计算提供了简单而直接的 途径。这些对象可以是新的Java对象,也可以是围绕现 有API的简单的Java包装程序。Java体现了“编写一次就 能在任何地方运行的模式。而RMI可将Java模式进行扩展 ,使之可在任何地方运行”。
因为RMI是以Java为核心的,所以,它将Java的安全性 和可移植性等强大功能带给了分布式计算。您可将代理 和梢?务逻辑等属性移动到网络中最合适的地方。如果您 要扩展Java在系统中的使用,RMI将使您充分利用其强大 功能。
RMI可利用标准Java本机方法接口JNI与现有的和原有的 系统相连接。RMI还可利用标准JDBC包与现有的关系数据 库连接。RMI/JNI和RMI/JDBC相结合,可帮助您利用RMI与目 前使用非Java语言的现有服务器进行通信,而且在您需 要时可扩展Java在这些服务器上的使用。RMI可帮助您在 扩展使用时充分利用Java的强大功能。
优点
从最基本的角度看,RMI是Java的远程过程调用(RPC)机 制。与传统的RPC系统相比,RMI具有若干优点,因为它 是Java面向对象方法的一部分。传统的RPC系统采用中性 语言,所以是最普通的系统--它们不能提供所有可能的 目标平台所具有的功能。
RMI以Java为核心,可与采用本机方法与现有系统相连 接。这就是说,RMI可采用自然、直接和功能全面的方式 为您提供分布式计算技术,而这种技术可帮助您以不断 递增和无缝的方式为整个系统添加Java功能。
RMI的主要优点如下:
前面我们讲到,RMI可以传递属性,并简单介绍了一下 一个有关开支报告程序的情况。下面我们将深入讨论如 何设计这样的系统。这样介绍的目的是使您能够利用RMI 的功能将属性从一个系统传递到另一个系统,并随心所 欲地安排当前的计算地点,并便于将来的改变。下面的 例子并未涉及真实世界可能发生的所有问题,但可帮助 读者了解处理问题的方法。
服务器定义的策略
假定公司关于开支报告的政策发生改变。例如,目前 公司只要求对超过20美元的开支需开具发票。但到明天 ,公司认为这太宽松了,便决定除不超过20美元的餐费 以外,任何开支均需开具发票。如果不能下载属性的话 ,那么在设计便于修改的系统时您可选择下列方法之一 :
这表明,政策永远是动态的。您要想修改政策,就只 需简单地编写通用政策接口的新的执行程序,把它安装 在服务器上,并对服务器进行配置以返回这种新类型的 对象即可。这样,每台客户机都会根据新的政策对新的 开支报告进行检查。
这是一种比任何静态方法都更好的方法,原因如下:
import java.rmi.*;import语句输入Java RMI包。所有RMI类型均在包java.rmi或 其子包内定义。接口ExpenseServer是一般的Java接口,具有 如下两种有趣的特点:
public interface ExpenseServer extends Remote {
Policy getPolicy() throws RemoteException;
void submitReport(ExpenseReport report)
throws RemoteException, InvalidReportException;
}
public interface Policy {如果该项目有效--即符合当前政策,则该方法可正常返 回。否则就会抛出一个描述该错误的例外。政策接口是 本地的(而非远程的),所以将通过本机对象在客户机上 执行--在客户机的虚拟机上,而非整个网络上运行的对 象。客户机可能运行下列程序:
void checkValid (Expenseentry entry)
throws PolicyViolationException;
}
Policy curPolicy = server.getPolicy();当用户请求客户机软件启动一份新的开支报告时,客户 机就调用server.getPolicy,并要求服务器返回一个包含当 前开支政策的对象。添加的每个项目首先都被提交给该 政策对象,以获得批准。如果政策对象报告无错误,则 该项目就被添加到报告中;否则错误就被显示给用户, 而后者可采取修正措施。当用户完成向报告中添加项目 时,整个报告就被呈交。服务程序如下:
start a new expense report
show the GUI to the user
while (user keeps adding entries) {
try {
curPolicy.checkValid(entry); // throws exception if not OK
add the entry to the expense report
} catch (PolicyViolationException e) {
show the error to the user
}
}
server.submitReport(report);
import java.rmi. *;除基本程序包外,我们还输入RMI的服务程序包。类型UnicastRemoteObject 定义了此服务程序远程对象的类型,在本例中,应为单 一服务程序而非复制服务(下面还会详细介绍)。Java类 ExpenseSeverImpl实现远程接口ExpenseServer的方法。远程主机 的客户机可使用RMI将信息发送给ExpenseServerImpl对象。
import java.rmi.server. *;
class ExpenseServerImpl
extends UnicastRemoteObject
implements ExpenseServer
{
ExpenseServerImpl() throws RemoteException {
// . . . set up server state . . .
}
public Policy getPolicy() {
return new TodaysPolicy();
}
public void submitReport(ExpenseReport report) {
// . . . write the report into the db . . .
}
}
本文中讨论的重要方法是getPolicy,它简单地返回定义 当前政策的对象。下面看一个执行政策的例子:
public class TodaysPolicy implements Policy {TodaysPolicy进行检查的目的是确保无收据的任何项目均少 于20美元。如果将来政策发生变化,仅少于20美元的餐 费可不受“需要收据”政策的限制,则您可提供新的政 策实现:
public void checkValid(ExpenseEntry entry)
throws PolicyViolationException
{
if (entry.dollars()< 20) {
return; // no receipt required
else if (entry.haveReceipt() == false) {
throw new PolicyViolationException;
}
}
}
public class TomorrowsPolicy implements Policy {编写这个类,并把它安装在服务器上,然后告诉服务器 开始提供TomorrowsPolicy对象,而非TodaysPolicy对象,这样 您的整个系统就会开始使用新的政策。当客户机调用服 务器的getPolicy方法时,客户机的RMI就会检查返回的对 象是否为已知类型。每台客户机首次遇到TomorrowsPolicy时 ,RMI就会在getPolicy返回前下载政策的实现。客户机可 轻松地开始增强这个新的政策。
public void checkValid(ExpenseEntry entry)
throws PolicyViolationException
{
if (entry.isMeal()&& entry.dollars() < 20) {
return; // no receipt required
} else if (entry.haveReceipt() == false) {
throw new PolicyViolationException;
}
}
}
RMI使用标准Java对象序列化机制传递对象。引用远程 对象参数作为远程引用传递。如果向某方法提供的参数 为原始类型或本机(非远程)对象,则向服务器传递一个 深副本。返回值也拾?照同样的方式处理,只不过是沿其 它方向。RMI可使用户向本机对象传递和返回完整对象图 并为远程对象传递和返回引用。
在真实的系统中,getPolicy方法可能会有一个可以识别 用户及开支报告类型(差旅、客户关系等)的参数,这样 可使政策加以区别。您或者可以不要求单独的政策和开 支报告对象,但您可以有一种newExpenseReport方法,它可返 回一个直接检查政策的ExpenseReport对象。这最后一种策略 可使您像修改政策一样简单地修改开支报告的内容--当 公司决定需要把餐费划分为早餐、午餐和晚餐项目,而 且像上述修改政策一样简单地执行修改时--可编写一个 实现该报告的新类,客户程序就会自动使用这个类。
计算服务器
开支报告的例子表示了客户机如何从服务器得到属性 。属性可沿两个方向传递--客户机也可将新的类型传递 给用户。最简单的例子就是如图2所示的计算服务器, 该服务程序可执行任意任务,这样整个企业内的客户机 都能利用高端或专用计算机。
public interface Task {运行时,它就会进行一些计算,并返回一个包含结果的 对象。这完全是一般性的任务--几乎所有计算任务都可 在这个接口下实现。远程接口ComputeServer也同样简单:
Object run();
}
import java.rmi.*;这个远程接口的唯一目的就是使客户机创建一个Task (任 务)对象,并把它发送给服务器执行,最后返回结果。 该服务器的基本实现如下:
public interface ComputeServer extends Remote {
Object compute(Task task) throws RemoteException;
}
import java.rmi.*;如果您看一看compute方法就会发现,它非常简单。它只 是接受传递给它的对象,并返回计算的结果。main方法 包括服务器的启动代码--它安装了RMI的缺省安全管理程 序,以防止他人存取本地系统,并创建可处理进入的请 求的ComputeServerImpl对象,并将其与名字"ComputeServer"关联 。这时,服务器已经准备好接收要执行的任务,而main 也完成了其设置。
import java.rmi.server.*;
public class ComputeServerImpl
extends UnicastRemoteObject
implements ComputeServer
{
public ComputeServerImpl() throws RemoteException { }
public Object compute(Task task) {
return task.run();
}
public static void main(String[] args) throws Exception {
// use the default, restrictive security manager
System.setSecurityManager(new RMISecurityManager());
ComputeServerImpl server = new ComputeServerImpl();
Naming.rebind("ComputeServer", server);
System.out.println("Ready to receive tasks");
return;
}
}
如上所述,这实际上是一种全面和实用的服务。系统 可以得到改进,比如,可添加要计算的参数,从而对使 用服务程序的部门进行计费。但在很多情况下,上述接 口和及其实现允许使用高端计算机进行远程计算。这又?明 了RMI的简单性--如果您键入上述类,对其进行编译,并 启动服务程序,您就拥有了能执行任意任务的运行计算 服务器。
下面介绍一个使用这种计算服务的例子。假定您购买 了一个能运行大量计算操作应用程序的非常高端的系统 。管理员可在该系统上启动一个Java虚拟机,运行ComputeServerImpl 对象。该对象现在就可接受要运行的任务。
现在假定一个小组准备通过一组数据培训一个神经网 络,以帮助制订采购策略。他们可以采用的步骤如下:
这个简单的计算服务器体系结构为系统的分布式功能 提供了功能强大的转换能力。一项任务的计算可以被转 移到一个能为其提供最好支持的系统上完成。这样的系 统可以被用来:
因为RMI允许使用Java实现下载属性,所以您可使用RMI 编写代理系统。代理的最简单格式如下:
import java.rmi.*;启动一个代理也就创建了实现Agent (代理)接口、找到服 务器、激活接受该代理对象的类。该代理的执行程序将 被下载到服务器上运行。accept方法将启动一个该代理的 新线程,激活其run方法,然后返回,从而使该代理一直 执行到run方法返回为止。代理可通过激活在另一台主机 上运行的服务程序的accept方法而移植到该主机,而其本 身则作为将被接受的代理来传递,并结束其在原来主机 上的线程。
public interface AgentServer extends Remote {
void accept(Agent agent)
throws RemoteException, InvalidAgentException;
}
public interface Agent extends java.io.Serializable {
void run();
}
面向对象的代码重用与设计 模式
面向对象的编程是一项允许代码重用的强大技术。很 多企业组织都使用面向对象的编程来减轻创建程序的负 担和提高系统的灵活性。RMI是完全面向对象的--信息被 发送给远程对象,而且对象可以被传递和返回。
Design Patterns (设计模式)目前在描述面向对象设计的 实践活动中获得了相当大的成功。首先是因为Design Patterns 的创新工作而使之大受欢迎,这些编程方式是一种正式 描述解决某类特定问题的完整方法的途径。所有这些设 计模式都依赖于创建一个或多个抽象概念,这些抽象概 念允许不同的实现,从而允许和增强软件重用。软件重 用是面向对象技术的主要优势,而设计模式则是促进重 用的最受欢迎的技术之一。
所有设计模式都依赖面向对象的多态性--这是对象( 如Task )拥有多个实现的能力。算法的普通部分(如compute 方法)不必知道使用了哪个实现,它只需知道得到一个 对象后应该对该对象采取什么操作。特别地,计算服务 器就是Command (指令)模式的一个例子,它可使您将请求 (任务)表示为对象,并对其进行调度。
只有当包括执行程序在内的完整对象能在客户机和服 务器之间传递时,才会存在这样的多态性。DCE和DCOM等 传统的RPC系统以及CORBA等基于对象的RPC系统不能下载 并执行程序,因为它们不能把真实对象作为参数传递, 而只能传递数据。
RMI可传递包括执行程序在内的所有类型,所以您可以 在分布式计算解决方案中,而不仅仅是本地计算中使用 面向对象的编程--包括设计方式。如果没有RMI这样完全 面向对象的系统,那么您就必须放弃很多分布式计算系 统中的设计方式--以及面向对象的软件重用的其它形式 。
与现有服务器的连接
人们常说,RMI主要是“从Java到Java”,但这种说法 掩盖了这样一个事实:Java可使用被称为JNI的本机方法 接口,很容易地与现有和原有系统连接。JNI和RMI的混 合使用与任何其它Java程序一样简单。您可使用JDBC,再 结合RMI,与现有的关系数据库连接。也就是说,您可使 用RMI连接二层次和三层次系统--即使双方都不是用Java 编写的亦可。这样做有很大的好处和优势,下面会详细 阐述。但首先让我们看一看它是如何完成的。
假定您现在有一台在关系数据库中存储了有关客户订 单信息的服务器。在任何多层次系统中,您都得设计一 个远程接口,以便于客户机访问服务器--利用作为Remote 接口的RMI:
import java.rmi.*;java.sql包包含JDBC包。每个远程方法均可被服务器采用实 际数据库的JDBC调用,或通过采用其它数据库访问机制 的本机方法实现。上面所示的方法返回一个Order (订单) 对象的Vector (列表)。Order (订单)就是在您的系统中定 义的、用来保存客户订单的类。
import java.sql.SQLException;
import java.util.Vector;
public interface OrderServer extends Remote {
Vector getUnpaid() throws RemoteException, SQLException;
void shutDown() throws RemoteException;
// ... other methods (getOrderNumber, getShipped, ...)
}
本节将介绍如何使用JDBC实现getUnpaid,和如何使用JNI 实施shutDown。
JDBC -- 连接数据库
使用JDBC实现getUnpaid的OrderServerImpl如下:
import java.rmi.*;其中大多是JDBC任务。除了以Order开始的类型是您的系 统的一部分类型以外,您所看到的所有类型均为JDBC或 RMI的一部分。构造函数初始化OrderServerImpl对象,创建与 jdbc URL中所规定的数据库的连接( Connection)。有了这个 连接,我们就可以使用prepareStatement定义一个能找到所 有未付款订单的查询。在此还可为其它方法定义其它查 询。OrderServerImpl作为数据库在同一个系统上运行,而且 还可能是在同一个进程中(下面将讨论shutDown )。
import java.rmi.server.*;
import java.sql.*;
import java.util.Vector;
public class OrderServerImpl
extends UnicastRemoteObject
implements OrderServer
{
Connection db; // connection to the db
PreparedStatement unpaidQuery; // unpaid order query
OrderServerImpl() throws RemoteException, SQLException {
db = DriverManager.getConnection("jdbc:odbc:orders");
unpaidQuery = db.preparedStatement("…");
}
public Vector getUnpaid() throws SQLException {
ResultSet results = unpaidQuery.executeQuery();
Vector list = new Vector();
while (results.next())
list.addElement(new Order(results));
return list;
}
public native void shutDown();
}
当getUnpaid方法在RMI服务器对象OrderServerImpl上被调用 后,就会执行预先编译的查询,并返回包含所有匹配元 素的JDBC ResultSet对象。随后,我们为结果集中的每个项 目创建新的Order对象,并将其添加到Vector对象中( Java 的动态数组)。在结束读取结果后,我们将这个向量返 回给客户机,后者可将结果显示给用户,或者其他相关 的人。
JNI -- 本机方法
RMI服务器和客户机可利用本机方法与现有的和原有的 系统连接。您可使用本机方法实现不能直接访问数据库 的远程方法,或者通过采用现有代码更简单地实现。您 可使用本机接口JNI编写C和C++程序,以实现?Java方法并 Java对象上调用该方法。用本机方法实现shutDown的程序 如下:
JNIEXPORT void JNICALL这是假定了现有服务器通过其API定义的DataSet类型得到 了引用。指向服务器DataSet的指针存储在dataSet域中。当 客户机调用shutDown时,服务器的shutDown方法就会被调用 。因为在服务器实现中声明了要用本机方法来实现shutDown 方法,所以,RMI将直接调用这个本机方法。这个本机方 法找到对象的dataSet域,得到其值,并用它调用现有API 的函数DSshutDown。
Java_OrderServerImpl_shutDown(JNIEnv *env, jobject this)
{
jclass cls;
jfieldID fid;
DataSet *ds;
cls = (*env)->GetObjectClass(env, this);
fid = (*env)->GetFieldID(env, cls, "dataSet", "J");
ds = (DataSet *) (*env)->GetObjectField(env, this, fid);
/* With a DataSet pointer we can use the original API */
DSshutDown(ds);
}
Sun公司目前正与ILOG公司合作,开发一种称作TwinPeaks 的产品。TwinPeaks将能够兼容目前的C和C++ API,并生成 Java类,该Java类包含了到Java类中API的调用。这样,您 就能从Java调用现有的任何API。TwinPeaks面市后,将有可 能完全使用Java (而非JNI调用)编写诸如shutDown这样的方 法。
体系结构
RMI系统可为分布式面向对象计算提供简单而又直接的 基础。其体系结构可允许对服务器和引用类型进行扩展 ,从而使RMI能以连续的方式添加功能。
当服务程序被输出后,其引用类型就被定义。在上面 的例子中,我们将服务器作为UnicastRemoteObject服务器输 出,即点到点非复制服务器。对这些对象的引用对于这 类服务器非常合适。不同类型的服务器有不同的引用句 法。例如,MulticastRemoteObject就有允许复制服务的引用句 法。
保密与安全
在执行RMI请求时,安全是绝对有保证的。RMI可在客 户机与服务器之间提供安全信道,并可将下载的执行程 序放入安全的“沙箱(sandbox)”中运行,从而保护您的系 统免遭不明客户机可能的攻击。
首先,必须定义您的安全需求,这非常重要。如果您 正在安全的企业网内部执行诸如ComputeServer这样的程序 ,则您只需知道谁在使用计算环路即可,这样您就能对 滥用系统的人进行跟踪。如果您需要提供商业计算服务 器,则您就需要防止多种恶意的破坏。这些都会影响接 口的设计--在企业内部,您可能只要求每个Task对象都 配备人名和部门编号,以便跟踪。而在商业领域中,您 可能需要更高的安全性,包括数字签名身份识别和某些 能帮助您剔除会耗费超过分配于其时间的恶意任务的合 同语言。
在客户机和服务器之间您可能需要有一个安全信道。 RMI可使您能提供可创建包括加密插口(socket)在内的您所 需要的任何类型插口的插口工厂。从JDK 1.2开始,您将 能够指定服务器插口所提供的服务的要求(通过给出对 这些要求的描述)。这种新技术可在小应用程序上采用 ,而多数浏览器都拒绝承诺设置插口工厂。插口要求可 包括加密和其它要求。
下载的类也存在安全问题。Java通过SecurityManager对象 处理安全问题,而该对象可传递所有与安全有关操作的 判断,如打开文件和网络连接等。RMI通过要求您在输出 任何服务对象或在服务器上调用任何方法之前安装安全 管理器,以使用这个标准的Java机制。RMI提供与小应用 程序(无文件存取,只是连接到发出主机等)限制相同的 RMISecurityManager类型。这样可防止下载的执行程序从计算 机上读取或写入数据、或与防火墙后面的其它系统连接 。您也可编写和安装自己的安全管理程序对象,以执行 不同的安全限制。
防火墙
RMI为防火墙后面的客户机提供了与远程服务器进行通 信的方法。这样可使您使用RMI在因特网上部署客户程序 ,如在用于万维网上的小应用程序中。穿越客户机的防 火墙会使通信速度降低,所以RMI成功地采用速度最快的 技术连接客户机与服务器。当客户机首次依次尝试下列 三种可能的方法,试图与服务器建立通信时,该技术即 被UnicastRemoteObject的引用所发现:
这种三步策略使客户机能够以尽可能高的效率实现通 信,并大多使用直接插口连接。在没有安装防火墙的系 统上,或在企业内部防火墙后的通信中,客户机将使用 插口直接连接到服务器上。二级通信技术的速度比直接 通信明显地要慢得多,但允许您编写能在因特网和万维 网上广泛使用的客户程序。
RMI应用在演变的企业中
您现在就可使用RMI连接新的Java应用程序(或小应用 程序)和现有的服务器。在这种情况下,您的企业可随 着Java用途的不断扩展而不断获益。如果您的系统的一部 分是用Java重新编写的,则RMI可使Java的优势从现有Java 组件转移到新的Java程序中。请考虑下面二层次系统中 单一请求在客户机和服务器之间往复转移的路径:
与上图相反,下图表示语言中性系统采用接口定义语 言(通常称作IDL )在客户机和服务器之间实现连接:
当您在企业的多数环境中使用Java时,这种损失就更 加巨大。使用RMI,您的整个系统中就能获得Java的好处 :
结论
RMI为真正面向对象的分布式计算提供了可靠的平台。 您可使用RMI连接到Java组件,或用其它语言编写的现有 的组件。随着Java在您的环境中所具备的重要性的日益 增加,您还可扩大Java的使用范围,并获得所有的好处 --无需移植、低维护成本和安全而保密的环境。RMI为您 提供了循序渐进地将Java扩展到您的系统所有部分的平 台,您可根据需要适时地添加Java服务器和客户机。只 要您添加了Java,那么它所有的好处都会随之而来。RMI 则使之更简单、保密和强大。
源地址:http://docs.huihoo.com/java/rmi/whitepage/index.html