RMI规范(6) --(10)

原创 2004年08月13日 01:41:00

RMI规范(6)
注册服务程序接口
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
第 6 章

RMI 系统使用 java.rmi.registry.Registry 接口和 java.rmi.registry.Locat
eRegistry 类来为按名字检索和注册对象提供众所周知的自举服务。

registry 是一种将名字映射到远程对象的远程对象。所有的服务器进程都支持自己的注册服务程序或能为主机所用的单个注册服务程序。

LocateRegistry 方法用于使注册服务程序在特定的主机中或主机与端口中运行起来。java.rmi.Naming 类方法用于调用一个远程对象,该对象使用恰当的 Locat
eRegistry.getRegistry 方法来实现 Registry 接口。

主题:
Registry 接口
LocateRegistry 类
RegistryHandler 接口

?

?

RMI规范(7)
远程对象激活
----------------------------------------------------------------------

-------------------------------------------------------------------------------
第 7 章

主题:
概述
激活协议
“可激活”远程对象的实现模型
激活接口

?

RMI规范(8)
第 8 章
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------

本章包含 rmic stub 编译器生成的 stub 和 skleton 所用的接口和类。

主题:
RemoteStub 类
RemoteCall 接口
RemoteRef 接口
ServerRef 接口
Skeleton 接口
Operation 类

?

RMI规范(9)
第 9 章
-------------------------------------------------------------------------------?
-------------------------------------------------------------------------------?

本章中的接口和类用于 RMI 的分布式垃圾收集器 (DGC)。

主题:
接口 DGC
Lease 类
ObjID 类
UID 类
VMID 类

?

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
RMI规范(10)
RMI 通信协议

------------------------------------------------------------------------------- ------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------

?

?

?

RMI规范(6)
注册服务程序接口
-------------------------------------------------------------------------------
第 6 章

RMI 系统使用 java.rmi.registry.Registry 接口和 java.rmi.registry.Locat
eRegistry 类来为按名字检索和注册对象提供众所周知的自举服务。

registry 是一种将名字映射到远程对象的远程对象。所有的服务器进程都支持自己的注册服务程序或能为主机所用的单个注册服务程序。

LocateRegistry 方法用于使注册服务程序在特定的主机中或主机与端口中运行起来。java.rmi.Naming 类方法用于调用一个远程对象,该对象使用恰当的 Locat
eRegistry.getRegistry 方法来实现 Registry 接口。

主题:
Registry 接口
LocateRegistry 类
RegistryHandler 接口

6.1 Registry 接口
java.rmi.registry.Registry 远程接口可为查询、绑定、再绑定、取消绑定和列出注册服务程序内容提供方法。java.rmi.Naming 类用 registry 远程接口提供基于 URL 的命名。

package java.rmi.registry;

public interface Registry extends java.rmi.Remote {
public static final int REGISTRY_PORT = 1099;
public java.rmi.Remote lookup(String name)
throws java.rmi.RemoteException,
java.rmi.NotBoundException, java.rmi.AccessException;
public void bind(String name, java.rmi.Remote obj)
throws java.rmi.RemoteException,
java.rmi.AlreadyBoundException, java.rmi.AccessException;
public void rebind(String name, java.rmi.Remote obj)
throws java.rmi.RemoteException, java.rmi.AccessException;
public void unbind(String name)
throws java.rmi.RemoteException,
java.rmi.NotBoundException, java.rmi.AccessException;
public String[] list()
throws java.rmi.RemoteException, java.rmi.AccessException;
}


REGISTRY_PORT 是注册服务程序的缺省端口。

lookup 方法返回绑定到指定 name 的远程对象,而远程对象则实现一组远程接口。客户机可将远程对象强制转换为所期望的远程接口类型(该强制类型转换可能以它在 Java 语言中的常见失败方式而失败)。

bind 方法将 name 与远程对象 obj 相关联。如果名字已绑定到某一对象上,则抛出 AlreadyBoundExcepton。
rebind 方法将 name 与远程对象 obj 相关联。该名字以前的所有绑定均将被丢弃。
unbind 方法删除 name 和远程对象 obj 之间的绑定。如果名字并未绑定到某一对象上,则抛出 NotBoundException。

list 方法返回一个 Strings 数组,其中包含注册服务程序中所绑名字的快照。该返回值含有注册服务程序内容的快照。
客户机可以使用 LocateRegistry 和 Registry 接口或基于 URL 的 java.rmi.Naming 类的方法来访问注册服务程序。该注册服务程序仅支持与服务器位于同一主机的客户机的 bind、unbind 和 rebind。所有主机均可进行查询操作。

.2 LocateRegistry 类
java.rmi.registry.LocateRegistry 类用来获得对特定主机(包括本地主机)的引导远程对象注册服务程序的引用(创建 stub),或者创建能在特定端口接受调用的远程对象注册服务程序。

注册服务程序实现将远程对象名(字符串)与远程对象引用相关联的简单命名语法。服务器重新启动不会记住这些名字和远程对象之间的绑定。

注意,getRegistry 调用并不真正与远程主机连接。它只简单地建立了对远程注册服务程序的本地引用,且即使远程主机中没有注册服务程序运行也将获得成功 。因此,对于该方法作为返回值返回的远程注册服务程序进行进一步的方法调用时,可能会导致失败。

package java.rmi.registry;

public final class LocateRegistry
{
public static Registry getRegistry()
throws java.rmi.RemoteException;
public static Registry getRegistry(int port)
throws java.rmi.RemoteException;
public static Registry getRegistry(String host)
throws java.rmi.RemoteException;
public static Registry getRegistry(String host, int port)
throws java.rmi.RemoteException;
public static Registry getRegistry(String host, int port,
RMIClientSocketFactory csf)
throws RemoteException;
public static Registry createRegistry(int port)
throws java.rmi.RemoteException;
public static Registry createRegistry(int port,
RMIClientSocketFactory csf,
RMIServerSocketFactory ssf)
throws RemoteException;
}

前四个 getRegistry 方法返回对当前主机、指定 port 的当前主机、指定的 host 或指定 host 的特定 port 的注册服务程序引用。所返回的是带有指定主机和端口信息的注册服务程序的远程 stub。

第五个 getRegistry 方法(将 RMIClientSocketFactory 作为参数)返回本地创建的远程 stub,它对应于指定 host 和 port 上的远程对象 Registry。与用该方法构造 stub 的远程注册服务程序进行通信时,将使用所提供的 RMIClientSocketFactory、csf 来创建到远程主机和端口的注册服务程序的 Socket 连接。

--------------------------------------------------------------------------------

注意 - 从 getRegistry 方法返回的注册服务程序是含有已知对象标识符的专门构造的 stub。目前仍不支持将注册服务程序 stub 从一个虚拟机传给另一个虚拟机(其有效性将取决于实现)。使用 LocateRegistry.getRegistry 方法可以获得主机的相应注册服务程序。


--------------------------------------------------------------------------------

createRegistry 方法创建和导出特定 port 的本地主机的注册服务程序。

第二种 createRegistry 方法在与注册服务程序通信时更富有灵活性。该调用将在本地主机中创建和导出 Registry,本地主机使用自定义套接字工厂用于与注册 服务程序的通信。所创建的注册服务程序在给定的 port 上使用从 RMIServerSocketFactory 所创建的 ServerSocket 监听到来请求。接收到注册服务程序引用的客户机将使用从 RMIClientSocketFactory 所创建的 Socket。


--------------------------------------------------------------------------------

注意 - 使用 createRegistry 方法启动注册服务程序不能保持服务器进程的活动性。


6.3 RegistryHandler 接口

------------------------------------------------------------------

注意 - JDK1.2 中不鼓励使用 RegistryHandler 接口。JDK1.1 中,它仅由 RMI
实现在内部使用,而不能用于应用程序。


-------------------------------------------------------------------

package java.rmi.registry;

public interface RegistryHandler
{
Registry registryStub(String host, int port)
throws java.rmi.RemoteException, java.rmi.UnknownHostException;
Registry registryImpl(int port) throws java.rmi.RemoteException;
}

方法 registryStub 返回一个与位于指定主机和端口的远程注册服务程序进行联系的 stub。

方法 registryImpl 将构造并导出位于指定的端口的注册服务程序。端口必须为非零。

?

?

?


RMI规范(7)
远程对象激活
----------------------------------------------------------------------

第 7 章

主题:
概述
激活协议
“可激活”远程对象的实现模型
激活接口

.1 概述
分布式对象系统被设计为支持长期存在的持久对象。假设这些系统将由成千(也许成万)个这样的对象组成,则对象的实现在无限期的时间段内被激活并保持活 动状态是不合理的。这将占用宝贵的系统资源。另外,客户机需要保存对对象的持久引用的能力,这样在一个系统崩溃后可以重新建立对象之间的通讯,因为通 常对一个分布对象的引用只有当对象处于活动状态时才有效。

对象激活是一种用来提供对对象持久引用和管理对象实现的执行的机制。在 RMI 中,激活允许对象根据需要开始执行。当访问(通过方法调用)“可激活”远程 对象时,如果该远程对象当前尚未执行,则系统将在适当的 Java 虚拟机中开始 该对象的执行。

7.1.1 术语
活动 (active) 对象是在某些系统的 Java 虚拟机中被实例化并被导出的远程对 象。非活动对象在虚拟机中尚未被实例化(或导出),但能变成活动状态的远程 对象。激活就是将非活动对象转化为活动对象的过程。激活要求对象与一台虚拟 机相关联,而这将可能需要将该对象的类加载到虚拟机中,同时该对象也恢复其 持久状态(如果有)。

在 RMI 系统中,我们使用了惰性激活。惰性激活就是将一个对象的激活延迟到客 户机第一次使用该对象时(即第一次方法调用时)。

7.1.2 惰性激活
远程对象的惰性激活是用不完善远程引用(有时称为不完善块)实现的。对远程 对象的不完善远程引用在第一次调用对象的方法时,“完善”为活动对象的引用 。每个不完善引用均保持一个持久句柄(激活标识符)和对目标远程对象的瞬态 远程引用。远程对象的激活标识符包含足够的信息来使第三方激活该对象。瞬态 引用是对可用来联系执行对象的主动远程对象的“活动”引用。

在不完善引用中,如果对远程对象的活引用为空,则不认为目标对象是主动的。
在方法调用中,不完善引用(对该对象)将加入激活协议以获得“活”引用,该 引用是对新激活的对象的远程引用(例如单路传送 (unicast) 的远程引用)。一 旦不完善引用得到活引用,则不完善引用将把方法调用传给底层的远程引用,而 该远程引用又将方法调用传给远程对象。

具体的说,远程对象的 stub 包含一个“不完善”远程引用类型,该类型既包括:

远程对象的激活标识符,又包括 “活”引用(可能为空),其中包含远程对象的“活动”远程引用类型(例如, 带有单路传送语义的远程引用类型)。

----------------------------------------------------------------------
注意 - RMI 系统对远程调用保留“至多一次”语义。换句话说,对可激活或单路 传送远程对象的调用将至多发送一次。因此,如果对远程对象的调用失败(由抛 出的 RemoteException 异常表示),则客户机将得到如下保证:远程方法的执行 不会超过一次,甚至根本就不执行。

7.2 激活协议
在远程方法调用期间,如果目标对象的“活”(live) 引用是未知的,则不完善引 用将使用激活协议。激活协议包括下列几个实体:不完善引用、激活器、Java 虚 拟机中的激活组和被激活的远程对象。

激活器(通常每个主机有一个)是一个实体,负责激活,它是:

将激活标识符映射到激活对象所需信息(对象的类、位置 -- URL 路径 -- 从该 处可加载类、对象可能需要用于自举 (bootstrap) 的特定数据等)的信息数据库,及 Java 虚拟机的管理器,它启动虚拟机(必要时)并将对象激活请求(和必要的信 息一起)传送到远程虚拟机中正确的激活组。
注意:激活器始终将激活标识符到活动对象的当前映射保存在缓存中,这样就无 需为每个激活请求而查询该组。

激活组(每个 Java 虚拟机中一个)是这样的实体,它接收对激活 Java 虚拟机 中对象的请求并将激活的对象返给激活器。

激活协议如下所示。不完善引用使用一个激活标识符并调用激活器(内部 RMI 接 口)来激活与该标识符关联的对象。激活器查找对象的激活描述符(先前已注册 )。对象的描述符包括:

对象的组标识符(指定对象激活时所处的虚拟机), 对象的类名, URL 路径,从该处加载对象的类代码, 特定于对象的已编组的初始化数据(例如,初始化数据可能是包含对象持久状态 的文件的名称)。
如果应容纳该对象的激活组存在,则激活器将激活请求传送到该组。如果激活组 不存在,则激活器将启动虚拟机以执行激活组,然后将激活请求传送到该组。

激活组将加载对象的类并用特定的构造函数来实例化该对象。此构造函数带多个 参数,包括先前注册的激活描述符。

对象完成激活时,激活组将把已编组对象引用传回激活器,然后该激活器记录激 活标识符和激活引用对,并将活动(活)引用返给不完善引用。随后,不完善引 用(在 stub 内)通过活动引用将方法调用直接传给远程对象。


----------------------------------------------------------------------
注意 - 在 JDK 中,RMI 提供激活系统接口的实现。要使用激活,必须首先运行 激活系统守护进程 (daemon) rmid。

7.3 “可激活”远程对象的实现模型
为了使可通过激活标识符访问的远程对象不受时间影响,开发人员必须做到:

为该远程对象注册一个激活描述符
在对象的类中包含一个专用构造函数,当 RMI 系统激活可激活对象时将调用它。

可用以下几种方法来注册激活描述符 (ActivationDesc):

调用类 Activatable 的静态 register 方法
用 Activatable 类的第一个或第二个构造函数创建“可激活”对象 显式地导出“可激活”对象。该过程可用 Activatable 的第一个或第二个 expo
rtObject 方法实现,其参数为 ActivationDesc、Remote 对象的实现和端口号。

对于特定对象,只可用上述三种方法之一来注册激活对象。有关如何实现可激活 对象的示例,请参阅后面的“构造可激活远程对象”。

7.3.1 ActivationDesc 类
ActivationDesc 含有激活对象所需的信息。它包含对象的激活组标识符、对象的类名、加载对象代码的 codebase 路径(或 URL)及 MarshalledObject(可包含 每次激活期间所用的对象特定初始化数据)。

激活过程中将查询在激活系统中注册的描述符以获取有关的信息,从而用于重新 创建或激活对象。对象的描述符中的 MarshalledObject 将作为第二个参数传给 远程对象的构造函数,以供激活过程使用。

package java.rmi.activation;

public final class ActivationDesc implements java.io.Serializable
{
public ActivationDesc(String className, String codebase,
java.rmi.MarshalledObject data)
throws ActivationException;
public ActivationDesc(String className, String codebase,
java.rmi.MarshalledObject data,
boolean restart)
throws ActivationException;
public ActivationDesc(ActivationGroupID groupID,
String className,
String codebase,
java.rmi.MarshalledObject data,
boolean restart);
public ActivationDesc(ActivationGroupID groupID,
String className,
String codebase,
java.rmi.MarshalledObject data);
public ActivationGroupID getGroupID();
public String getClassName();
public String getLocation();
public java.rmi.MarshalledObject getData()
public boolean getRestartMode();
}

ActivationDesc 的第一个构造函数构造一个对象的对象描述符,这个对象的类是 className(可从 codebase 路径加载),它的初始化信息(已编组形式)为 data。如果使用这种形式的构造函数,则对象的组标识符缺省为该虚拟机 ActivationGroup 的当前标识符。具有相同 ActivationGroupID 的所有对象都将在同一 虚拟机中被激活。如果当前组是非活动的或无法创建缺省组,则将抛出 Activat
ionException。如果 groupID 为 null,则将抛出 IllegalArgumentException。
?


----------------------------------------------------------------------
----------

注意 - 作为创建 ActivationDesc 的副效应,如果该虚拟机的 ActivationGrou
p 当前不是活动的,则将创建缺省 ActivationGroup。缺省激活组将 java.rmi.
RMISecurityManager 作为安全管理器,并在重新激活时将激活组虚拟机中的属性
设置为该虚拟机中的当前属性。如果应用程序需用不同的安全管理器,则在创建
缺省 ActivationDesc 之前必须设置该虚拟机的组。有关如何为虚拟机创建 Act
ivationGroup 的详细信息,参见方法 ActivationGroup.createGroup。


----------------------------------------------------------------------
----------

ActivationDesc 第二个构造函数构造对象描述符的方式与第一个构造函数相同,
但必须提供附加的参数 restart。如果对象要求重启服务,这意味着当激活器重
新启动时,对象也会自动重新启动(与根据需要激活的惰性激活相反)。此时,
restart 应为 true。如果 restart 为 false,则对象将只是在需要时激活(通
过远程方法调用)。

ActivationDesc 的第三个构造函数构造一个对象的对象描述符。这个对象的组标
识符为 groupID,它的类名为 className(可从 codebase 路径加载),它的初
始化信息为 data。所有具有相同 groupID 的对象都将在同一 Java 虚拟机中被
激活。

ActivationDesc 第四个构造函数构造对象描述符的方式与第三个构造函数相同,
但它允许指定重启模式。如果对象需要重新启动(定义如上),则 restart 应为
true。

getGroupID 方法返回该描述符所指定对象的组标识符。组可以提供一种将对象聚
合到单一 Java 虚拟机中的方法。

getClassName 方法返回该激活描述符所指定对象的类名。

getLocation 方法返回用于下载该对象的类的 codebase 路径。

getData 方法返回一个“已编组对象”,该编组对象包含用于初始化(激活)描
述符指定的对象的数据。

getRestartMode 方法在该对象的重启模式启用时返回 true,否则返回 false。

7.3.2 ActivationID 类
激活协议利用激活标识符来表示不受时间影响的可激活远程对象。激活标识符(
类 ActivationID 的实例)含有激活对象所需的若干信息:

对象激活器的远程引用
对象的唯一标识符。
对象的激活标识符可通过向激活系统注册对象来得到。可用下述几种方法之一来
完成注册(如上所述):

通过 Activatable.register 方法
通过第一个或第二个 Activatable 构造函数(都有三个参数而且注册并导出对象

通过第一个或第二个 Activatable.exportObject 方法(它们以激活描述符、对
象实现及端口为参数,且都注册并导出对象)。

package java.rmi.activation;

public class ActivationID implements java.io.Serializable
{
public ActivationID(Activator activator);
public Remote activate(boolean force)
throws ActivationException, UnknownObjectException, java.rmi.
RemoteExcep
tion;
public boolean equals(Object obj);
public int hashCode();
}

ActivationID 的构造函数接受参数 activator,该参数指定对激活器(负责激活
与该激活标识符关联的对象)的远程引用。ActivationID 的实例是全局唯一的。
activate 方法将激活与该激活标识符关联的对象。如果 force 参数为 true,激
活器将把远程对象的任何高速缓存引用视为已过时,从而迫使激活器在激活对象
时与组联系。如果 force 为 false,则返回可接受的高速缓存值。如果激活失败
,则抛出 ActivationException。如果激活器不能识别该对象标识符,则该方法
将抛出 UnknownObjectException。如果对激活器的远程调用失败,则抛出
RemoteException。
equals 方法可实现基于内容的相等比较。如果所有的域都相等(完全相同或从各
域的 Object.equals 语义上等价),它将返回 true。如果 p1 和 p2 是类 Act
ivationID 的实例,则在 p1.equals(p2) 返回 true 时 hashCode 方法返回相同
的值。

7.3.3 Activatable 类
Activatable 类提供对需要持久访问而不受时间影响,同时又被系统激活的远程
对象的支持。类 Activatable 是开发人员用来实现和管理可激活对象的主要应用
程序接口。注意,必须先运行激活系统守护进程 rmid,然后才能注册和/或激活
对象。


package java.rmi.activation;
public abstract class Activatable
extends java.rmi.server.RemoteServer
{
protected Activatable(String codebase,
java.rmi.MarshalledObject data,
boolean restart,
int port)
throws ActivationException, java.rmi.RemoteException;

protected Activatable(String codebase,
java.rmi.MarshalledObject data,
boolean restart,
int port,
RMIClientSocketFactory csf,
RMIServerSocketFactory ssf)
throws ActivationException, java.rmi.RemoteException;

protected Activatable(ActivationID id, int port)
throws java.rmi.RemoteException;

protected Activatable(ActivationID id, int port,
RMIClientSocketFactory csf,
RMIServerSocketFactory ssf)
throws java.rmi.RemoteException;

protected ActivationID getID();

public static Remote register(ActivationDesc desc)
throws UnknownGroupException, ActivationException,
java.rmi.RemoteException;

public static boolean inactive(ActivationID id)
throws UnknownObjectException, ActivationException,
java.rmi.RemoteException;

public static void unregister(ActivationID id)
throws UnknownObjectException, ActivationException,
java.rmi.RemoteException;

public static ActivationID exportObject(Remote obj,
String codebase,
MarshalledObject data,
boolean restart,
int port)
throws ActivationException, java.rmi.RemoteException;

public static ActivationID exportObject(Remote obj,
String codebase,
MarshalledObject data,
boolean restart,
int port,
RMIClientSocketFactory csf,
RMIServerSocketFactory ssf)
throws ActivationException, java.rmi.RemoteException;

public static Remote exportObject(Remote obj,
ActivationID id,
int port)
throws java.rmi.RemoteException;

public static Remote exportObject(Remote obj,
ActivationID id,
int port,
RMIClientSocketFactory csf,
RMIServerSocketFactory ssf)
throws java.rmi.RemoteException;

public static boolean unexportObject(Remote obj, boolean force)
throws java.rmi.NoSuchObjectException;
}

可激活远程对象的实现可能扩展或不扩展类 Activatable。确实扩展 Activatab
le 类的远程对象实现将从超类 java.rmi.server.RemoteObject 中继承 hashCo
de 和 equals 方法中的相应定义。因此,引用同一 Activatable 远程对象的两
个远程引用是相等的(equals 方法将返回 true)。同样,类 Activatable 的实
例将“等于”该实例的相应 stub 对象(即如果 Object.equals 方法用与该实现
匹配的 stub 对象作为参数进行调用,它将返回 true。反之亦然)。

Activatable 类方法
Activatable 类的第一个构造函数用于在指定 port 上注册和导出对象(如果 p
ort 为零,则选用某个匿名端口)。下载的类代码对象的 URL 路径是 codbase,
其初始化数据是 data。如果 restart 为 true,则对象将在激活器重新启动或组
失效时自动重新启动。如果 restart 为 false,则对象将根据需要而激活(通过
对对象的远程方法调用)。

Activatable 类的具体子类必须调用该构造函数才能在初始构造期间注册和导出
该对象。作为构造可激活对象时的副效应,远程对象将被“注册”到激活系统上
并“导出”(如果 port 为零,则在匿名端口导出)到 RMI 运行时,从而使之可
接受来自客户机的到来调用。

如果向激活系统注册对象失败,则该构造函数将抛出 ActivationException。如
果将对象导出到 RMI 运行时失败,则将抛出 RemoteException。

第二个构造函数与第一个 Activatable 构造函数相同,但它允许将客户机和服务
器套接字工厂规范用于与该可激活对象进行通讯。详细信息,参见“RMI 套接字
工厂”。

第三个构造函数用于激活对象并从指定的 port 将其(用 ActivationID,id)导
出。当对象本身被其专用的“激活”构造函数激活时,Activatable 类的具体子
类必须调用该构造函数。该“激活”构造函数的参数必须是:

对象的激活标识符 (ActivationID) 及
对象的初始化/自举数据 (MarshalledObject)。
作为构造的副效应,远程对象将被“导出”到 RMI 运行时中(从指定 port)并
可接受来自客户机的调用。如果将对象导出到 RMI 运行时失败,则该构造函数将
抛出 RemoteException。

第四个构造函数与第三个构造函数相同,但它允许将客户机和服务器套接字工厂
规范用于与该可激活对象进行通讯。

getID 方法返回对象的激活标识符。该方法是受保护的,因而只有子类才能获取
对象的标识符。 对象的标识符用于报告对象的非活动状态或注销对象的激活描述
符。

register 方法为可激活的远程对象向激活系统注册对象描述符 desc,从而使该
对象能在需要时被激活。该方法可用于注册可激活对象而无需先创建该对象。它
返回可激活对象的 Remote stub,因此可加以保存并在今后调用,从而强制首次
创建/激活该对象。如果没有向激活系统注册 desc 中的组标识符,该方法将抛出
UnknownGroupException。如果激活系统未运行,则抛出 ActivationException
。最后,如果对激活系统的远程调用失败,则抛出 RemoteException。

inactive 方法用于通知系统具有相应激活 id 的对象当前是非活动的。如果已知
该对象当前是活动的,则该对象将不从 RMI 运行时环境中导出(仅在没有待定的
或执行的调用时),从而可不再接收到来的调用。该调用还将通知该虚拟机的 A
ctivationGroup 该对象处于非活动状态;而该组又将通知其 ActivationMonito
r。如果该调用成功,则此后对激活器的激活请求将导致重新激活该对象。如果成
功地阻止该对象被导出(意味着此时它没有待定或执行调用),则 inactive 方
法返回 true;如果由于待定或进程内调用而不能阻止该对象被导出,则返回 fa
lse。如果对象是未知的(它可能是非活动的),则该方法将抛出 UnknownObjec
tException;如果组是非活动的,则抛出 ActivationException;如果通知显示
器的调用失败,则抛出 RemoteException。如果该对象被认为是活动的但已自己
阻止导出,则该操作仍将成功进行。

unregister 方法用于撤消与 id 关联的激活描述符的先前注册。对象不能再用这
个 id 来激活。如果该对象 id 对激活系统来说是未知的,则抛出 UnknownObje
ctException。如果激活系统未处于运行状态,则抛出 ActivationException。如
果对激活系统的远程调用失败,则抛出 RemoteException。

第一个 exportObject 方法可被“可激活”但不扩展 Activatable 类的对象显式
调用,其目的是:a) 向激活系统注册对象的激活描述符 desc,由所提供的 cod
ebase 和 data 构造(因此可激活该对象);b) 从指定 port 导出远程对象 ob
j(如果该 port 为零,则将选择匿名端口)。对象一旦被导出,即可接收到来的
调用。

该 exportObject 方法返回向激活系统注册描述符 desc 时所获得的激活标识符
。如果在虚拟机中激活组不是活动的,则抛出 ActivationException。如果对象
注册或导出失败,则抛出 RemoteException。

如果 obj 扩展 Activatable,则不必调用该方法,因为第一个 Activatable 构
造函数将调用该方法。

第二个 exportObject 方法与第一个相同,但它允许将客户机和服务器套接字工
厂规范用于与该可激活对象进行通讯。

第三个 exportObject 方法将带标识符 id 的“可激活”远程对象(不一定是 A
ctivatable 类型的)导出到 RMI 运行时,以使对象 obj 能够接收到来的调用。
如果 port 为零,则将在某个匿名端口导出该对象。

激活时,不扩展 Activatable 类的“可激活”对象应显式地调用此 exportObje
ct 方法。确实扩展 Activatable 类的对象不必直接调用该方法;它将被上面的
第三个构造函数所调用(应从其专用的激活构造函数中调用子类)。

该 exportObject 方法将返回可激活对象的 Remote stub。如果该对象的导出失
败,则该方法将抛出 RemoteException。

第四个 exportObject 方法与第三个相同,但它允许将客户机和服务器套接字工
厂规范用于与该可激活对象进行通讯。

unexportObject 方法使远程对象 obj 不能接收到来的调用。如果参数 force 为
true,则即使有对远程对象的待定调用或远程对象在进程内仍有调用,对象也将
被强制阻止导出。如果参数 force 为 false,则只在没有对对象的待定调用或进
程内没有调用时它才被阻止导出。如果成功地阻止了该对象的导出,则 RMI 运行
时将从其内部表中删掉它。使用这种强制方式从 RMI 删除对象可能会使客户机持
有远程对象的过时远程引用。如果此前没有将对象导出到 RMI 运行时,则该方法
将抛出 java.rmi.NoSuchObjectException。

构造可激活的远程对象
为了使对象可激活,“可激活”对象实现类不管是否扩展 Activatable 类,都必
须定义一个特殊的公共构造函数。该公共构造函数带有两个参数:激活标识符(
类型为 ActivationID)及激活数据 java.rmi.MarshalledObject(在注册时所用
的激活描述符中提供)。当激活组在其虚拟机内激活某个远程对象时,它将利用
此特殊构造函数来构造对象(后面将作详细说明)。远程对象实现可适当用激活
数据来初始化自己。远程对象也可能想保留其激活标识符,这样就可以在它变为
非活动状态(通过对 Activatable. inactive 方法的调用)时通知激活组。

Activatable 构造函数的第一种和第二种形式用于从指定的 port 注册并导出可
激活对象。最初构造对象时应使用该构造函数;该构造函数的第三种形式将在重
新激活对象时使用。

Activatable 的具体子类必须在最初构造期间调用第一种或第二种构造函数以注
册并导出对象。 构造函数首先用对象的类名创建一个激活描述符 (ActivationD
esc)、对象所提供的 codebase 和 data(其激活组为虚拟机的缺省激活组)。然
后,构造函数将向缺省的 ActivationSystem 注册该描述符。最后,构造函数在
特定 port 上(如果 port 为零,则选择匿名端口)将可激活对象导出到 RMI 运
行时中,并将该对象作为 activeObject 报告给本地 ActivationGroup。如果在
注册或导出的过程中出错,则构造函数抛出 RemoteException。注意,构造函数
也初始化它的 ActivationID(通过注册获得),因此接下来对 protected 方法
getID 的调用将返回对象的激活标识符。

Activatable 构造函数的第三种形式用于从指定的端口导出对象。当 Activatab
le 的具体子类被对象自己的“激活”构造函数激活时,必须调用第三种构造函数
。该“激活”构造函数带两个参数:

对象的 ActivationID
对象的初始化数据,一个 MarshalledObject 对象
该构造函数只将可激活对象从特定 port(如果 port 为零,则选择匿名端口)导
出到 RMI 运行时中,而并不通知 ActivationGroup 该对象已是活动对象。因为
正是 ActivationGroup 激活该对象的,因而它自然知道对象已处活动状态。

下面是远程对象接口 Server 和扩展 Activatable 类的实现 ServerImpl 的示例:

package examples;

public interface Server extends java.rmi.Remote
{
public void doImportantStuff()
throws java.rmi.RemoteException;
}

?

public class ServerImpl extends Activatable implements Server
{
// 初始构造、注册及导出时的构造函数
public ServerImpl(String codebase, MarshalledObject data)
throws ActivationException, java.rmi.RemoteException
{
// 向激活系统注册对象,然后
// 在匿名端口上导出
super(codebase, data, false, 0);
}

// 激活及导出的构造函数。该构造函数由
// ActivationInstantiator.newInstance
// 方法在激活过程中所调用,以构造对象
public ServerImpl(ActivationID id, MarshalledObject data)
throws java.rmi.RemoteException
{
// 调用父类的构造函数以
// 将对象导出到 RMI 运行时。
super(id, 0);
// 初始化对象(例如利用数据)
}

public void doImportantStuff() { ... }
}


对象将负责导出自己。Activatable 的构造函数负责将对象导出到具有活动引用
类型 UnicastRemoteObject 的 RMI 运行时中,因此,扩展 Activatable 的对象
实现不必关心显式导出对象(不是调用相应的超类构造函数)的具体细节。如果
对象实现不扩展类 Activatable,则该对象必须通过调用某个 Activatable.exp
ortObject 静态方法来显式导出该对象。

下例中,ServerImpl 不扩展 Activatable,而是扩展另一个类。因此,ServerI
mpl 负责在初始构造和激活过程中导出自己。下述的类定义给出了 ServerImpl
的初始化构造函数及其特殊“激活”构造函数,以及每个构造函数内用于导出对
象的相应调用:

package examples;

public class ServerImpl extends SomeClass implements Server
{
// 初始创建时的构造函数
public ServerImpl(String codebase, MarshalledObject data)
throws ActivationException, java.rmi.RemoteException
{
// 注册并导出对象
Activatable.exportObject(this, codebase, data, false, 0);
}

// 激活的构造函数
public ServerImpl(ActivationID id, MarshalledObject data)
throws java.rmi.RemoteException
{
// 导出对象
Activatable.exportObject(this, id, 0);
}

public void doImportantStuff() { ... }
}

在不创建对象的情况下注册激活描述符
要在不创建对象的情况下向激活系统注册可激活远程对象,程序员只需为对象注
册激活描述符(类 ActivationDesc 的实例)。激活描述符含有一切所需的信息
,因此必要时激活系统就可以激活该对象。可用以下方法来注册类 examples.Se
rverImpl 实例的激活描述符(忽略异常处理):

Server server;ActivationDesc desc;String codebase = "http://zaphod/cod
ebase/";

MarshalledObject data = new MarshalledObject("some data");desc = new A
ctivationDesc( "examples.ServerImpl", codebase, data);server = (Server
)Activatable.register(desc);

register 调用将返回一个 Remote stub(它是 examples.ServerImpl 对象的 s
tub),并实现一组与 examples.ServerImpl 所实现的远程接口相同的远程接口
(即 stub 实现远程接口 Server)。该 stub 对象(上述被强制类型转换并指派
给 server 的对象)可作为参数传给期望实现 examples.Server 远程接口的对象
的任何方法调用。


7.4 激活接口
在 RMI 激活协议中,为使系统运转正常,激活器必须保证以下两点:

象所有的系统守护进程一样,计算机处于打开状态时激活器必须保持运行状态
激活器不能重新激活已处在活动状态的远程对象。
激活器将为它所要激活的组和对象维护一个相应的信息数据库。

7.4.1 激活器接口
激活器是参与激活进程的实体之一。如前所述,stub 内的不完善引用将通过调用
激活器的 activate 方法来获得对可激活远程对象的“活”引用。激活器接到激
活请求后,就开始查找该激活标识符 id 的激活描述符,以决定要在哪个组中激
活对象,然后调用激活组的实例化器的newInstance 方法(ActivationGroup 的
远程接口将在后面说明)。激活器将根据需要启动激活组的执行过程。例如,如
果给定的组描述符的激活组还未运行,则激活器将为该激活组生成一个子虚拟机
,以便在该新虚拟机中建立该组。

激活器将负责监控和检测激活组何时失败,以便将过时的远程引用从其内部表中
删去。

package java.rmi.activation;

public interface Activator extends java.rmi.Remote
{
java.rmi.MarshalledObject activate(ActivationID id, boolean force)
throws UnknownObjectException, ActivationException,
java.rmi.RemoteException;
}


activate 方法激活与激活标识符 id 所关联的对象。如果激活器知道对象已经为
活动对象,且 force 参数为 false,就会立即将含有“活”引用的 stub 返给调
用程序;如果激活器不知道相应的远程对象是活动的或 force 参数为 true,则
激活器将利用激活描述符信息(先前已为获得 id 而注册过)来决定对象应该在
哪个组(虚拟机)中被激活。如果对象组的相应激活 Instantiator 已经存在,
则激活器将通过传递 id 和对象的激活描述符来调用该激活实例化器的newInsta
nce 方法。

如果对象组描述符的激活实例化器(组)还不存在,则激活器将启动新的 Activ
ationInstantiator 化身执行进程(例如通过生成子进程)。当激活器为某一组
重建 ActivationInstantiator 时,它必须将该组的化身数增一。注意,化身数
从 0 开始。激活系统用化身数来检测后来的 ActivationSystem.activeGroup 和
ActivationMonitor.inactiveGroup 调用。激活系统将弃去那些化身数小于组的
当前数的调用。
----------------------------------------------------------------------
----------

注意 - 激活器在启动新的激活组时必须与激活组的标识符、描述符及化身数进行
通讯。激活器将在一个独立的虚拟机中(例如作为一个独立的进程或子进程)生
成激活组。因此,它必须将信息传递过去,以指定用 ActivationGroup.createG
roup 方法来创建该组时所需的信息。激活器并未指定如何将此信息传给所生成的
进程。可用已编组对象的形式将此信息送到子进程的标准输入中。


----------------------------------------------------------------------
----------
当激活器接收到激活组的回调(通过 ActivationSystem.activeGroup 方法,该
回调可指明激活组的引用和化身数)时,激活器就可以调用该激活实例化器的 n
ewInstance 方法,从而将每个待定的激活请求转发给该激活实例化器,同时将结
果(一个已编组远程对象引用、一个 stub)返给每个调用程序。

注意,激活器接收的是 MarshalledObject 对象而不是 Remote 对象。因此,激
活器不需要加载该对象的代码,也不需要参与该对象的分布式垃圾收集。如果激
活器保留了对远程对象的强引用,则该激活器将防止该对象在正常的分布式垃圾
收集机制下被当作垃圾而收集。

如果激活失败,activate 方法将抛出 ActivationException。激活可能会因以下
各种原因而失败:找不到类、无法与激活组取得联系等。如果先前没有为激活标
识符 id 向该激活器注册激活描述符,则 activate 方法将抛出 UnknownObject
Exception。如果对该激活器的远程调用失败,则抛出 RemoteException。

7.4.2 ActivationSystem 接口
ActivationSystem 提供了一种为组和可激活对象(这些对象将在这些组中被激活
)注册的方法。 ActivationSystem 与 Activator 和 ActivationMonitor 都能
密切合作。前者负责激活通过 ActivationSystem 所注册的对象;后者负责获取
活动对象、非活动对象及非活动组的有关信息。

package java.rmi.activation;

public interface ActivationSystem extends java.rmi.Remote
{
public static final int SYSTEM_PORT = 1098;

ActivationGroupID registerGroup(ActivationGroupDesc desc)
throws ActivationException, java.rmi.RemoteException;

ActivationMonitor activeGroup(ActivationGroupID id,
ActivationInstantiator group,
long incarnation)
throws UnknownGroupException, ActivationException,
java.rmi.RemoteException;

void unregisterGroup(ActivationGroupID id)
throws ActivationException, UnknownGroupException,
java.rmi.RemoteException;

ActivationID registerObject(ActivationDesc desc)
throws ActivationException, UnknownGroupException,
java.rmi.RemoteException;

void unregisterObject(ActivationID id)
throws ActivationException, UnknownObjectException,
java.rmi.RemoteException;

void shutdown() throws java.rmi.RemoteException;
}

?

----------------------------------------------------------------------
----------

注意 - 作为一种安全措施,以上所有方法(registerGroup、activeGroup、unr
egisterGroup、registerObject、unregisterObject 和 shutdown)如果被客户
机所调用,且该客户机所处主机与激活系统不同,则将抛出 java.rmi.AccessEx
ception。该异常是 java.rmi.RemoteException 的子类。


----------------------------------------------------------------------
----------

registerObject 方法用来注册激活描述符 desc,同时也为可激活远程对象获取
激活标识符。 ActivationSystem 为描述符 desc 所指定的对象创建 Activatio
nID(激活标识符),并将激活描述符及其关联的标识符记录在稳定的存储库中以
备将来使用。当 Activator 接到某个特定标识符的 activate 请求时,它就查找
该指定标识符的激活描述符(先前已注册),并用此信息来激活该对象。如果 d
esc 中所引用的组并没有向该系统注册,则该方法将抛出 UnknownGroupExcepti
on。如果注册失败(例如,数据库更新失败等),则抛出 ActivationException
。如果远程调用失败, 则抛出 RemoteException。

unregisterObject 方法用于删除激活标识符 id 及以前向 ActivationSystem 注
册过的相关描述符。该调用完成后,就不能再用对象的激活 id 来激活该对象。
如果对象的 id 是未知的(没有被注册),则该方法将抛出 UnknownObjectExce
ption。如果注销失败(例如,数据库的更新失败等),则该方法将抛出 Activa
tionException。如果远程调用失败,则抛出 RemoteException。

registerGroup 方法用于向激活系统注册组描述符 desc 所指定的激活组,并返
回指派给该组的 ActivationGroupID。必须先向 ActivationSystem 注册激活组
,然后才能在该组中注册对象。 如果组的注册失败,则该方法将抛出 Activati
onException。如果远程调用失败,则抛出 RemoteException。

activeGroup 方法是来自 ActivationGroup(具有标识符 id)的回调,用以通知
激活系统下列信息:组现在是活动的,而且也是该虚拟机的 ActivationInstant
iator。此回调是在 ActivationGroup.createGroup 方法内部进行的,可以获取
ActivationMonitor。组将用该 ActivationMonitor 就对象和组的状态(即该组
内的组和对象已变为非活动的)对系统进行更新。 如果该组没有被注册,则抛出
UnknownGroupException。如果该组已是活动的,则抛出 ActivationException
。如果对激活系统的远程调用失败,则抛出 RemoteException。

unregisterGroup 方法将具有标识符 id 的激活组从激活系统中删掉。激活组将
进行此回调,以通知激活器应该将该组销毁。如果此调用顺利完成,就将无法再
在该组内注册和激活对象。有关该组及其相关对象的所有信息都将从系统中删掉
。如果该组是未经注册的,则该方法将抛出 UnknownGroupException。如果远程
调用失败,则抛出 RemoteException。如果注销失败(如数据库更新失败等),
则抛出 ActivationException。

shutdown 方法将以适当的异步方式终止激活系统和所有有关的激活进程(激活器
、监控器及组)。由激活守护进程所生成的所有组都将被销毁,激活守护进程也
将退出。要关闭激活系统守护进程 rmid,请执行命令:

rmid -stop [-端口号]

该命令将关闭指定端口上的激活守护进程(如果没有指定端口,则将关闭缺省端
口上的守护进程)。
 

7.4.3 ActivationMonitor 类
ActivationMonitor 是 ActivationGroup 特有的,它可通过调用 ActivationSy
stem.activeGroup 以报告某个组时获得(这种调用是在 ActivationGroup.crea
teGroup 方法内部进行的)。激活组在出现以下情况时负责通知 ActivationMon
itor:它的对象已变成活动对象、非活动对象或该组已整个变成非活动。

package java.rmi.activation;
public interface ActivationMonitor extends java.rmi.Remote
{

public abstract void inactiveObject(ActivationID id)
throws UnknownObjectException, RemoteException;

public void activeObject(ActivationID id,
java.rmi.MarshalledObject mobj)
throws UnknownObjectException, java.rmi.RemoteException;

public void inactiveGroup(ActivationGroupID id, long incarnation)
throws UnknownGroupException, java.rmi.RemoteException;
}


当激活组中的某个对象变成非活动(停用)时,该激活组将调用其监控器的 ina
ctiveObject 方法。激活组通过调用它的 inactiveObject 方法来发现其虚拟机
中的对象(该对象参与了激活)是否已为非活动状态。

inactiveObject 调用将通知 ActivationMonitor 其所保留的、具有激活标识符
id 的对象的远程对象引用已不再有效。监控器将把与 id 关联的引用视为已过
时。由于该引用被当成过时引用,因此后面为同一激活标识符所进行的 activat
e 调用将导致远程对象被重新激活。如果该对象对 ActivationMonitor 未知,该
方法将抛出 UnknownObjectException。如果远程调用失败,则抛出 RemoteExce
ption。

activeObject 调用通知 ActivationMonitor 与 id 相关联的对象现在是活动的
。参数 obj 是该对象的 stub 的编组表述。 如果组中的某个对象是通过其它方
法被激活的,而不是由系统直接激活的(也就是说,对象被注册并自己“激活”
自己),则 ActivationGroup 必须通知其监控器。如果该对象的标识符先前没有
被注册,则该方法将抛出 UnknownObjectException。如果远程调用失败,则抛出
RemoteException。

inactiveGroup 调用通知监控器由 id 和 incarnation 所指定的组现在已非活动
。一旦下一个要求在该组内激活对象的请求出现时,将用一个更大的化身数来重
新创建该组。当组中所有对象都报告说它们已非活动对象时,该组即变成非活动
组。如果该组的 id 没有被注册,或者化身数比该组当前的化身数更小,则该方
法将抛出 UnknownGroupException。如果远程调用失败,则抛出 RemoteExcepti
on。

 
7.4.4 ActivationInstantiator 类
ActivationInstantiator 负责创建可激活对象的实例。ActivationGroup 的具体
子类实现 newInstance 方法,以便对在组内创建对象的过程进行控制。


package java.rmi.activation;

public interface ActivationInstantiator extends java.rmi.Remote
{
public MarshalledObject newInstance(ActivationID id,
ActivationDesc desc)
throws ActivationException, java.rmi.RemoteException;
}


为了在该组内重新创建具有激活标识符 id、描述符 desc 的对象,激活器需要调
用实例化器的 newInstance 方法。实例化器负责:

用描述符的 getClassName 方法来决定对象的类;
从由描述符中所获得的 cadebase 路径中加载对象的类(用 getLocation 方法)

通过调用对象的类的特殊“激活”构造函数创建该类的实例。这种特殊构造函数
带有两个参数:一个是对象的 ActivationID,另一个是含有对象特有的初始化数
据的 MarshalledObject;
返回一个含有它所创建的远程对象的 MarshalledObject。
实例化器还负责在它所创建或激活的对象不再是活动对象时进行报告,因此可以
对其 ActivationMonitor 执行相应的 inactiveObject 调用(详细信息,参见
ActivationGroup 类)。

如果对象的激活失败, newInstance 方法将抛出 ActivationException。如果远
程调用失败,则该方法将抛出 RemoteException。

7.4.5 ActivationGroupDesc 类
激活组描述符 ActivationGroupDesc 含有创建或重新创建激活组(将在该激活组
中激活相同 Java 虚拟机上的对象)所需的信息。

这种描述符含有:

组的类名,
组的 cadebase 路径(组的类所在位置),
“已编组”对象,它可包含对象特定的初始化数据。
对象的类必须是 ActivationGroup 的具体子类。ActivationGroup 的子类是用
ActivationGroup.createGroup 静态方法来创建或重新创建的,该静态方法将调
用含有以下两个参数的特殊构造函数:

该组的 ActivationGroupID,
该组的初始化数据(在 java.rmi.MarshalledObject 中)

package java.rmi.activation;

public final class ActivationGroupDesc implements java.io.Serializable
{

public ActivationGroupDesc(java.util.Properties props,
CommandEnvironment env);;

public ActivationGroupDesc(String className,
String codebase,
java.rmi.MarshalledObject data,
java.util.Properties props,
CommandEnvironment env);

public String getClassName();

public String getLocation();

public java.rmi.MarshalledObject getData();

public CommandEnvironment getCommandEnvironment();

public java.util.Properties getPropertiesOverrides();

}
第一个构造函数将创建一个组描述符,它用系统的缺省值来作为组的实现和代码
位置。各属性指定了 Java 环境覆盖(它将覆盖组实现虚拟机中的系统属性)。
命令环境可控制启动子虚拟机时所用的确切命令/选项,也可为 null 以接受 rm
id 的缺省值。

第二个构造函数和第一个相同,但它允许指定 Properties 和 CommandEnvironm
ent。

getClassName 方法返回组的类名。

getLocation 方法返回 cadebase 路径,从中可加载该组的类。

getData 方法以编组形式返回组的初始化数据。

getCommandEnvironment 方法返回命令环境(可能为 null)。

getPropertiesOverrides 方法返回该描述符的属性覆盖(可能为 null)。

 

7.4.6 ActivationGroupDesc.CommandEnvironment 类
CommandEnvironment 类允许对缺省系统属性进行覆盖,并可为 ActivationGrou
p 指定由实现所定义的选项。

public static class CommandEnvironment

implements java.io.Serializable
{
public CommandEnvironment(String cmdpath, String[] args);
public boolean equals(java.lang.Object);
public String[] getCommandOptions();
public String getCommandPath();
public int hashCode();
}
 

构造函数将用所给的命令 cmdpath 和另一个命令行选项 args 创建 CommandEnv
ironment。

equals 实现对命令环境对象的内容等效操作。对 hashCode 方法进行适当的实现
可在必要时将 CommandEnvironment 对象储存在散列表中。

getCommandOptions 方法返回环境对象的命令行选项。

getCommandPath 方法返回环境对象的命令字符串。

7.4.7 ActivationGroupID 类
注册过的激活组的标识符可有以下几个用途:

在激活系统中对该组进行唯一标识
含有对该组的激活系统的引用,因而必要时该组能与其激活系统联系。
ActivationGroupID 将在调用 ActivationSystem.registerGroup 时被返回,并
用来在该激活系统内标识该组。当创建或重新创建激活组时,该组标识符将被作
为参数传给激活组的特殊构造函数。


package java.rmi.activation;

public class ActivationGroupID implements java.io.Serializable
{
public ActivationGroupID(ActivationSystem system);

public ActivationSystem getSystem();

public boolean equals(Object obj);

public int hashCode();
}

ActivationGroupID 构造函数创建唯一的组标识符。该标识符的 ActivationSys
tem 是 system。

getSystem 方法返回组的激活系统。

hashCode 方法返回组标识符的散列码。两个指向同一远程组的组标识符将具有相
同的散列码。

equals 方法比较两个组标识符内容的等效性。如果以下两个条件均成立,该方法
将返回 true: 1) 两个唯一标识符在内容上等价。2) 各标识符中指定的激活系
统都引用同一远程对象。

 

7.4.8 ActivationGroup 类
ActivationGroup 负责在其组中创建“可激活”对象的新实例,并在出现下列情
况时通知其 ActivationMonitor: 其对象变成活动或非活动,或者该组整体变成
非活动。

ActivationGroup 最初是由以下方法之一创建的:

作为创建对象的“缺省” ActivationDesc 的副效应而创建
通过对 ActivationGroup.createGroup 方法的显式调用而创建
作为在组(该组中仅 ActivationGroupDesc 被注册过)中激活第一个对象时的副
效应而创建。
只有激活器才能重新创建 ActivationGroup。必要时,激活器将为每个已注册的
激活组生成一个独立的虚拟机(例如作为子进程)并将激活请求定向到相应的组
。生成虚拟机的方式与实现有关。激活组是通过 ActivationGroup.createGroup
静态方法来创建的。createGroup 方法对即将创建的组有两个要求: 1) 该组必
须是 ActivationGroup 的具体子类;2) 该组必须有一个取以下两个参数的构造
函数:

该组的 ActivationGroupID,
该组的初始化数据(包含在 MarshalledObject中)
创建完毕,ActivationGroup 的缺省实现将在创建该组的 ActivationGroupDesc
时把系统属性设置为强制系统属性,并将安全管理器设置为 java.rmi.RMISecu
rityManager。如果应用程序要求当对象在组中被激活时设置某些特定的属性,则
该应用程序应在创建 ActivationDesc 之前(缺省 ActivationGroupDesc 创建前
)设置这些属性。

package java.rmi.activation;

public abstract class ActivationGroup
extends UnicastRemoteObject
implements ActivationInstantiator
{
protected ActivationGroup(ActivationGroupID groupID)
throws java.rmi.RemoteException;

public abstract MarshalledObject newInstance(ActivationID id,
ActivationDesc desc)
throws ActivationException, java.rmi.RemoteException;

public abstract boolean inactiveObject(ActivationID id)
throws ActivationException, UnknownObjectException,
java.rmi.RemoteException;

public static ActivationGroup createGroup(ActivationGroupID id,
ActivationGroupDesc desc,
long incarnation)
throws ActivationException;

public static ActivationGroupID currentGroupID();

public static void setSystem(ActivationSystem system)
throws ActivationException;

public static ActivationSystem getSystem()
throws ActivationException;

protected void activeObject(ActivationID id,
java.rmi.MarshalledObject mobj)
throws ActivationException, UnknownObjectException,
java.rmi.RemoteException;

protected void inactiveGroup()
throws UnknownGroupException, java.rmi.RemoteException;
}


激活器通过调用激活组的 newInstance 方法来激活具有激活描述符 desc 的对象
。激活组负责:

利用描述符的 getClassName 方法来确定该对象的类;
通过 getLocation 方法从由描述符中所得到的 URL 路径中加载该类;
通过调用该对象类的特殊构造函数来创建该类的实例。该构造函数取以下两个变
量:对象的 ActivationID 和含有对象初始化数据的 MarshalledObject;
将刚创建的远程对象的序列化版本返给激活器。
如果无法创建所给描述符的实例,该方法将抛出 ActivationException。

对该组的 inactiveObject 方法的调用是通过调用 Activatable.inactive 方法
来间接进行的。当远程对象停用时(该对象被认为非活动的),该对象的实现必
须调用 Activatable 的 inactive。如果对象在非活动时不调用 Activatable.i
nactive,则该对象将永远不会被作为垃圾收集,因为组将对其所创建的对象保留
强引用。

组的 inactiveObject 方法可阻止从 RMI 运行时中导出与 id 关联的远程对象(
仅当没有对该对象的待定或正在执行的调用时)。使该对象将无法再接收到来的
RMI 调用。如果该对象当前还有待定或正在执行的调用,inactiveObject 将返
回 false 且不采取任何动作。

如果 unexportObject 操作成功,意味着该对象没有待定或正在执行的调用。此
时组将通过监控器的 inactiveObject 方法通知它的 ActivationMonitor 该远程
对象当前是非活动的,以便激活器在接到下一激活请求时即重新将该远程对象激
活。如果操作成功,inactiveObject 将返回 true。如果该对象被 ActivationG
roup 认为是活动的但已被阻止导出,操作将仍有可能成功。

如果激活组不知道该对象(例如该对象先前已被报告为是非活动的,或者从未通
过该激活组来激活该对象),则 inactiveObject 方法将抛出 UnknownObjectEx
ception。如果非活动操作失败(例如,对激活器或激活组的远程调用失败),则
将抛出 RemoteException。

createGroup 方法将为当前虚拟机创建和设置激活组。只有在激活组是当前未设
定的情况下才能对它进行设置。当 Activator 为了执行到来的 activate 请求而
启动某一激活组的重新创建过程时,该激活组即通过 createGroup 方法进行设置
。组必须先向 ActivationSystem 注册一个描述符,然后才能用该方法(将注册
中得到的 ActivationID 传给它)来创建该组。

由 ActivationGroupDesc(即 desc)指定的组必须是 ActivationGroup 的具体
子类并具有公共构造函数。该公共构造函数取两个参数:一个是组的 Activatio
nGroupID;另一个是 MarshalledObject,它含有组的初始化数据(从组的Activ
ationGroupDesc 中获得的)。注意: 如果应用程序要创建自己的自定义激活组
,该组必须在构造函数中设置安全管理器。否则将不能在该组中激活对象。

创建完组后,即通知 ActivationSystem 该组已通过调用 activeGroup 方法(该
方法将返回组的 ActivationMonitor)而被激活。应用程序不必单独调用 activ
eGroup,因为 createGroup 方法将负责这一回调。

一旦组被创建,对 currentGroupID 方法的后续调用都将返回该组的标识符,直
到该组变成非活动组为止。那时,currentGroupID 方法将返回 null。

参数 incarnation 表示当前的组化身数,也就是该组被激活的次数。一旦组被成
功创建,化身数就被用作 activeGroup 方法的参数。化身数从 0 开始。如果该
组已经存在,或在组的创建过程中出错,createGroup 方法将抛出 ActivationE
xception。

setSystem 方法用于设置虚拟机的 ActivationSystem (system)。只有当前没有
组为活动组时才能对激活系统进行设置。如果没有通过显式调用 setSystem 来设
置激活系统,则 getSystem 方法将尝试通过在 Activator 的注册服务程序中查
找名称 java.rmi.activation.ActivationSystem 来获得对 ActivationSystem
的引用。缺省情况下,查找激活系统所用的端口号将由 ActivationSystem.SYST
EM_PORT 定义。通过设置属性 java.rmi.activation.port 可将该端口覆盖掉。
如果调用 setSystem 时激活系统已经设置好,则该方法将抛出 ActivationExce
ption。

getSystem 方法返回该虚拟机的激活系统。激活系统可由 setSystem 方法(前述
)设置。

activeObject 方法是一个受保护的方法。子类将用它来对组监控器进行 active
Object 回调,以通知监控器具有指定激活 id 且其 stub 包含在 mobj 中的远程
对象现在是活动的。这一调用只是转发给组的 ActivationMonitor。

inactiveGroup 方法是一个受保护的方法。子类用它来通知组监控器该组已变成
非活动的。当组中每个参与虚拟机中激活活动的对象都变成非活动时,子类就进
行此调用。

7.4.9 MarshalledObject 类
MarshalledObject 是某个对象的容器,它允许在进行 RMI 调用时将该对象当作
一个参数来传递,但它延迟该对象在接收端上的序列化恢复,直到应用程序明确
地请求该对象(通过调用该容器对象)。包含在该 MarshalledObject 中的可序
列化对象是用相同的语义(该语义作为 RMI 调用中的参数来传递)来序列化和序
列化恢复的(请求时)。也就是说,MarshalledObject 中的任一远程对象都可表
示为其 stub 的序列化实例。MarshalledObject 所包含的对象可以是远程对象、
非远程对象或远程对象和非远程对象的完整图形。

当对象被放入 MarshalledObject 的 wrapper 中时,该对象的序列化形式将被注
释为 cadebase URL(可从中加载类);同样,当从对象的 MarshalledObject
的 wrapper 中取回该对象时,如果本地没有该对象的代码,则使用该 URL(序列
化时所注解的)来定位和加载该对象类的字节代码。

package java.rmi;

public final class MarshalledObject implements java.io.Serializable
{
public MarshalledObject(Object obj) throws java.io.IOException;

public Object get()
throws java.io.IOException, ClassNotFoundException;

public int hashCode();

public boolean equals();
}


MarshalledObject 的构造函数将可序列化的对象 obj 作为单一参数,并将对象
的编组表示存放在字节流中。对象的编组表示保存了对象在 RMI 调用时所传递的
语义:

流中的每个类都被注解为其 cadebase URL,以便在重构该对象(通过调用 get
方法)时可以找到各个类的字节代码并进行加载;
远程对象由它们的代理服务器 stub 所代替。
当把类 MarshalledObject 的实例写到 java.io.ObjectOutputStream 中时,所
含对象的编组形式(在构造中创建)亦被写到流中。因此,只有字节流才被序列
化。

当从 java.io.ObjectInputStream 中读取 MarshalledObject 时,并不把所包含
的对象序列化恢复为一个具体对象;该对象将一直保持其编组表示,直到该已编
组对象的 get 方法被调用为止。

get 方法总是要用所含对象的编组形式来重新构造该对象。内部表示将用解编参
数(用于 RMI 调用)所用的语义来进行序列化恢复。因此,对象表示的序列化恢
复将用嵌入对象序列化流中的 URL 注解来加载类的代码(如果本地没有该代码)

对象编组表示的 hashCode 与传递给构造函数的对象相同。如果进行比较的对象
的编组表示是等价的,则 equals 方法将返回 true。该 equals 方法所采用的比
较将忽略类的 cadebase 注解,即除了序列化表示中各个类的 cadebase 外,只
要两个对象具有相同的序列化表示,则这两个对象就是等价的。

?

?

?


RMI规范(8)
第 8 章

本章包含 rmic stub 编译器生成的 stub 和 skleton 所用的接口和类。

主题:
RemoteStub 类
RemoteCall 接口
RemoteRef 接口
ServerRef 接口
Skeleton 接口
Operation 类

8.1 RemoteStub 类
java.rmi.server.RemoteStub 类是远程对象 stub 的通用超类。stub 对象是一
种代理,它支持的远程接口集与远程对象的实际实现所定义的完全相同。

package java.rmi.server;
public abstract class RemoteStub extends java.rmi.RemoteObject
{
protected RemoteStub();
protected RemoteStub(RemoteRef ref);
protected static void setRef(RemoteStub stub, RemoteRef ref);
}


RemoteStub 的第一个构造函数将创建带有空远程引用的 stub。第二个构造函数
将创建带有给定远程引用 ref 的 stub。

JDK1.2 中不鼓励使用(且不支持)setRef 方法。

8.1.1 远程对象与 stub 类的类型等价性
客户机可与 stub(代理)对象进行交互,该 stub 的远程接口集与远程对象类所
定义的完全相同。由于 stub 类是由实现一个或多个远程接口的实现类精华所生
成的,因此 stub 类不包括类层次(组成对象的类型图)的非远程部分。例如,
如果 C 扩展 B 而 B 扩展 A,但只有 B 实现远程接口,则 stub 将由 B 生成,
而非 C。

由于 stub 实现与远程对象类相同的远程接口集,因此从 Java 系统的角度看,
stub 拥有与服务器对象类型图的远程部分相同的类型。这样,客户机即可利用内
置 Java 操作来检查远程对象的类型,同时也可从一个远程接口向另一远程接口
进行强制类型转换。

stub 是用 rmic 编译器生成的。
8.1.2 声明为终态的对象方法的语义
以下方法在 java.lang.Object 类中被声明为终态 final,因此不能被任意实现
所覆盖:

getClass
notify
notifyAll
wait
getClass 的缺省实现适用于所有 Java 对象(本地或远程);对于远程对象,该
方法不需要专门的实现。getClass 方法在远程 stub 上使用时,它报告由 rmic
生成的 stub 对象的确切类型。注意,stub 类型仅反映远程对象实现的远程接
口,而非本地接口。

java.lang.Object 的 wait 和 notify 方法将处理 Java 语言线程模型上下文中
的等待和通知。虽然对远程 stub 使用这些方法不会违反 Java 线程模型,但这
些方法的语义将与用于本地 Java 对象时的语义不同。尤其,使用这些方法时所
操作的将是远程对象的客户机本地引用 (stub),而不是远程站点的实际对象。

8.2 RemoteCall 接口
RemoteCall 是远程对象的 stub 和 skeleton 所用的抽象接口,用来执行对远程
对象的调用。

----------------------------------------------------------------------
----------

注意 - JDK 1.2 中不鼓励使用 RemoteCall 接口。JDK 1.2 stub 协议也已不再
使用此接口。现在,stub 在 JDK 1.2 中使用新的 invoke 方法,该方法不需要
将 RemoteCall 作为参数。


----------------------------------------------------------------------
----------

package java.rmi.server;
import java.io.*;

public interface RemoteCall
{
ObjectOutput getOutputStream() throws IOException;
void releaseOutputStream() throws IOException;
ObjectInput getInputStream() throws IOException;
void releaseInputStream() throws IOException;
ObjectOutput getResultStream(boolean success)
throws IOException, StreamCorruptedException;
void executeCall() throws Exception;
void done() throws IOException;
}


方法 getOutputStream 返回输出流。stub 将把参数编组到其中,或者 skeleton
将把结果编组到其中。

方法 releaseOutputStream 释放输出流。在某些传输中,这将导致释放流。

方法 getInputStream 返回 InputStream。stub 从中解编结果,或者 skeleton
从中解编参数。

方法 releaseInputStream 释放输入流。这将允许某些传输提前释放连接的输入
端。

方法 getResultStream 返回输出流(写完与调用成功有关的标题信息后)。每次
远程调用将只能获得一次结果流。如果 success 为真,则要编组的结果属于正常
返回;否则结果为异常。如果已获得该远程调用的结果流,则抛出
StreamCorruptedException。

方法 executeCall 尽其所能执行调用。

方法 done 允许完成远程调用后进行清除。
.3 RemoteRef 接口
接口 RemoteRef 表示远程对象的句柄。每个 stub 均包含 RemoteRef 的一个实
例,其中包含引用的具体表示。该远程引用将用来在其代表的远程对象上执行远
程调用。

package java.rmi.server;

public interface RemoteRef extends java.io.Externalizable
{
Object invoke(Remote obj,
java.lang.reflect.Method method,
Object[] params,
long opnum)
throws Exception;

RemoteCall newCall(RemoteObject obj, Operation[] op, int opnum, long hash)
throws RemoteException;
void invoke(RemoteCall call) throws Exception;
void done(RemoteCall call) throws RemoteException;
String getRefClass(java.io.ObjectOutput out);
int remoteHashCode();
boolean remoteEquals(RemoteRef obj);
String remoteToString();
}

第一个 invoke 方法将方法调用代理给 stub (obj) 的远程引用,并允许引用负
责建立对远程主机的连接,将 method 和参数 params 的一些表示进行编组,然
后使用方法调用与远程主机进行通讯。该方法将返回驻留在远程主机上的远程对
象的方法调用结果,或者在调用失败时抛出 RemoteException,而在远程调用抛
出异常时则抛出应用程序级异常。注意,操作号 opnum 表示一散列方法签名,可
以用来对要传输的方法进行编码。

opnum 的方法散列是一个 64 位(长)整数,是根据美国国家标准技术局 (NIST
) 的安全散列算法 (SHA-1),用字节流消息摘要的前两个 32 位值计算而来的。
字节流包含由远程方法名后跟其方法描述符组成的字符串的 UTF 编码(有关方法
描述符的说明,参见 Java 虚拟机规范的第 4.3.3 节)。散列值将从 SHA-1 散
列的第一个和第二个 32 位值得到。

long hash = sha[1] << 32 + sha[0]

?

----------------------------------------------------------------------
----------

注意 - JDK 1.2 中不鼓励使用 newCall、invoke 和 done 方法。rmic 用 JDK
1.2 stub 协议版本生成的 stub 不再使用这些方法。由 newCall、invoke 和 d
one 组成的调用序列已被新的 invoke 方法所替换,该新方法将 Method 对象作
为一个参数。


----------------------------------------------------------------------
----------

方法 newCall 将为远程对象 obj 上的新远程方法调用创建相应的调用对象。操
作数组 op 包含远程对象上的可用操作。操作号 opnum 是对操作数组的索引,它
指定该远程调用的特定操作。 传递操作数组和索引可使 stub 生成器能分派操作
索引并进行解释。远程引用可能需要操作说明以在调用中进行编码。

方法 invoke 负责执行远程调用。invoke 将产生能顺利通过而不被 stub 捕获的
“用户”异常。如果在远程调用过程中产生异常,则 invoke 应注意在产生“用
户异常”或 RemoteException 之前清除连接。

方法 done 允许远程引用清除(或重新使用)连接。只有当 invoke 调用已成功
地(非异常)返回 stub 时,才应调用 done。

方法 getRefClass 返回将被序列化到流 out 上的引用类型的非包限定的类全名

方法 remoteHashCode 返回远程对象的散列码。两个引用相同远程对象的远程对
象 stub 将有相同的散列码(以支持在散列表中将远程对象作为键值)。Remote
Object 将把对其 hashCode 方法的调用转发给远程引用的 remoteHashCode 方法

方法 remoteEquals 比较两个远程对象的等价性。如果两个远程对象引用相同的
远程对象,即认为它们等价。例如,如果两个 stub 引用相同的远程对象,则认
为它们等价。 RemoteObject 将把对其 equals 方法的调用转发给远程引用的 r
emoteEquals 方法。

方法 remoteToString 返回表示该远程对象的引用的 String。

8.4 ServerRef 接口
接口 ServerRef 表示远程对象实现的服务器端句柄。

package java.rmi.server;

public interface ServerRef extends RemoteRef {

RemoteStub exportObject(java.rmi.Remote obj, Object data)
throws java.rmi.RemoteException;

String getClientHost() throws ServerNotActiveException;
}


方法 exportObject 将为所提供的 Remote 对象实现 obj 查找或创建客户机 st
ub 对象。参数 data 包含导出对象所需的信息(如端口号)。

方法 getClientHost 返回当前客户机的主机名。当它被当前正在处理远程对象调
用的线程调用时,将返回执行此调用的客户机主机名。如果当前某一远程方法调
用未处于服务状态,则调用 ServerNotActiveException。

8.5 Skeleton 接口
接口 Skeleton 仅能由 rmic 编译器所生成 skeleton 的实现。远程对象的 ske
leton 是服务器端的实体,它将为实际远程对象实现分配调用。


----------------------------------------------------------------------
----------

注意 - JDK1.2 中不鼓励使用 Skeleton 接口。每个由 rmic stub 编译器生成的
1.1(以及在 1.2 中由缺省 rmic -vcompat 生成的兼容 1.1 的 skeleton)sk
eleton 类均实现该接口。JDK1.2 兼容版本中分配远程方法调用将不再需要 ske
leton。要生成兼容 JDK1.2 或更高版本的 stub,请使用带有选项 -v1.2 的命令
rmic。


----------------------------------------------------------------------
----------

package java.rmi.server;

public interface Skeleton
{

void dispatch(Remote obj, RemoteCall call, int opnum, long hash)
throws Exception;
Operation[] getOperations();
}


dispatch 方法将解编 call 对象中获得的输入流中的参数,调用实际远程对象实
现 obj 上的方法(由操作号 opnum 表示),然后将返回值进行编组。如果在调
用过程中发生异常,则抛出异常。

getOperations 方法返回包含远程对象方法的操作描述符的数组。
8.6 Operation 类
类 Operation 包含对远程对象 Java 方法的说明。


----------------------------------------------------------------------
----------

注意 - JDK 1.2 中不鼓励使用 Operation 接口。JDK 1.2 stub 协议不再使用原
有的、以 Operation 作为参数的 RemoteRef.invoke 方法。stub 在 JDK 1.2 中
使用了新的 invoke 方法,它不再需要将 Operation 作为参数。


----------------------------------------------------------------------
----------

package java.rmi.server;

public class Operation
{

public Operation(String op);

public String getOperation();

public String toString();
}


构造 Operation 对象时通常带有方法签名。

方法 getOperation 返回操作描述符的内容(其初始化时使用的值)。

方法 toString 也返回操作描述符的字符串表示(通常为方法签名)。

?

?

?

?


RMI规范(9)
第 9 章

本章中的接口和类用于 RMI 的分布式垃圾收集器 (DGC)。

主题:
接口 DGC
Lease 类
ObjID 类
UID 类
VMID 类
9.1 接口 DGC
DGC 抽象用于分布式垃圾收集算法的服务器端。此接口包含两种方法:dirty 和
clean。当远程引用在客户机(客户机由 VMID 表示)端解编时,将调用 dirty
。当客户机中不再存在对远程引用的引用时,将进行相应的清理调用。失败的 d
irty 调用必须预定一次 strong(强)clean 调用,以便保留调用顺序号码,从
而检测以后由分布式垃圾收集器接收的无序调用。

对远程对象的引用将被拥有该引用的客户机租用 (leased) 一段时间。租用期从
接收到 dirty 调用时开始。客户机必须在租用到期前在其所保存的远程引用上再
次调用 dirty,以便进行续租。如果在到期之前不续租,则分布式垃圾收集器就
认为该客户机不再引用远程对象。

package java.rmi.dgc;
import java.rmi.server.ObjID;

public interface DGC extends java.rmi.Remote
{

Lease dirty(ObjID[] ids, long sequenceNum, Lease lease)
throws java.rmi.RemoteException;

void clean(ObjID[] ids, long seqNum, VMID vmid, boolean strong)
throws java.rmi.RemoteException;
}


方法 dirty 请求租用与数组参数 ids 中所含对象标识符相关联的远程对象引用
。lease 包含客户机唯一虚拟机标识符 (VMID) 和请求的租用期。垃圾收集器将
为每个本地虚拟机中导出的远程对象保留一个 reference list(引用列表)--
保存引用的客户机列表。如果准予租用,垃圾收集器将把客户机 VMID 添加到 i
ds 中指示的每个远程对象的引用列表中。sequenceNum 用来检测和丢弃后期垃圾
收集器调用的顺序号。每次调用垃圾收集器,顺序号都将增大。

某些客户机不能生成唯一的 VMID。这是因为只有当 VMID 包含 true(真)主机
地址时,才是通用的唯一标识符。由于安全性限制,某些客户机将无法获得该地
址。此种情况下,客户机可以使用空 VMID。分布式垃圾收集器将为客户机指派
VMID。

dirty 调用将返回 Lease 对象,其中包含远程引用所用的 VMID 和准许的租用期
(服务器可以决定授予比客户机请求短的租用期)。客户机必须使用垃圾收集器
所用的 VMID,以便在客户机释放远程对象引用时进行相应的 clean 调用。

客户虚拟机仅需对每个虚拟机中引用的远程引用进行一次初始 dirty 调用(即使
它对同一远程对象进行多次引用)。此外,客户机还必须在租用到期前调用 dir
ty 对远程引用的租用进行续租。当客户机对特定的远程对象再无任何引用时,必
须为与该引用关联的对象 ID 预定一次 clean 调用。

clean 调用将从 ids 指定的远程对象的引用列表中删除 vmid。顺序号用来检测
以后的清理操作。如果参数 strong 为真,则代表 clean 调用是一次失败的 di
rty 调用的结果。此时应记住客户机 vmid 的顺序号。

9.2 Lease 类
租用包含一个唯一的虚拟机标识符和一个租用期。Lease 对象用来请求及准予对
远程对象引用的租用。

package java.rmi.dgc;

public final class Lease implements java.io.Serializable
{

public Lease(VMID id, long duration);

public VMID getVMID();

public long getValue();
}


Lease 构造函数创建带有特定 VMID 和租用期的租用。VMID 可以为 null。

getVMID 方法返回与租用相关联的客户机 VMID。

getValue 方法返回租用期。

9.3 ObjID 类
类 ObjID 用来作为虚拟机中对远程对象的唯一标识。每个标识符均包含一个对象
号和一个特定主机上的唯一地址空间标识符。对象标识符将在导出远程对象时指
派给该对象。

ObjID 由对象号 (long) 和地址空间的唯一标识符 (UID) 组成。

package java.rmi.server;

public final class ObjID implements java.io.Serializable
{

public ObjID ();

public ObjID (int num);

public void write(ObjectOutput out) throws java.io.IOException;

public static ObjID read(ObjectInput in)
throws java.io.IOException;

public int hashCode()

public boolean equals(Object obj)

public String toString()
}


ObjID 构造函数的第一种形式将生成唯一的对象标识符。第二个构造函数生成已
知的对象标识符(例如注册服务程序和分布式垃圾收集器所用的标识符),并将
已知的对象号用作参数。第二个构造函数所生成的已知对象 ID 与缺省构造函数
所生成的对象 ID 不冲突;为强制实现这一点,ObjID 的对象号将被设置为构造
函数中提供的“已知”号,同时所有 UID 域均将设置为零。

方法 write 将对象 ID 的表示法编组到输出流中。

方法 read 构造一个对象 ID,其内容将从指定的输入流中读取。

方法 hashCode 将对象号作为 hashcode 返回。

如果 obj 与 ObjID 内容相同,则 equals 方法返回 true。

toString 方法返回一个包含对象 ID 表示法的字符串。仅当对象 ID 来自非本地
地址空间时,地址空间标识符才将含在字符串表示法中。

.4 UID 类
UID 是一种抽象类,用来创建生成它的主机的唯一标识符。它将以地址空间标识
符的形式含在 ObjID 中。UID 由一个在主机上唯一的号码 (int)、时间 (long)
和计数组成 (short)。

package java.rmi.server;

public final class UID implements java.io.Serializable
{

public UID();

public UID(short num);

public int hashCode();

public boolean equals(Object obj);

public String toString();

public void write(DataOutput out) throws java.io.IOException;

public static UID read(DataInput in) throws java.io.IOException;
}


构造函数的第一种形式将创建生成它的主机的唯一纯标识符。此 UID 在以下条件
下具有唯一性:a) 计算机需要一秒以上的时间进行重新启动,b) 计算机的时钟
将永不向前调整。为了构造全局唯一的 UID,只需将 UID 与 InetAddress 分别
配对。

构造函数的第二种形式将创建一种已知的 UID。已知 ID 有 216 -1 种可能。这
种构造函数所生成的 ID 不会与缺省 UID 构造函数所生成的 ID 冲突。缺省构造
函数生成主机上真正唯一的标识符。

方法 hashCode、equals 和 toString 是为 UID 所定义的。如果两个 UID 的内
容相同,则视为二者等价。

方法 write 将把 UID 写入输出流中。

方法 read 构造一个 UID,其内容将从指定输入流中读取。

?

?

?


RMI规范(10)
RMI 通信协议

10.1 概述
RMI 协议使用另两个协议作为其在通信格式:Java 对象序列化和 HTTP。对象序
列化协议用于编组调用和返回数据。HTTP 协议用于“投寄”远程方法调用,并在
情况允许时获得返回数据。每种协议都有专门的语法文档。产品规则中的非终结
符号可能会引用其它协议(对象序列化或 HTTP)所管理的规则。在跨协议边界时
,后续产品将使用该嵌入的协议。

关于语法符号的说明
我们使用与 Java 语言规范(参见 JLS 的第 2.3 节)中类似的符号。
流中的控制代码由十六进制形式的文字值表示。
语法中的有些非终结符号表示调用中提供的与应用程序有关的值。这种非终结符
号的定义由其 Java 类型组成。语法后面是将这些非终结字符映射到相应类型的
表。
10.2 RMI 传输协议
RMI 的通信格式由 Stream 表示。这里所采用的术语是从客户机的角度来讲的。
out 指输出消息,而 in 指输入消息。传输标题的内容并未用对象序列化进行格
式化。

Stream: Out In

RMI 所用的输入和输出流是成对出现的。每个 out 流都有相应的 in 流。在语法
中,out 流映射到套接字的输出流(从客户机角度)。in 流(在语法中)将与相
应套接字的输入流配对。由于输出与输入流是成对的,所以输入流中唯一需要的
标题信息就是一个表示是否理解协议的确认;其他标题信息(例如魔数和版本号
)都将由流对的上下文所隐含。

10.2.1 输出流格式
RMI 中的输出流由传输标题信息后跟一个消息序列组成。此外,输出流也可包含
嵌入在 HTTP 协议中的调用。

Out: Header Messages HttpMessage

Header: 0x4a 0x52 0x4d 0x49 Version Protocol

Version: 0x00 0x01

Protocol: StreamProtocol SingleOpProtocol MultiplexProtocol

StreamProtocol: 0x4b

SingleOpProtocol: 0x4c

MultiplexProtocol: 0x4d

Messages: Message Messages Message

Messages 将包装在 Protocol 指定的特定协议内。对于 SingleOpProtocol,He
ader 的后面可能只有一个 Message,且该 Message 没有包装其它数据。Single
OpProtocol 用于 HTTP 请求中所嵌入的调用,其中请求和响应都只能为一个。


对于 StreamProtocol 和 MultiplexProtocol,服务器必须用字节 0x4e(表示支
持该协议)和 EndpointIdentifier(包含主机名和端口号,服务器可以看到它们
在被客户机使用)进行响应。如果由于安全原因而无法执行该操作,客户机即可
使用该信息来确定其主机名。随后,客户机必须用另一个包含接受连接的缺省端
点的 EndpointIdentifier 进行响应。对于MultiplexProtocol,服务器可以用它
来标识客户机。

对于 StreamProtocol,本次端点协商后,将在输出流上发送 Messages,而不对
数据进行进一步打包。对于 MultiplexProtocol,套接字将连接用作多路复用连
接的具体连接,如第 10.6 节“RMI 的多路复用协议”中所述。在该多路复用连
接上初始化的虚拟连接由一系列 Messages 组成,如下所述。

输出消息共有三种:Call、Ping 和 DgcAck。Call 将对方法调用进行编码。Pin
g 是一个传输级消息,用于测试远程虚拟机的活动性。DGCAck 是一个对服务器的
分布式垃圾收集器的确认,指示客户机已经接收到服务器返回值中的远程对象。


Message: Call Ping DgcAck

Call: 0x50 CallData

Ping: 0x52

DgcAck: 0x54 UniqueIdentifier

10.2.2 输入流格式
当前输入信息共有三种:ReturnData、HttpReturn 和 PingAck。ReturnData 是
“正常”RMI 调用的结果。HttpReturn 是 HTTP 协议中嵌入调用的返回结果。P
ingAck 是对 Ping 消息的确认。

In: ProtocolAck Returns ProtocolNotSupported HttpReturn

ProtocolAck: 0x4e

ProtocolNotSupported: 0x4f

Returns: Return Returns Return

Return: ReturnData PingAck

ReturnData: 0x51 ReturnValue

PingAck: 0x53
10.3 RMI 对对象序列化协议的使用
RMI 调用中的调用和返回数据将使用 Java 对象序列化协议进行格式化。每个方
法调用的 CallData 表示为 ObjectIdentifier(调用的目标)、Operation(代
表要调用方法的数字)、Hash(检验客户机 stub 与远程对象 skeleton 是否使
用同一 stub 协议的数字),后跟该调用的零个或多个参数列表。

在 JDK1.1 stub 协议中,Operation 代表方法号(由 rmic 分配),而 Hash 是
stub/skeleton 散列,它是该 stub 的接口散列。在 JDK1.2 stub 协议(利用
带 -v1.2 选项的 rmic 生成 JDK1.2 stub)中,Operation 的值为 -1 且 Hash
代表了所要调用方法的散列。散列将在“ RemoteRef 接口”一节中介绍。

CallData: ObjectIdentifier Operation Hash Argumentsopt

ObjectIdentifier: ObjectNumber UniqueIdentifier

UniqueIdentifier: Number Time Count

Arguments: Value Arguments Value

Value: Object Primitive

RMI 调用的 ReturnValue 由指示正常或异常返回的返回代码、标记返回值的 Un
iqueIdentifier(用于在必要时发送 DGCAck)后跟以下返回结果组成:返回的值
或抛出的异常。

ReturnValue: 0x01 UniqueIdentifier Valueopt 0x02 UniqueIdentifier Exc
eption


----------------------------------------------------------------------
----------

注意 - ObjectIdentifier、UniqueIdentifier 和 EndpointIdentifier 并不是
用缺省序列化编写的,而是各自使用自己的 write 方法(但不是对象序列化所用
的 writeObject 方法);每种标识符的 write 方法都将其组件数据连续添加到
输出流中。


----------------------------------------------------------------------
----------

10.3.1 类注解和类加载
RMI 分别覆盖了 ObjectOutputStream 和 ObjectInputStream 的 annotateClas
s 和 resolveClass 方法。每个类都用 codebase URL(加载该类的位置)进行注
解。annotateClass 方法中将查询加载该类的类加载器以得到其 codebase URL。
如果类加载器非空且其 codebase 也为非空,则将使用 ObjectOutputStream.wr
iteObject 方法将该 codebase 写入流中;否则将使用 writeObject 方法将空值
写入流中。注意:最好不要注解“java”包中的类,因为它们对于接收者来说总
是可用的。

类注解是在序列化恢复期间用 ObjectInputStream.resolveClass 方法解析的。
resolveClass 方法首先用 ObjectInputStream.readObject 方法读取注解。如果
注解(codebase URL)非空,它就获得该 URL 的类加载器并试图加载该类。利用
java.net.URLConnection 获取类字节,即可对该类进行加载(与 web 浏览器的
applet 类加载器所用的机制相同)。

10.4 RMI 对 HTTP POST 协议的使用
为了通过防火墙调用远程方法,有些 RMI 调用使用了 HTTP 协议,尤其是 HTTP
POST。在传递标题中指定的 URL 可以为下列内容之一:

http://:/ http:///cgi-bin/java-rmi?forward=


第一个 URL 用于与特定 host 和 port 上的 RMI 服务器直接通信。第二种形式
的 URL 用于调用服务器上的“cgi”脚本,后者将把调用转发给指定 port 上的
服务器。

HttpPostHeader 是 POST 请求的标准 HTTP 标题。HttpResponseHeader 是对传
递过程的标准 HTTP 响应。如果响应状态代码不是 200,则认为没有返回值。注
意一个 HTTP POST 请求中只能嵌入一个 RMI 调用。

HttpMessage: HttpPostHeader Header Message

HttpReturn: HttpResponseHeader Return

----------------------------------------------------------------------
----------

注意 - 只有 SingleOpProtocol 出现在 HttpMessage 的标题中。HttpReturn 不
包含用于确认协议的字节。


----------------------------------------------------------------------
----------
10.5 RMI 的与应用程序有关的值
本表列表出 RMI 所用的代表与应用程序有关的值的非终结符号。该表将每个符号
映射为相应的类型。每个符号都分别使用它所嵌入在其中的协议进行格式化。

Count short

Exception java.lang.Exception

Hash long

Hostname UTF

Number int

Object java.lang.Object

ObjectNumber long

Operation int

PortNumber int

Primitive byte, int, short, long...

Time long

10.6 RMI 的多路复用协议
多路复用的目的是提供一种模型,其中两个端点都可打开多个到另一端点的全双
工连接,而在相同环境下,使用其他工具(例如 TCP 连接)时,只有一个端点能
打开这样的双向连接。利用这种简单的多路复用协议,RMI 即可允许客户在某些
其他协议无能为力的情况下,连接到 RMI 的服务器对象上。例如,有些 applet
环境的安全管理器不允许创建服务器套接字监听到来的连接,以防止这种 appl
et 从直接套接字连接上导出 RMI 对象及提供远程调用服务。但是,如果该 app
let 可以打开到其 codebase 主机的正常套接字连接,它就可以在该连接上使用
多路复用协议,从而允许 codebase 主机调用该 applet 所导出的 RMI 对象的方
法。本节介绍了多路复用协议的格式和规则。

?


10.6.1 定义
本节定义一些将在协议其余部分使用的术语。

端点是用多路复用协议连接的两个用户之一。

多路复用协议必须位于已有的双向可靠字节流之上,假设由一个端点向另一个端
点进行初始化。在当前的 RMI 用法中,它通常是 TCP 连接,由 java.net.Sock
et 对象建立。该连接称为具体连接。

多路复用协议有助于虚拟连接的使用。虚拟连接本身就是双向的可靠字节流,代
表两个端点之间的特定会话。一个连接上两个端点之间的虚拟连接集组成一个多
路复用连接。使用多路复用协议,虚拟连接可以由任一端点打开和关闭。虚拟连
接相对给定端点的状态由在具体连接上发送和接收的多路复用协议元素定义。该
状态涉及连接是打开还是关闭、传送的实际数据及相关的流控制机制。如果没有
特别说明,本节中其余部分将使用术语连接表示虚拟连接。

给定多路复用连接内的虚拟连接由一个 16 位整数标识,称为连接标识符。因而
,一个多路复用连接中可能存在 65,536 个虚拟连接。实现可能会限制能同时使
用的虚拟连接数。

10.6.2 连接状态和流控制
连接由用多路复用协议定义的各种操作来控制。下面是协议所定义的操作名:OP
EN、CLOSE、CLOSEACK、REQUEST 和 TRANSMIT。所有操作的准确格式和规则将在
第 10.6.3 节 “协议格式”中详细介绍。

OPEN、CLOSE 和 CLOSEACK 操作控制连接的打开和关闭,而 REQUEST 和 TRANSM
IT 操作用于在流控制机制的限制内在打开的连接上传输数据。

连接状态
如果端点发送连接的 OPEN 操作或接收到连接的 OPEN 操作(且随后没有关闭它
),则该虚拟连接相对于该端点即为打开的。下面介绍不同的协议操作。

如果端点发送连接的 CLOSE 操作,但随后没有接收到该连接的 CLOSE 或 CLOSE
ACK 操作,则该虚拟连接相对于该端点是等待关闭的。

如果端点从来没有打开过连接或接收到连接的 CLOSE 或 CLOSEACK 操作(且随后
没有打开),则该虚拟连接相对于该端点是关闭的。

流控制
多路复用协议使用简单的包流控制机制允许多个虚拟连接并存于同一具体连接上
。流控制机制的高级要求是所有虚拟连接的状态都是独立的;一个连接的状态不
会影响其他连接。例如,如果处理来自某个连接的数据的数据缓冲区已满,应不
会防碍其他连接的数据传输和处理。如果连接的继续依赖于另一个连接的结束(
例如递归 RMI 调用时),则这一点将至关重要。因而,它的实际意义是实现必须
总能消耗和处理在具体连接上(假定它遵循该规范)准备输入的所有多路复用协
议数据。

每个端点具有两个与各连接相关联的状态值:该端点已经请求但尚未接收到的数
据字节数(输入请求数)和另一端点请求但该端点尚未提供的数据字节数(输出
请求数)。

端点的输出请求数在从其他端点接收到 REQUEST 操作时将增大,而在它发送 TR
ANSMIT 操作时将减小。端点的输入请求数在它发送 REQUEST 操作时将增大,而
在它接收到 TRANSMIT 操作时将减小。这些值如果为负就将违反协议。

如果端点发送 REQUEST 操作而导致其输入请求数增大并超过其当前可以无阻塞处
理的字节数,则违反协议。但如果连接的用户在等待读取数据,则应确保其输入
请求数大于零。

如果端点发送的 TRANSMIT 操作包含有比其输出请求数更多的字节,则违反协议
。它可以缓冲外流的数据,直到连接用户请求显式刷新写入到连接中的数据。但
如果因为显式的刷新或实现的输入缓冲区满而必须在连接上发送数据,则连接用
户可能被阻塞,直到有足够 TRANSMIT 操作。

在满足上述规则的前提下,实现可以相对自由地发送 REQUEST 和 TRANSMIT 操作
。例如,如果其输入缓冲区不空,则端点可以请求连接的更多数据。

10.6.3 协议格式
多路复用协议的字节流格式由连续的可变长度记录序列组成。记录的第一个字节
是一个操作码,它可以识别记录的操作并可确定其内容其余部分的格式。我们定
义了下列合法的操作码:

值 名称

0xE1 OPEN

0xE2 CLOSE

0xE3 CLOSEACK

0xE4 REQUEST

0xE5 TRANSMIT

如果记录的第一个字节不是所定义的操作码,则违反协议。下面各节介绍了每种
操作码的记录格式。

OPEN 操作
下面是 OPEN 操作的记录格式:

大小(字节) 名字 描述

1 opcode 操作码 (OPEN)

2 ID 连接标识符

端点将发送 OPEN 操作以打开指定的连接。如果ID 指向对发送端点当前已打开或
即将关闭的连接,则违反协议。打开连接后,连接的输入和请求数状态在两个端
点上都为零。

接收到 OPEN 操作表示另一端点正在打开指定的连接。打开连接后,连接的输出
和请求数状态在两个端点处都为零。

为防止两端点间的标识符冲突,有效连接标识符空间将根据最高位的值分为两半
。每个端点仅允许打开高位为某一特定值的连接。启动具体连接的端点必须只打
开高位为标识符中的连接,另一端点必须只打开高位为零的连接。例如,如果不
能创建服务器套接字的 RMI applet 启动了与其 codebase 主机的多路复用连接
,则该 applet 可以打开标识符范围为 0x8000-7FFF 的虚拟连接,而服务器可以
打开标识符范围为 0-0x7FFF 的虚拟连接。

CLOSE 操作
以下是 CLOSE 操作的记录格式:

大小(字节) 名字 描述

1 opcode 操作代码 (OPEN)

2 ID 连接标识符

端点发送 CLOSE 操作以关闭指定的连接。如果 ID 指向对发送端点当前已关闭或
即将关闭的连接(如果它已发送过此连接的 CLOSE 操作,也可能是对接收端点即
将关闭的连接),则违反协议。 发送 CLOSE 后,连接就成为对发送端点即将关
闭的连接。因此,该端点将不能重新打开该连接,直到它从另一端点接收到 CLO
SE 或 CLOSEACK 为止。

接收到 CLOSE 操作表示另一端点已关闭指定的连接,因此该连接已在接收端点上
被关闭。虽然接收端点可能不再为此连接发送其它操作(直到被再次打开),但
它仍应为此连接的读者提供实现的输入缓冲区中的数据。如果连接已经被打开(
而不是即将关闭),则接收端点必须用 CLOSEACK 操作作为响应。

CLOSEACK 操作
以下是 CLOSEACK 操作的记录格式:

大小(字节) 名字 描述

1 opcode 操作代码 (OPEN)

2 ID 连接标识符

端点发送 CLOSEACK 操作以表明已收到来自接收端点的 CLOSE 操作。如果收到操
作时 ID 指向的连接不是对接收端点将要关闭的连接,则违反协议。

接收到 CLOSEACK 操作可将指定连接的状态从即将关闭改为已关闭,因此以后还
可重新打开连接。

REQUEST 操作
以下是 REQUEST 操作的记录格式:

大小(字节) 名字 描述

1 opcode 操作代码 (OPEN)

2 ID 连接标识符

4 count 请求的额外字节数

端点发送 REQUEST 操作以增大指定连接的输入请求数。如果 ID 未指向发送端点
打开的连接,则违反协议。端点的输入请求数按值 count 递增。count 的值是
32 位有符号整数。如果为负数或零,则违反协议。

接收到 REQUEST 操作将使指定连接的输出请求数按 count 增加。如果接收端点
即将关闭连接,则将忽略 REQUEST 操作。

TRANSMIT 操作
以下是 TRANSMIT 操作的记录格式。

大小(字节) 名字 描述

1 opcode 操作码 (OPEN)

2 ID 连接标识符

4 count 传输的字节数

count data 传输数据

端点发送 TRANSMIT 操作后,才真正在指定连接上传输数据。如果 ID 未指向对
发送端点打开的连接,则违反协议。端点的输出请求按值 count 递减。count 的
值是 32 位有符号整数。如果为负数或零,则违反协议。如果 TRANSMIT 操作导
致输出请求数成为负数,则也违反协议。

接收到 TRANSMIT 操作时从连接中可读到的字节队列将增加 count 字节的数据。
接收端点的输入请求数按值 count 递减。如果这会使输入请求数成为零,而该连
接的用户却试图读取更多数据,则该端点应用另一个 REQUEST 操作作为响应。如
果接收端点即将关闭连接,则将忽略 TRANSMIT 操作。

违反协议
如果出现上述违反协议的现象,或者在具体连接中检测到通讯错误,则多路复用
连接即被关闭。实际连接将被终止,而所有虚拟连接也被立即关闭。连接的用户
可以读取虚拟连接中已经可以读取的数据。

?

?


?

收藏助手
不良信息举报
您举报文章:RMI规范(6) --(10)
举报原因:
原因补充:

(最多只允许输入30个字)