Java远程方法调用

来源:互联网 发布:网页指定数据抓取 编辑:程序博客网 时间:2024/03/29 02:46
概述

  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可将完整的对象作为参数和返回值进行传递,而不仅仅是预定义的数据类型。也就是说,您可以将类似Java哈希表这样的复杂类型作为一个参数进行传递。而在目前的RPC系统中,您只能依靠客户机将此类对象分解成基本数据类型,然后传递这些数据类型,最后在服务器端重新创建哈希表。RMI则不需额外的客户程序代码(将对象分解成基本数据类型),直接跨网传递对象。
 可移动属性:RMI可将属性(类实现程序)从客户机移动到服务器,或者从服务器移到客户机。例如,您可以定义一个检查雇员开支报告的接口,以便察看雇员是否遵守了公司目前实行的政策。在开支报告创建后,客户机就会从服务器端获得实现该接口的对象。如果政策发生变化,服务器端就会开始返回使用了新政策的该接口的另一个实现程序。您不必在用户系统上安装任何新的软件就能在客户端检查限制条件--从而向用户提供烁快的反馈,并降低服务器的工作量。这样就能具备最大的灵活性,因为政策改变时只需要您编写一个新的Java类,并将其在服务器主机上安装一次即可。
 设计方式:对象传递功能使您可以在分布式计算中充分利用面向对象技术的强大功能,如二层和三层结构系统。如果您能够传递属性,那么您就可以在您的解决方案中使用面向对象的设计方式。所有面向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象--包括实现和类型--就会失去设计方式上所提供的优点。
 安  全:RMI使用Java内置的安全机制保证下载执行程序时用户系统的安全。RMI使用专门为保护系统免遭恶意小应用程序侵害而设计的安全管理程序,可保护您的系统和网络免遭潜在的恶意下载程序的破坏。在情况严重时,服务器可拒绝下载任何执行程序。
便于编写和使用:RMI使得Java远程服务程序和访问这些服务程序的Java客户程序的编写工作变得轻松、简单。远程接口实际上就是Java接口。服务程序大约用三行指令宣布本身是服务程序,其它方面则与任何其它Java对象类似。这种简单方法便于快速编写完整的分布式对象系统的服务程序,并快速地制做软件的原型和早期版本,以便于进行测试和评估。因为RMI程序编写简单,所以维护也简单。
 可连接现有/原有的系统:RMI可通过Java的本机方法接口JNI与现有系统进行进行交互。利用RMI和JNI,您就能用Java语言编写客户端程序,还能使用现有的服务器端程序。在使用RMI/JNI与现有服务器连接时,您可以有选择地用Java重新编写服务程序的任何部分,并使新的程序充分发挥Java的功能。类似地,RMI可利用JDBC、在不修改使用数据库的现有非Java源代码的前提下与现有关系数据库进行交互。
 编写一次,到处运行:RMI是Java“编写一次,到处运行”方法的一部分。任何基于RMI的系统均可100%地移植到任何Java虚拟机上,RMI/JDBC系统也不例外。如果使用RMI/JNI与现有系统进行交互工作,则采用JNI编写的代码可与任何Java虚拟机进行编译、运行。
 分布式垃圾收集:RMI采用其分布式垃圾收集功能收集不再被网络中任何客户程序所引用的远程服务对象。与Java 虚拟机内部的垃圾收集类似,分布式垃圾收集功能允许用户根据自己的需要定义服务器对象,并且明确这些对象在不再被客户机引用时会被删除。
 并行计算:RMI采用多线程处理方法,可使您的服务器利用这些Java线程更好地并行处理客户端的请求。Java分布式计算解决方案:RMI从JDK1.1开始就是Java平台的核心部分,因此,它存在于任何一台1.1 Java虚拟机中。所有RMI系统均采用相同的公开协议,所以,所有Java系统均可直接相互对话,而不必事先对协议进行转换。

传递属性


  前面我们讲到,RMI可以传递属性,并简单介绍了一下一个有关开支报告程序的情况。下面我们将深入讨论如何设计这样的系统。这样介绍的目的是使您能够利用RMI的功能将属性从一个系统传递到另一个系统,并随心所欲地安排当前的计算地点,并便于将来的改变。下面的例子并未涉及真实世界可能发生的所有问题,但可帮助读者了解处理问题的方法。

服务器定义的策略

  图1是可进行动态配置的开支报告系统的示意图。客户机向用户显示图形用户界面(GUI),用户填写开支报告。客户机程序使用RMI与服务器进行通信。服务器使用JDBC( Java关系数据库连接包)将开支报告存储在数据库中。至此,这看起来与其它多层次系统大同小异,但有一个重大区别--RMI能下载属性。
假定公司关于开支报告的政策发生改变。例如,目前公司只要求对超过20美元的开支需开具发票。但到明天,公司认为这太宽松了,便决定除不超过20美元的餐费以外,任何开支均需开具发票。如果不能下载属性的话,那么在设计便于修改的系统时您可选择下列方法之一:

  用客户端安装与政策有关的程序。政策改变时,必须更新包含此政策的所有客户端程序。您可在若干服务器上安装客户程序,并要求所有用户从这些服务器之一运行客户程序,从而减少问题。但这仍不能彻底解决问题--那些让程序运行好几天的用户就无法使程序更新,而总是会有一些用户为了方便而把软件复制到本地磁盘上。
  您可要求服务器在每次向开支报告添加项目时检查政策。但这样就会在客户机和服务器之间产生大量数据流,并增加服务器的工作量。这还会使系统变得更加脆弱--网络故障会立即妨碍用户,而不仅仅是只在其呈交开支报告或启动新的报告时对其产生影响。同时,添加项目的速度也会变慢,因为这需要穿越整个网络往返一圈才能到达(不堪重负的)服务器。
您可在呈交报告时要求服务器对政策进行检查。这样就会使用户创建很多必须待批报告的错误项目,而不是立刻捕捉到第一个错误,从而使用户有机会停止制造错误。为避免浪费时间,用户需要立刻得到有关错误的反馈。
  有了RMI,您就能以简单的方法调用程序从服务器得到属性,从而提供了一种灵活的方式,将计算任务从服务器卸载到客户机上,同时为用户提供速度更快的反馈。当用户准备编写一份新的开支报告时,客户机就会向服务器要求一个对象,该对象嵌入了适用于该开支报告的当前政策,就如同通过用Java编写的政策接口所表示的那样。该对象可以以任何方式实现当前政策。如果这是客户机RMI首次看到这种专门执行的政策,就会要求服务器提供一份执行过程的副本。如果执行过程将来发生变化,则一种新的政策对象将被返回给客户机,而RMI运行时则会要求得到新的执行过程。
  这表明,政策永远是动态的。您要想修改政策,就只需简单地编写通用政策接口的新的执行程序,把它安装在服务器上,并对服务器进行配置以返回这种新类型的对象即可。这样,每台客户机都会根据新的政策对新的开支报告进行检查。

这是一种比任何静态方法都更好的方法,原因如下:


  所有客户机不必暂停或用新的软件来升级--软件可根据需要在不工作时加以更新。
  服务器不必参与项目检查工作,该工作可在本地完成。
  允许动态限制,因为对象执行程序(而不仅仅是数据)是在客户机和服务器之间进行传递的。
  使用户能立刻看到错误。


使客户机在服务器上所能调用的方法的远程接口定义如下:
import java.rmi.*;
public interface ExpenseServer extends Remote {
Policy getPolicy() throws RemoteException;
void submitReport(ExpenseReport report)
throws RemoteException, InvalidReportException;
}
import语句输入Java RMI包。所有RMI类型均在包java.rmi或其子包内定义。接口ExpenseServer是一般的Java接口,具有如下两种有趣的特点:


它扩展了名为Remote的RMI接口,这使该接口标记为可用于远程调用。


它的所有方法均抛出RemoteException,后者用来表示网络或信息故障。

  远程方法还可抛出您所需要的任何其他例外,但至少必须抛出RemoteException,这样您才能处理只会在分布式系统中发生的错误状态。该接口本身支持两个方法:getPolicy (返回一个实现政策接口的对象),和submitReport(提交一个完成的开支请求,并在报告无论因何种原因使表格出现错误时,抛出一个例外)。
政策接口本身可声明一种使客户机知道能否在开支报告中添加一个项目的方法:
public interface Policy {
void checkValid (Expenseentry entry)
throws PolicyViolationException;
}
如果该项目有效--即符合当前政策,则该方法可正常返回。否则就会抛出一个描述该错误的例外。政策接口是本地的(而非远程的),所以将通过本机对象在客户机上执行--在客户机的虚拟机上,而非整个网络上运行的对象。客户机可能运行下列程序:
Policy curPolicy = 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);


  当用户请求客户机软件启动一份新的开支报告时,客户机就调用server.getPolicy,并要求服务器返回一个包含当前开支政策的对象。添加的每个项目首先都被提交给该政策对象,以获得批准。如果政策对象报告无错误,则该项目就被添加到报告中;否则错误就被显示给用户,而后者可采取修正措施。当用户完成向报告中添加项目时,整个报告就被呈交。服务程序如下:
import java.rmi. *;
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 . . .
}
}
除基本程序包外,我们还输入RMI的服务程序包。类型UnicastRemoteObject定义了此服务程序远程对象的类型,在本例中,应为单一服务程序而非复制服务(下面还会详细介绍)。Java类ExpenseSeverImpl实现远程接ExpenseServer的方法。远程主机的客户机可使用RMI将信息发送给ExpenseServerImpl对象。


本文中讨论的重要方法是getPolicy,它简单地返回定义当前政策的对象。下面看一个执行政策的例子:

public class TodaysPolicy implements Policy {
public void checkValid(ExpenseEntry entry)
throws PolicyViolationException
{
if (entry.dollars() < 20) {
return; // no receipt required
else if (entry.haveReceipt() == false) {
throw new PolicyViolationException;
}
}
}
TodaysPolicy进行检查的目的是确保无收据的任何项目均少于20美元。如果将来政策发生变化,仅少于20美元的餐费可不受“需要收据”政策的限制,则您可提供新的政策实现:
public class TomorrowsPolicy implements Policy {
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;
}
}
}
  编写这个类,并把它安装在服务器上,然后告诉服务器开始提供TomorrowsPolicy对象,而非daysPolicy对象,这样您的整个系统就会开始使用新的政策。当客户机调用服务器的getPolicy方法时,客户机的RMI就会检查返回的对象是否为已知类型。每台客户机首次遇到TomorrowsPolicy时,RMI就会在getPolicy返回前下载政策的实现。客户机可轻松地开始增强这个新的政策。


  RMI使用标准Java对象序列化机制传递对象。引用远程对象参数作为远程引用传递。如果向某方法提供的参数为原始类型或本机(非远程)对象,则向服务器传递一个深副本。返回值也拾照同样的方式处理,只不过是沿其它方向。RMI可使用户向本机对象传递和返回完整对象图并为远程对象传递和返回引用。

  在真实的系统中,getPolicy方法可能会有一个可以识别用户及开支报告类型(差旅、客户关系等)的参数,这样可使政策加以区别。您或者可以不要求单独的政策和开支报告对象,但您可以有一种newExpenseReport方法,它可返回一个直接检查政策的ExpenseReport对象。这最后一种策略可使您像修改政策一样简单地修改开支报告的内容--当公司决定需要把餐费划分为早餐、午餐和晚餐项目,而且像上述修改政策一样简单地执行修改时--可编写一个实现该报告的新类,客户程序就会自动使用这个类。

计算服务器

  开支报告的例子表示了客户机如何从服务器得到属性。属性可沿两个方向传递--客户机也可将新的类型传递给用户。最简单的例子就是如图2所示的计算服务器,该服务程序可执行任意任务,这样整个企业内的客户机都能利用高端或专用计算机。

任务由一个简单的本地(非远程)接口定义:
public interface Task {
Object run();
}
运行时,它就会进行一些计算,并返回一个包含结果的对象。这完全是一般性的任务--几乎所有计算任务都可在这个接口下实现。远程接口ComputeServer也同样简单:
import java.rmi.*;
public interface ComputeServer extends Remote {
Object compute(Task task) throws RemoteException;
}
这个远程接口的唯一目的就是使客户机创建一个Task (任务)对象,并把它发送给服务器执行,最后返回结果。该服务器的基本实现如下:
import java.rmi.*;
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;
}
}


  如果您看一看compute方法就会发现,它非常简单。它只是接受传递给它的对象,并返回计算的结果。main方法包括服务器的启动代码--它安装了RMI的缺省安全管理程序,以防止他人存取本地系统,并创建可处理进入的请求的ComputeServerImpl对象,并将其与名字"ComputeServer"关联。这时,服务器已经准备好接收要执行的任务,而main 也完成了其设置。
如上所述,这实际上是一种全面和实用的服务。系统可以得到改进,比如,可添加要计算的参数,从而对使用服务程序的部门进行计费。但在很多情况下,上述接口和及其实现允许使用高端计算机进行远程计算。这又明了RMI的简单性--如果您键入上述类,对其进行编译,并启动服务程序,您就拥有了能执行任意任务的运行计算服务器。

  下面介绍一个使用这种计算服务的例子。假定您购买了一个能运行大量计算操作应用程序的非常高端的系统。管理员可在该系统上启动一个Java虚拟机,运行ComputeServerImpl对象。该对象现在就可接受要运行的任务。

现在假定一个小组准备通过一组数据培训一个神经网络,以帮助制订采购策略。他们可以采用的步骤如下:

  定义一个类--暂且称之为PurchaseNet。它能接受一组数据,并运行培训数据,返回一个经过培训的神经网络。PurchaseNet将实现Task(任务)接口,并在其run方法中执行其工作。他们可能还需要一个Neuron类来描述所返回的网络中的节点,而且很可能需要其它类来描述处理过程。run方法将返回一个由一组经过培训的Neuron对象组成的NeuralNet对象。


当这些类被编写好并进行小规模测试时,用一个PurchaseNet 对象调用ComputeServer的compute方法。
当ComputeServerImpl对象中的RMI系统接收到作为进入参数的 PurchaseNet对象时,它就下载PurchaseNet的实现,并调用该服务器的compute方法,并把该对象作为Task (任务)参数。
Task,也就是PurchaseNet对象,将开始执行其执行程序。当执行程序需要诸如Neuron和NeuralNet等新的类时,它们可根据需要下载。
所有计算都将在计算服务器上执行,而客户机线程则等待结果。(客户机系统上的另一个线程则会显示“正在等待”光标或使用Java的内置并行机制执行另一个任务 )。当运行返回其NeuralNet对象时,这个对象将作为compute 方法的结果被传递回客户机。
这不需要在服务器上安装其它软件--所有必须完成的任务都由各部门在其自己的计算机上完成,随后再根据需要传递给计算服务器主机。
这个简单的计算服务器体系结构为系统的分布式功能提供了功能强大的转换能力。一项任务的计算可以被转移到一个能为其提供最好支持的系统上完成。这样的系统可以被用来:

  在ComputeServerImpl对象运行于有数据挖掘需要的主机上,支持数据挖掘应用程序。这样可使您轻松地把任何计算移动到数据所在的地方。
在从当前股票价格、发货信息或其它实时信息等外部资源获得直接数据的服务器上运行计算任务。
通过运行ComputeServer (接受进入的请求并将其转送到运行 ComputeServerImpl的负担最小的服务器上)的不同实现,而将任务分布在多个服务器上。
代理
因为RMI允许使用Java实现下载属性,所以您可使用RMI 编写代理系统。代理的最简单格式如下:

import java.rmi.*;
public interface AgentServer extends Remote {
void accept(Agent agent)
throws RemoteException, InvalidAgentException;
}
public interface Agent extends java.io.Serializable {
void run();
}
启动一个代理也就创建了实现Agent(代理)接口、找到服务器、激活接受该代理对象的类。该代理的执行程序将被下载到服务器上运行。accept方法将启动一个该代理的新线程,激活其run方法,然后返回,从而使该代理一直执行到run方法返回为止。代理可通过激活在另一台主机上运行的服务程序的accept方法而移植到该主机,而其本身则作为将被接受的代理来传递,并结束其在原来主机上的线程。
面向对象的代码重用与设计模式

  面向对象的编程是一项允许代码重用的强大技术。很多企业组织都使用面向对象的编程来减轻创建程序的负担和提高系统的灵活性。RMI是完全面向对象的--信息被发送给远程对象,而且对象可以被传递和返回。

Design Patterns (设计模式)目前在描述面向对象设计的实践活动中获得了相当大的成功。首先是因为Design Patterns的创新工作而使之大受欢迎,这些编程方式是一种正式描述解决某类特定问题的完整方法的途径。所有这些设计模式都依赖于创建一个或多个抽象概念,这些抽象概念允许不同的实现,从而允许和增强软件重用。软件重用是面向对象技术的主要优势,而设计模式则是促进重用的最受欢迎的技术之一。

所有设计模式都依赖面向对象的多态性--这是对象( 如Task )拥有多个实现的能力。算法的普通部分(如compute方法)不必知道使用了哪个实现,它只需知道得到一个对象后应该对该对象采取什么操作。特别地,计算服务器就是Command(指令)模式的一个例子,它可使您将请求 (任务)表示为对象,并对其进行调度。

只有当包括执行程序在内的完整对象能在客户机和服务器之间传递时,才会存在这样的多态性。DCE和DCOM等传统的RPC系统以及CORBA等基于对象的RPC系统不能下载并执行程序,因为它们不能把真实对象作为参数传递,而只能传递数据。

  RMI可传递包括执行程序在内的所有类型,所以您可以在分布式计算解决方案中,而不仅仅是本地计算中使用面向对象的编程--包括设计方式。如果没有RMI这样完全面向对象的系统,那么您就必须放弃很多分布式计算系统中的设计方式--以及面向对象的软件重用的其它形式。

出处: http://publishblog.blogdriver.com/blog/tb.b?diaryID=947754
原创粉丝点击