开支报告的例子表示了客户机如何从服务器得到属性。属性可沿两个方向传递--客户机也可将新的类型传递给用户。最简单的例子就是如图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这样完全面向对象的系统,那么您就必须放弃很多分布式计算系统中的设计方式--以及面向对象的软件重用的其它形式。