EJB技术的基础是另外两种技术:RMI-IIOP和JNDI。要想了解EJB,一定要先了解RMI-IIOP和JNDI。因此,我们在介绍EJB细节之前,先了解这两项技术。我们的介绍比较基本,因此大多数组织只要了解这些就已经够了。
Java RMI-IIOP
Java RMI-IIOP(Java Remote Method Invocation over the Internet Inter-ORB Protocol)是J2EE的网络机制。Java RMI-IIOP允许你编写分布式对象,使得对象的通信范围能够在内存中,跨Java虚拟机,跨物理设备。
Remote Method Invocation
RPC(remote procedure call)是一台机器的进程调用另一台机器的进程的过程。而remote method invocation则比RPC的概念更进一步,允许分布式对象间的通信。RMI-IIOP允许调用远程对象的方法,而不仅仅是过程。这有利于面向对象编程。Java RMI (Remote Method Invocation 远程方法调用)是用Java在JDK1.1中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的威力就体现在它强大的开发分布式网络应用的能力上,而RMI就是开发百分之百纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。
remote method invocation决不简单,需要考虑几个问题:
marshalling和unmarshalling.在不同机器间通过网络传递变量(包括Java基本类型和对象),如果目标机器表示数据的方式和原机器不同该怎么办?例如二进制库不同。因此marshalling和unmarshalling就是传递变量的过程。
变量传递方法.变量有两种传递方法:pass-by-value和pass-by-reference。对于前者,你的目标方法只需使用一份copy,但对于后者,远程方法对变量的任何修改都会影响到源数据。
网络和机器的不稳定.需要有一种机制保证一个JVM崩溃之后,不会影响系统的正常运作。
在 Java 分布式对象模型中,remote object 是这样一种对象:它的方法可以从其它 Java 虚拟机(可能在不同的主机上)中调用。该类型的对象由一种或多种 remote interfaces(它是声明远程对象方法的 Java 接口)描述。远程方法调用 (RMI) 就是调用远程对象上远程接口的方法的动作。更为重要的是,远程对象的方法调用与本地对象的方法调用语法相同。
Remote Interface
RMI-IIOP遵循了接口和实现的原则。你写的所有网络代码都是应用于接口,而不是实现。实际上,你必须使用RMI-IIOP中的范例,没有其它的选择。直接在你的对象实现上执行远程调用是不可能的,你只能在对象类的接口上单独进行这一操作。
所以我们在使用RMI-IIOP时,你必须建立一个客户接口,叫做remote interface。这个远程接口应该扩展java.rmi.Remote接口。
Remote Object Implementation
远程对象和客户机的物理位置并不是很重要。可以运行在同一地址空间或是跨Internet运行。
为了使对象成为一个远程对象,你需要执行一下步骤:
继承javax.rmi.PortableRemoteObject。PortableRemoteObject是进行远程调用的基类,当你的远程对象调用构造器时,PortableRemoteObject对象的构造器也会自动被调用。
不继承javax.rmi.PortableRemoteObject。如果你的远程对象需要继承其它的类,而Java不允许多重继承,因此你不能继承PortableRemoteObject。这时,你需要手动调用javax.rmi.PortableRemoteObject.eXPortObject()。
Stub和Skeletons
我们来看看在RMI-IIOP背后隐藏的网络架构。RMI-IIOP的一个好处就是你可以不用管你要调用的对象是本地的还是远程的。这就叫做local/remote transparency。
RMI应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机调用这些远程对象的方法。而典型的客户机程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI为服务器和客户机进行通信和信息传递提供了一种机制。
在与远程对象的通信过程中,RMI使用标准机制:stub和skeleton。远程对象的stub担当远程对象的客户本地代表或代理人角色。调用程序将调用本地stub的方法,而本地stub将负责执行对远程对象的方法调用。在RMI中,远程对象的stub与该远程对象所实现的远程接口集相同。调用stub的方法时将执行下列操作:(1) 初始化与包含远程对象的远程虚拟机的连接;(2) 对远程虚拟机的参数进行编组(写入并传输);(3) 等待方法调用结果;(4) 解编(读取)返回值或返回的异常;(5) 将值返回给调用程序。为了向调用程序展示比较简单的调用机制,stub将参数的序列化和网络级通信等细节隐藏了起来。在远程虚拟机中,每个远程对象都可以有相应的skeleton(在JDK1.2环境中无需使用skeleton)。Skeleton负责将调用分配给实际的远程对象实现。它在接收方法调用时执行下列操作:(1) 解编(读取)远程方法的参数;(2) 调用实际远程对象实现上的方法;(3) 将结果(返回值或异常)编组(写入并传输)给调用程序。stub和skeleton由rmic编译器生成。
要实现local/remote transparency可没有那么简单。为了屏蔽你调用的是远端主机上的对象,RMI-IIOP需要模拟一个本地对象供你调用。这个本地对象叫做stub。它负责接受本地的方法调用请求,把这些请求委托给真正实现它们的对象(可以通过网络定位)。这样就使得远程调用看起来就和本地调用一样。
利用RMI编写分布式对象应用程序需要完成以下工作:(1) 定位远程对象。应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用RMI的简单命名工具rmiregistry来注册它的远程对象,也可以将远程对象引用作为常规操作的一部分来进行传递和返回。(2)与远程对象通信。远程对象间通信的细节由RMI处理,对于程序员来说,远程通信看起来就像标准的Java方法调用。(3)给作为参数或返回值传递的对象加载类字节码。因为RMI允许调用程序将纯Java对象传给远程对象,所以,RMI将提供必要的机制,既可以加载对象的代码又可以传输对象的数据。在RMI分布式应用程序运行时,服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器上的注册服务程序中用远程对象的名字查找该远程对象,然后调用它的方法。
定位远程对象。应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用 RMI 的简单命名工具 rmiregistry 来注册它的远程对象;也可将远程对象引用作为常规操作的一部分来进行传递和返回。
与远程对象通讯。远程对象间通讯的细节由 RMI 处理;对于程序员来说,远程通讯看起来就象标准的 Java 方法调用。给作为参数或返回值传递的对象加载类字节码因为 RMI允许调用程序将纯 Java 对象传给远程对象,所以 RMI 将提供必要的机制,既可以加载对象的代码又可以传输对象的数据。服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器注册服务程序中用远程对象的名字查找该远程对象,然后调用它的方法。RMI 能用 Java系统支持的任何 URL 协议(例如 HTTP、FTP、file 等)加载类字节码。
stub只是解决了一半的问题。我们还希望远程对象也不用考虑网络问题。因此远程对象也需要一个本地的skeleton来接受调用。skeleton接受网络调用并把调用委托给远程对象实现。
你的J2EE服务器应当提供一种方法来产生必须的stub和skeleton,以减轻你的对网络问题考虑的负担。典型的是通过命令行工具来完成,例如sun的J2EE参考实现包就使用了一个名为rmic(RMI compiler)的工具来产生stub和skeleton类。你应当把stub部署在客户机上,并把skeleton部署在服务器上。
对象序列化和变量传递
在RMI分布式应用系统中,服务器与客户机之间传递的Java对象必须是可序列化的对象。不可序列化的对象不能在对象流中进行传递。对象序列化扩展了核心Java输入/输出类,同时也支持对象。对象序列化支持把对象编码以及将通过它们可访问到的对象编码变成字节流;同时,它也支持流中对象图形的互补重构造。序列化用于轻型持久性和借助于套接字或远程方法调用(RMI)进行的通信。序列化中现在包括一个 API(Application Programming Interface,应用程序接口),允许独立于类的域指定对象的序列化数据,并允许使用现有协议将序列化数据域写入流中或从流中读取,以确保与缺省读写机制的兼容性。
为编写应用程序,除多数瞬态应用程序外,都必须具备存储和检索 Java对象的能力。以序列化方式存储和检索对象的关键在于提供重新构造该对象所需的足够对象状态。存储到流的对象可能会支持 Serializable(可序列化)或 Externalizable(可外部化)接口。对于Java对象,序列化形式必须能标识和校验存储其内容的对象所属的 Java类,并且将该内容还原为新的实例。对于可序列化对象,流将提供足够的信息将流的域还原为类的兼容版本。对于可外部化对象,类将全权负责其内容的外部格式。序列化 Java 对象的目的是:提供一种简单但可扩充的机制,以序列化方式维护 Java对象的类型及安全属性;具有支持编组和解编的扩展能力以满足远程对象的需要;具有可扩展性以支持 Java 对象的简单持久性;只有在自定义时,才需对每个类提供序列化自实现;允许对象定义其外部格式。
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;
}
RemoteException 类
java.rmi.RemoteException 类是在远程方法调用期间由 RMI 运行时所抛出的异常的超类。为确保使用 RMI 系统的应用程序的健壮性,远程接口中声明的远程方法在其 throws 子句中必须指定 java.rmi.RemoteException(或它的超类,例如 java.io.IOException 或 java.lang.Exception)。
当远程方法调用由于某种原因失败时,将抛出 java.rmi.RemoteException 异常。远程方法调用失败的原因包括:
通讯失败(远程服务器不可达或拒绝连接;连接被服务器关闭等。)
参数或返回值传输或读取时失败
协议错误
RemoteException 类是一个已检验的异常(必须由远程方法的调用程序处理并经编译器检验的异常),而不是 RuntimeException。
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远程对象在其远程方法被调用时开始执行并在必要时自己关闭。
实现远程接口
实现远程接口的类的一般规则如下:
该类通常扩展 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 方法的正确远程语义(如果需要)。
远程方法调用中的参数传递
传给远程对象的参数或源于它的返回值可以是任意可序列化的 Java 对象。这包括 Java 基本类型, 远程?Java 对象和实现 java.io.Serializable 接口的非远程 Java 对象。有关如何使类序列化的详细信息,参见 Java“对象序列化规范”。本地得不到的作为参数或返回值的类,可通过 RMI 系统进行动态下载。
传递非远程对象
非远程对象将作为远程方法调用的参数传递或作为远程方法调用的结果返回时,是通过复制传递的;也就是使用 Java 对象序列化机制将该对象序列化。因此,在远程对象调用过程中,当非远程对象作为参数或返回值传递时,非远程对象的内容在调用远程对象之前将被复制。从远程方法调用返回非远程对象时,将在调用的虚拟机中创建新对象。
传递远程对象
当将远程对象作为远程方法调用的参数或返回值传递时,远程对象的 stub 程序即被传递出去。作为参数传递的远程对象仅能实现远程接口。
引用的完整性
如果一个对象的两个引用在单个远程方法调用中以参数形式(或返回值形式)从一个虚拟机传到另一个虚拟机中,并且它们在发送虚拟机中指向同一对象,则两个引用在接收虚拟机中将指向该对象的同一副本。进一步说就是:在单个远程方法调用中,RMI 系统将在作为调用参数或返回值传递的对象中保持引用的完整性。
类注解
当对象在远程调用中被从一个虚拟机发送到另一个虚拟机中时,RMI 系统在调用流中用类的信息 (URL) 给类描述符加注解,以便该类能在接收器上加载。在远程方法调用期间,调用可随时下载类。
参数传输
为将 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的子类并和参数传输的替代行为相同。
定位远程对象
我们专门提供了一种简单的引导名字服务器,用于存储对远程对象的已命名引用。使用类 java.rmi.Naming 的基于 URL 的方法可以存储远程对象引用。客户机要调用远程对象的方法,则必须首先得到该对象的引用。对远程对象的引用通常是在方法调用中以返回值的形式取得。RMI 系统提供一种简单的引导名字服务器,通过它得到给定主机上的远程对象。java.rmi.Naming 类提供基于统一资源定位符 (URL) 的方法,用来绑定、再绑定、解开和列出位于某一主机及端口上的名字-对象对。
(出处:http://www.knowsky.com)