Java 分布式对象模型
2.1 分布式对象应用程序
RMI 应用程序通常包括两个独立的程序:服务器程序和客户端程序。典型的服务器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户端调用那些远程对象上的方法。
而典型的客户端程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI 为服务器和客户机进行通讯和信息传递提供了一种机制。这样的应用程序有时被称为分布式对象应用程序。
分布式对象应用程序需要:
1.定位远程对象
应用程序为了得到远程对象的引用,它既可以使用 RMI 的简单命名工具 rmiregistry 来注册它的远程对象;也可将远程对象引用作为常规操作的一部分来进行传递和返回。
2.与远程对象通讯
远程对象间通讯的细节由RMI处理;对于程序员来说,远程通讯看起来就象标准的Java方法调用。
3.给作为参数或返回值的对象加载字节码
因为RMI允许调用程序使用纯Java对象传给远程对象,所以RMI必须提供必要的机制,使得它既能加载对象的代码又可以传输对象的数据。
下面的插图说明了一个RMI分布式应用程序如何使用注册表来获取远程对象引用。服务器调用注册表来关联名字与远程对象,即绑定远程对象。客户端通过名字在服务器注册表中查找远程对象,随后再调用其上的方法。
这幅插图同时也展示了RMI系统如何使用一个存在的web服务器,在它需要对象的时候,从服务器到客户端或从客户端到服务器端来加载用Java编程语言编写的类的字节码。
RMI可以通过任何一个Java平台支持的URL协议(如,HTTP,FTP,file等等)来加载类的字节码。
2.2 术语的定义
在 Java 分布式对象模型中,remote object 是这样一种对象:它的方法可以从其它 Java 虚拟机(可能在不同的主机上)中调用。
该类型的对象由一种或多种 remote interfaces(它是声明远程对象方法的 Java 接口)描述。远程方法调用 (RMI) 就是调用远程对象上远程接口的方法的动作。更为重要的是,远程对象的方法调用与本地对象的方法调用语法相同。
2.3 分布式和非分布式模型的比较
Java SE 平台的分布式对象模型与Java SE 平台的对象模型有以下几个方面相似:
1.远程对象的引用在任一种方法调用中(本地或远程)都能以参数形式传递或以结果形式返回。
2.远程对象可以被强制转换成远程接口集合中的任何一个,只要该接口可以用内置Java语法进行强制类型转换实现所支持。
3.内置 Java 操作符 instanceof 可用来测试远程对象所支持的远程接口。
Java SE 平台的分布式对象模型与Java SE平台对象模型有以下方面不同:
1.远程对象的客户机与远程接口发生交互,而从不与这些接口的实现类交互。
2.远程方法的非远程参数和返回结果是通过复制而非引用的方式传递的。这是因为对象的引用只能在一个单独的虚拟机中才有用。
3.远程对象以引用的方式进行传递,而不是通过复制实际远程实现方式传递。
4.某些 java.lang.Object 类定义的方法的语义专用于远程对象。
5.因为调用远程对象的失败模式本来就比调用本地对象的失败模式复杂,所以客户机必须处理远程方法调用期间发生的额外异常。
2.4 RMI 接口和类概述
负责说明RMI系统远程行为的接口和类被定义在java.rmi包层次中。下面的图描述了几个接口和类的关系:
2.4.1 java.rmi.Remote 接口
在 RMI 中,远程接口声明了可从远程 Java 虚拟机中调用的方法集。远程接口必须满足下列要求:
1.远程接口至少必须直接或间接扩展 java.rmi.Remote 接口。
2.远程接口或其父接口中的方法声明必须满足下列远程方法声明的要求:
1).远程方法声明在其 throws 子句中除了要包含与应用程序有关的异常(注意与应用程序有关的异常无需扩展 java.rmi.RemoteException )之外,还必须包
括java.rmi.RemoteException 异常(或它的超类,例如java.io.IOException 或 java.lang.Exception )。
2).远程方法声明中,作为参数或返回值声明的(在参数表中直接声明或嵌入到参数的非远程对象中)远程对象必须声明为远程接口,而非该接口的实现类。
java.rmi.Remote 接口是一个不含方法定义的标记接口:
public interface Remote {}
远程接口必须至少扩展 java.rmi.Remote 接口(或其它扩展java.rmi.Remote 的远程接口)。然而,远程接口在下列情况中可以扩展非远程接口:
1).远程接口也可扩展其它非远程接口,只要被扩展接口的所有方法(如果有)满足远程方法声明的要求。
例如,下面的接口 BankAccount 即为访问银行帐户定义了一个远程接口。它包含往帐户存款、获取帐户余额和从帐户取款的远程方法:
public interface BankAccount extends java.rmi.Remote
{
public void deposit(float amount) throws java.rmi.RemoteException;
public void withdraw(float amount)throws OverdrawnException, java.rmi.RemoteException;
public float getBalance() throws java.rmi.RemoteException;
}
下例说明了有效的远程接口 Beta。它扩展非远程接口 Alpha(有远程方法)和接口 java.rmi.Remote:
public interface Alpha {
public final String okay = "constants are okay too";
public Object foo(Object obj) throws java.rmi.RemoteException;
public void bar() throws java.io.IOException;
public int baz() throws java.lang.Exception;
}
public interface Beta extends Alpha, java.rmi.Remote {
public void ping() throws java.rmi.RemoteException;
}
2.4.2 RemoteException 类
java.rmi.RemoteException 类是在远程方法调用期间由 RMI 运行时所抛出的异常的超类。
为确保使用 RMI 系统的应用程序的健壮性,远程接口中声明的远程方法在其 throws 子句中必须指定 java.rmi.RemoteException(或它的超类,例如 java.io.IOException 或 java.lang.Exception)。
当远程方法调用由于某种原因失败时,将抛出 java.rmi.RemoteException 异常。远程方法调用失败的原因包括:
1).通讯失败(远程服务器不可达或拒绝连接;连接被服务器关闭等。)
2).参数或返回值在编组(marshalling)或解组(unmarshalling)时失败
3).协议错误
RemoteException 类是一个受查异常(必须由远程方法的调用程序处理并经编译器检验的异常),而不是 RuntimeException。
2.4.3 RemoteObject 类及其子类
RMI 服务器函数由 java.rmi.server.RemoteObject 及其子类 java.rmi.server.RemoteServer、java.rmi.server.UnicastRemoteObject和java.rmi.activation.Activatable 提供。
1).java.rmi.server.RemoteObject 为对远程对象敏感的 java.lang.Object方法、hashCode、 equals 和 toString 提供实现。
2).创建远程对象并将其导出(使它们可为远程客户机利用)所需的方法由类UnicastRemoteObject 和 Activatable 提供。
子类可以识别远程引用的语义,例如服务器是简单的远程对象还是可激活的远程对象(调用时将执行的远程对象)。
3).java.rmi.server.UnicastRemoteObject 类定义了单例远程对象,其引用只有在服务器进程活着时才有效。
4).java.rmi.activation.Activatable 是抽象类,它定义的可激活的远程对象在其远程方法被调用时开始执行并在必要时自己关闭。
2.5 实现远程接口
实现远程接口的类的一般规则如下:
1).该类通常扩展 java.rmi.server.UnicastRemoteObject,因而将继承类java.rmi.server.RemoteObject 和java.rmi.server.RemoteServer 提供的远程行为。
2).该类能实现任意多的远程接口。
3).该类能扩展其它远程实现类。
4).该类能定义远程接口中不出现的方法,但这些方法只能在本地使用而不能在远程使用。
例如,下面的类 BankAcctImpl 实现 BankAccount 远程接口并扩展java.rmi.server.UnicastRemoteObject 类:
package mypackage;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class BankAccountImpl extends UnicastRemoteObject implements BankAccount{
private float balance = 0.0;
public BankAccountImpl(float initialBalance)throws RemoteException{
balance = initialBalance;
}
public void deposit(float amount) throws RemoteException{
...
}
public void withdraw(float amount) throws OverdrawnException,RemoteException{
...
}
public float getBalance() throws RemoteException{
...
}
}
注意:必要时,实现远程接口的类能扩展除java.rmi.server.UnicastRemoteObject 类以外的其它一些类。
但实现类此时必须承担起一定的责任,即导出对象(由 UnicastRemoteObject 构造函数负责)和实现从 java.lang.Object 类继承的 hashCode、 equals 和 toString 方法的正确远程语义(如果需要)。
2.6 远程方法调用中的参数传递
传给远程对象的参数或源于它的返回值可以是任意可序列化对象。这包括基本类型, 远程对象和实现 java.io.Serializable 接口的非远程对象。
有关如何使类序列化的详细信息,参见 Java“对象序列化规范”。本地得不到的作为参数或返回值的类,可通过 RMI 系统进行动态下载。
有关 RMI 读取参数、返回值和异常时如何下载参数和返回值类的详细信息,参见“动态类加载”一节。
2.6.1 传递非远程对象
非远程对象作为远程方法调用的参数传递或作为远程方法调用的结果返回时,是通过复制传递的;也就是使用 Java 对象序列化机制。
因此,在远程方法调用过程中,当非远程对象作为参数或返回值传递时,非远程对象的内容在调用远程对象之前将被复制。
当非远程对象从远程方法调用中返回时,新对象将在调用者的虚拟机中创建。
2.6.2 传递远程对象
当将已导出的远程对象作为远程方法调用的参数或返回值传递时,实际传递的是远程对象的stub。作为参数传递的远程对象仅能实现远程接口。未导出的远程对象将不会被替换为stub实例。一个远程对象能作为参数传递只需实现remote接口。
2.6.3 引用的完整性
如果一个对象的两个引用在单个远程方法调用中以参数形式(或返回值形式)从一个虚拟机传到另一个虚拟机中,并且它们在发送虚拟机中指向同一对象,
则两个引用在接收虚拟机中将指向该对象的同一副本。进一步说就是:在单个远程方法调用中,RMI 系统将在作为调用参数或返回值传递的对象中保持引用的完整性。
2.6.4 类注解
当对象在远程调用中被从一个虚拟机发送到另一个虚拟机中时,RMI 系统在调用流中用类的信息 (URL) 给类描述符加注解,以便该类能在接收端上加载。
在远程方法调用期间,调用可随时下载类。
2.6.5 参数传输
RMI调用中的参数将被写到一个流(此流是java.io.ObjectOutputStream的子类)中,其目的是为了将参数序列化到远程调用的目的地。
ObjectOutputStream的子类覆盖了 replaceObject 方法,目的是用其相应的stub实例来替换每个已导出的远程对象。
对象参数使用ObjectOutputStream 的 writeObject 方法写入流中。
而ObjectOutputStream 则通过 writeObject 方法为每个写入流中的对象(包含所写对象所引用的对象)调用 replaceObject 方法。
RMI的ObjectOutputStream子类的 replaceObject 方法返回下列值:
1).如果传给 replaceObject 的对象是 java.rmi.Remote 的实例,并且此对象已经导出到RMI运行时环境,则返回远程对象的stub实例。
如果此对象是java.rmi.Remote的实例但它没有导出到RMI运行时环境,则replaceObject方法将返回此对象自身。
远程对象的stub实例可以通过调用java.rmi.server.RemoteObject.toStub方法而获得。
2).如果传给 replaceObject 的对象不是 java.rmi.Remote 的实例,则简单返回该对象。
RMI 的 ObjectOutputStream 子类也实现 annotateClass 方法,该方法用类的位置注解调用流以便能在接收器中下载该类。
有关如何使用 annotateClass的详细信息,参见“动态类加载”一节。
因为参数写入一个 ObjectOutputStream,所以指向调用程序同一对象的引用将在接收端那里指向该对象的同一副本。在接收端,参数将被单个ObjectInputStream 所读取。
用于写对象的 ObjectOutputStream(类似的还有用于读对象的ObjectInputStream )的所有其它缺省行为将保留在参数传递中。
例如,写对象时对 writeReplace 的调用及读对象时对 readResolve 的调用就是由 RMI的参数编组与解组完成的。
与上述 RMI 参数传递方式类似,返回值(或异常)将被写入ObjectOutputStream的子类并和参数传输的替代行为相同。
2.7 定位远程对象
我们专门提供了一种简单的引导名字服务器,用于存储对远程对象的已命名引用。使用类 java.rmi.Naming 的基于 URL 的方法可以存储远程对象引用。
客户机要调用远程对象的方法,则必须首先得到该对象的引用。对远程对象的引用通常是在方法调用中以返回值的形式取得。
RMI 系统提供一种简单的引导名字服务器,通过它得到给定主机上的远程对象。
java.rmi.Naming 类提供基于统一资源定位符 (URL) 的方法,用来绑定、重新绑定、解除绑定和列出位于某一主机及端口上的名字-对象对