RMI规范--第二章
Java 分布式对象模型
2.1 分布式对象应用程序
RMI 应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务
器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机
调用那些远程对象上的方法。而典型的客户机程序则从服务器中得到一个或多个
远程对象的引用,然后调用远程对象的方法。RMI 为服务器和客户机进行通讯
和信息传递提供了一种机制。这样的应用程序有时被称为分布式对象应用程序。
分布式对象应用程序需要:
定位远程对象
应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用 RMI 的简
单命名工具 rmiregistry 来注册它的远程对象;也可将远程对象引用作为常规
操作的一部分来进行传递和返回。
与远程对象通讯
远程对象间通讯的细节由 RMI 处理;对于程序员来说,远程通讯看起来就象标
准的 Java 方法调用。给作为参数或返回值传递的对象加载类字节码因为 RMI
允许调用程序将纯 Java 对象传给远程对象,所以 RMI 将提供必要的机制,
既可以加载对象的代码又可以传输对象的数据。
服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器注册服务
程序中用远程对象的名字查找该远程对象,然后调用它的方法。RMI 能用 Java
系统支持的任何 URL 协议(例如 HTTP、FTP、file 等)加载类字节码。
2.2 术语的定义
在 Java 分布式对象模型中,remote object 是这样一种对象:它的方法可以
从其它 Java 虚拟机(可能在不同的主机上)中调用。该类型的对象由一种或
多种 remote interfaces(它是声明远程对象方法的 Java 接口)描述。
远程方法调用 (RMI) 就是调用远程对象上远程接口的方法的动作。更为重要的
是,远程对象的方法调用与本地对象的方法调用语法相同。
2.3 分布式和非分布式模型的比较
Java 分布式对象模型在以下几方面与 Java 对象模型相似:
远程对象的引用在任一种方法调用中(本地或远程)都能以参数形式传递或以结
果形式返回。
远程对象可以被强制转换成任何远程界面,只要该界面为使用内置 Java 语法
进行强制类型转换的实现所支持。
内置 Java 操作符 instanceof 可用来测试远程对象所支持的远程接口。
Java 分布式对象模型在以下几方面与 Java 对象模型不同:
远程对象的客户机与远程接口发生交互,而从不与这些接口的实现类交互。
远程方法的非远程参数和返回结果是通过复制而非引用的方式传递的。这是因为
对象的引用只在单个虚拟机中才有用。
远程对象以引用的方式进行传递,而不是复制实际的远程实现。
某些 java.lang.Object 类定义的方法的语义专用于远程对象。
因为调用远程对象的失败模式本来就比调用本地对象的失败模式复杂,所以客户
机必须处理远程方法调用期间发生的额外异常。
2.4 RMI 接口和类概述
2.4.1 java.rmi.Remote 接口
在 RMI 中,远程接口是声明了可从远程 Java 虚拟机中调用的方法集。远程接
口必须满足下列要求:
远程接口至少必须直接或间接扩展 java.rmi.Remote 接口。
远程接口中的方法声明必须满足下列远程方法声明的要求:
远程方法声明在其 throws 子句中除了要包含与应用程序有关的异常(注意与
应用程序有关的异常无需扩展 java.rmi.RemoteException )之外,还必须包
括 java.rmi.RemoteException 异常(或它的超类,例如
java.io.IOException 或 java.lang.Exception )。
远程方法声明中,作为参数或返回值声明的(在参数表中直接声明或嵌入到参数
的非远程对象中)远程对象必须声明为远程接口,而非该接口的实现类。
java.rmi.Remote 接口是一个不定义方法的标记接口:
public interface Remote
远程接口必须至少扩展 java.rmi.Remote 接口(或其它扩展
java.rmi.Remote 的远程接口)。然而,远程接口在下列情况中可以扩展非远
程接口:
远程接口也可扩展其它非远程接口,只要被扩展接口的所有方法(如果有)满足
远程方法声明的要求。
例如,下面的接口 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 异
常。远程方法调用失败的原因包括:
通讯失败(远程服务器不可达或拒绝连接;连接被服务器关闭等。)
参数或返回值传输或读取时失败
协议错误
RemoteException 类是一个已检验的异常(必须由远程方法的调用程序处理并
经编译器检验的异常),而不是 RuntimeException。
2.4.3 RemoteObject 类及其子类
RMI 服务器函数由 java.rmi.server.RemoteObject 及其子类
java.rmi.server.RemoteServer、java.rmi.server.UnicastRemoteObject
和 java.rmi.activation.Activatable 提供。
java.rmi.server.RemoteObject 为对远程对象敏感的 java.lang.Object
方法、hashCode、 equals 和 toString 提供实现。
创建远程对象并将其导出(使它们可为远程客户机利用)所需的方法由类
UnicastRemoteObject 和 Activatable 提供。子类可以识别远程引用的语义,
例如服务器是简单的远程对象还是可激活的远程对象(调用时将执行的远程对象)。
java.rmi.server.UnicastRemoteObject 类定义了单体(单路传送)远程对
象,其引用只有在服务器进程活着时才有效。
类 java.rmi.activation.Activatable 是抽象类,它定义的 activatable
远程对象在其远程方法被调用时开始执行并在必要时自己关闭。
2.5 实现远程接口
实现远程接口的类的一般规则如下:
该类通常扩展 java.rmi.server.UnicastRemoteObject,因而将继承类
java.rmi.server.RemoteObject 和java.rmi.server.RemoteServer 提供
的远程行为。
该类能实现任意多的远程接口。
该类能扩展其它远程实现类。
该类能定义远程接口中不出现的方法,但这些方法只能在本地使用而不能在远程
使用。
例如,下面的类 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 对象。这包
括 Java 基本类型, 远程?Java 对象和实现 java.io.Serializable 接口的
非远程 Java 对象。有关如何使类序列化的详细信息,参见 Java“对象序列化
规范”。本地得不到的作为参数或返回值的类,可通过 RMI 系统进行动态下载。
有关 RMI 读取参数、返回值和异常时如何下载参数和返回值类的详细信息,参
见“动态类加载”(3.4)一节。
2.6.1 传递非远程对象
非远程对象将作为远程方法调用的参数传递或作为远程方法调用的结果返回时,
是通过复制传递的;也就是使用 Java 对象序列化机制将该对象序列化。
因此,在远程对象调用过程中,当非远程对象作为参数或返回值传递时,非远程
对象的内容在调用远程对象之前将被复制。
从远程方法调用返回非远程对象时,将在调用的虚拟机中创建新对象。
2.6.2 传递远程对象
当将远程对象作为远程方法调用的参数或返回值传递时,远程对象的 stub 程序
即被传递出去。作为参数传递的远程对象仅能实现远程接口。
2.6.3 引用的完整性
如果一个对象的两个引用在单个远程方法调用中以参数形式(或返回值形式)从
一个虚拟机传到另一个虚拟机中,并且它们在发送虚拟机中指向同一对象,则两
个引用在接收虚拟机中将指向该对象的同一副本。进一步说就是:在单个远程方
法调用中,RMI 系统将在作为调用参数或返回值传递的对象中保持引用的完整性
。
2.6.4 类注解
当对象在远程调用中被从一个虚拟机发送到另一个虚拟机中时,RMI 系统在调用
流中用类的信息 (URL) 给类描述符加注解,以便该类能在接收器上加载。在远
程方法调用期间,调用可随时下载类。
2.6.5 参数传输
为将 RMI 调用的参数序列化到远程调用的目的文件里,需要将该参数写入作为
java.io.ObjectOutputStream 类的子类的流中。ObjectOutputStream 子类
将覆盖 replaceObject 方法,目的是用其相应的 stub 类取代每个远程对象。
对象参数将通过 ObjectOutputStream 的 writeObject 方法写入流中。而
ObjectOutputStream 则通过 writeObject 方法为每个写入流中的对象(包
含所写对象所引用的对象)调用 replaceObject 方法。RMIObjectOutputStream
子类的 replaceObject 方法返回下列值:
如果传给 replaceObject 的对象是 java.rmi.Remote 的实例,则返回远程对
象的 stub 程序。远程对象的 stub 程序通过对
java.rmi.server.RemoteObject.toStub
方法的调用而获得。
如果传给 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) 的方法,用来绑定、再绑定、解开和列出位于某一主机及
端口上的名字-对象对。
Java 分布式对象模型
2.1 分布式对象应用程序
RMI 应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务
器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机
调用那些远程对象上的方法。而典型的客户机程序则从服务器中得到一个或多个
远程对象的引用,然后调用远程对象的方法。RMI 为服务器和客户机进行通讯
和信息传递提供了一种机制。这样的应用程序有时被称为分布式对象应用程序。
分布式对象应用程序需要:
定位远程对象
应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用 RMI 的简
单命名工具 rmiregistry 来注册它的远程对象;也可将远程对象引用作为常规
操作的一部分来进行传递和返回。
与远程对象通讯
远程对象间通讯的细节由 RMI 处理;对于程序员来说,远程通讯看起来就象标
准的 Java 方法调用。给作为参数或返回值传递的对象加载类字节码因为 RMI
允许调用程序将纯 Java 对象传给远程对象,所以 RMI 将提供必要的机制,
既可以加载对象的代码又可以传输对象的数据。
服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器注册服务
程序中用远程对象的名字查找该远程对象,然后调用它的方法。RMI 能用 Java
系统支持的任何 URL 协议(例如 HTTP、FTP、file 等)加载类字节码。
2.2 术语的定义
在 Java 分布式对象模型中,remote object 是这样一种对象:它的方法可以
从其它 Java 虚拟机(可能在不同的主机上)中调用。该类型的对象由一种或
多种 remote interfaces(它是声明远程对象方法的 Java 接口)描述。
远程方法调用 (RMI) 就是调用远程对象上远程接口的方法的动作。更为重要的
是,远程对象的方法调用与本地对象的方法调用语法相同。
2.3 分布式和非分布式模型的比较
Java 分布式对象模型在以下几方面与 Java 对象模型相似:
远程对象的引用在任一种方法调用中(本地或远程)都能以参数形式传递或以结
果形式返回。
远程对象可以被强制转换成任何远程界面,只要该界面为使用内置 Java 语法
进行强制类型转换的实现所支持。
内置 Java 操作符 instanceof 可用来测试远程对象所支持的远程接口。
Java 分布式对象模型在以下几方面与 Java 对象模型不同:
远程对象的客户机与远程接口发生交互,而从不与这些接口的实现类交互。
远程方法的非远程参数和返回结果是通过复制而非引用的方式传递的。这是因为
对象的引用只在单个虚拟机中才有用。
远程对象以引用的方式进行传递,而不是复制实际的远程实现。
某些 java.lang.Object 类定义的方法的语义专用于远程对象。
因为调用远程对象的失败模式本来就比调用本地对象的失败模式复杂,所以客户
机必须处理远程方法调用期间发生的额外异常。
2.4 RMI 接口和类概述
2.4.1 java.rmi.Remote 接口
在 RMI 中,远程接口是声明了可从远程 Java 虚拟机中调用的方法集。远程接
口必须满足下列要求:
远程接口至少必须直接或间接扩展 java.rmi.Remote 接口。
远程接口中的方法声明必须满足下列远程方法声明的要求:
远程方法声明在其 throws 子句中除了要包含与应用程序有关的异常(注意与
应用程序有关的异常无需扩展 java.rmi.RemoteException )之外,还必须包
括 java.rmi.RemoteException 异常(或它的超类,例如
java.io.IOException 或 java.lang.Exception )。
远程方法声明中,作为参数或返回值声明的(在参数表中直接声明或嵌入到参数
的非远程对象中)远程对象必须声明为远程接口,而非该接口的实现类。
java.rmi.Remote 接口是一个不定义方法的标记接口:
public interface Remote
远程接口必须至少扩展 java.rmi.Remote 接口(或其它扩展
java.rmi.Remote 的远程接口)。然而,远程接口在下列情况中可以扩展非远
程接口:
远程接口也可扩展其它非远程接口,只要被扩展接口的所有方法(如果有)满足
远程方法声明的要求。
例如,下面的接口 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 异
常。远程方法调用失败的原因包括:
通讯失败(远程服务器不可达或拒绝连接;连接被服务器关闭等。)
参数或返回值传输或读取时失败
协议错误
RemoteException 类是一个已检验的异常(必须由远程方法的调用程序处理并
经编译器检验的异常),而不是 RuntimeException。
2.4.3 RemoteObject 类及其子类
RMI 服务器函数由 java.rmi.server.RemoteObject 及其子类
java.rmi.server.RemoteServer、java.rmi.server.UnicastRemoteObject
和 java.rmi.activation.Activatable 提供。
java.rmi.server.RemoteObject 为对远程对象敏感的 java.lang.Object
方法、hashCode、 equals 和 toString 提供实现。
创建远程对象并将其导出(使它们可为远程客户机利用)所需的方法由类
UnicastRemoteObject 和 Activatable 提供。子类可以识别远程引用的语义,
例如服务器是简单的远程对象还是可激活的远程对象(调用时将执行的远程对象)。
java.rmi.server.UnicastRemoteObject 类定义了单体(单路传送)远程对
象,其引用只有在服务器进程活着时才有效。
类 java.rmi.activation.Activatable 是抽象类,它定义的 activatable
远程对象在其远程方法被调用时开始执行并在必要时自己关闭。
2.5 实现远程接口
实现远程接口的类的一般规则如下:
该类通常扩展 java.rmi.server.UnicastRemoteObject,因而将继承类
java.rmi.server.RemoteObject 和java.rmi.server.RemoteServer 提供
的远程行为。
该类能实现任意多的远程接口。
该类能扩展其它远程实现类。
该类能定义远程接口中不出现的方法,但这些方法只能在本地使用而不能在远程
使用。
例如,下面的类 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 对象。这包
括 Java 基本类型, 远程?Java 对象和实现 java.io.Serializable 接口的
非远程 Java 对象。有关如何使类序列化的详细信息,参见 Java“对象序列化
规范”。本地得不到的作为参数或返回值的类,可通过 RMI 系统进行动态下载。
有关 RMI 读取参数、返回值和异常时如何下载参数和返回值类的详细信息,参
见“动态类加载”(3.4)一节。
2.6.1 传递非远程对象
非远程对象将作为远程方法调用的参数传递或作为远程方法调用的结果返回时,
是通过复制传递的;也就是使用 Java 对象序列化机制将该对象序列化。
因此,在远程对象调用过程中,当非远程对象作为参数或返回值传递时,非远程
对象的内容在调用远程对象之前将被复制。
从远程方法调用返回非远程对象时,将在调用的虚拟机中创建新对象。
2.6.2 传递远程对象
当将远程对象作为远程方法调用的参数或返回值传递时,远程对象的 stub 程序
即被传递出去。作为参数传递的远程对象仅能实现远程接口。
2.6.3 引用的完整性
如果一个对象的两个引用在单个远程方法调用中以参数形式(或返回值形式)从
一个虚拟机传到另一个虚拟机中,并且它们在发送虚拟机中指向同一对象,则两
个引用在接收虚拟机中将指向该对象的同一副本。进一步说就是:在单个远程方
法调用中,RMI 系统将在作为调用参数或返回值传递的对象中保持引用的完整性
。
2.6.4 类注解
当对象在远程调用中被从一个虚拟机发送到另一个虚拟机中时,RMI 系统在调用
流中用类的信息 (URL) 给类描述符加注解,以便该类能在接收器上加载。在远
程方法调用期间,调用可随时下载类。
2.6.5 参数传输
为将 RMI 调用的参数序列化到远程调用的目的文件里,需要将该参数写入作为
java.io.ObjectOutputStream 类的子类的流中。ObjectOutputStream 子类
将覆盖 replaceObject 方法,目的是用其相应的 stub 类取代每个远程对象。
对象参数将通过 ObjectOutputStream 的 writeObject 方法写入流中。而
ObjectOutputStream 则通过 writeObject 方法为每个写入流中的对象(包
含所写对象所引用的对象)调用 replaceObject 方法。RMIObjectOutputStream
子类的 replaceObject 方法返回下列值:
如果传给 replaceObject 的对象是 java.rmi.Remote 的实例,则返回远程对
象的 stub 程序。远程对象的 stub 程序通过对
java.rmi.server.RemoteObject.toStub
方法的调用而获得。
如果传给 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) 的方法,用来绑定、再绑定、解开和列出位于某一主机及
端口上的名字-对象对。