1.1 RMI 的基本概念
1.1.1 什么是RMI
RMI(Remote Method Invocation) 远程方法调用是一种计算机之间对象互相调用对方函数,启动对方进程的一种机制,使用这种机制,某一台计算机上的对象在调用另外一台计算机上的方法时,使用的程序语法规则和在本地机上对象间的方法调用的语法规则一样。
1.1.2 RMI 的用途
1、 分布式体系结构
我们为什么要使用分布式计算呢?
Ø
Ø
可以用多种方法配置多个计算机系统以共享处理,包括共享内存、共享磁盘或只是共享一条公共通信通道。最新的技术允许物理上相隔很远的系统能够在处理计算问题时协同工作。
关于利用计算能力这一主题,因特网及伴随的通信协议 TCP/IP 的出现已使无数的计算机系统史无前例地连接起来。对一些应用程序来说,能够利用如此多的计算功能来解决问题是令人满意的。甚至更吸引人的是,大多数计算机系统都有充足的空闲时间,可以帮助解决其它问题。将来,网格计算会利用分布式计算能力进行出售,这与电力行业出售电能非常相似。
2、 Java 分布式对象编程技术
RMI 是 Enterprise JavaBeans 的支柱,是建立分布式 Java 应用程序的方便途径。只要按照 RMI 规则设计程序,可以不必再过问在 RMI 之下的网络细节了,如: TCP 和 Socket 等等。任意两台计算机之间的通讯完全由 RMI 负责。调用远程计算机上的对象就像本地对象一样方便。
1.1.3 RMI 应用程序分类
依据 RMI 应用程序各部分职责,可对应用程序进行如下分类:
Ø
Ø
Ø
1.2 创建 RMI 应用程序步骤
1、 定义远程接口
在 Java 中,远程对象是实现远程接口的类的实例 , 远程接口声明每个要远程调用的方法。在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节,客户通过接口句柄发送消息即可。远程接口具有如下特点:
Ø
Ø
Ø
代码范例 1
package com.itjob; import java.rmi.*; public interface RmiSample extends Remote{ } |
2、 实现远程接口
远程对象实现类必须扩展远程对象 java.rmi.UnicastRemoteObject 类,并实现所定义的远程接口。远程对象的实现类中包含实现每个远程接口所指定的远程方法的代码。这个类也可以含有附加的方法,但客户只能使用远程接口中的方法。因为客户是指向接口的一个句柄,而不是它的哪个类。必须为远程对象定义构造函数,即使只准备定义一个默认构造函数,用它调用基础类构造函数。因为基础类构造函数可能会抛出 java.rmi.RemoteException ,所以即使别无它用必须抛出 java.rmi.RemoteException 例外。
代码范例 2
package com.itjob.rmi; import java.rmi.*; import java.rmi.server.*; import com.itjob.RmiSample public class RmiSampleImpl extends UnicastRemoteObject implements RmiSample{ } |
3、 编写服务器类
包含 main 方法的类可以是实现类自身,也可以完全是另一个类。下面通过 RmiSampleServer 来创建一个远程对象的实例,并通过 java.rmi.registry.LocateRegistry 类的 createRegistry 方法从指定端口号启动注册服务程序,也可以通过执行 rmiregistry 命令启动注册服务程序,注册服务程序的缺省运行端口为 1099 。
代码范例 3
package com.itjob.rmi; import java.rmi.*; import java.rmi.registry.*; public class RmiSampleServer{ } } |
|
Ø
LocateRegistry.createRegistry(8808); 指定本 RMI 服务程序不使用默认端口 1099, 而是使用自己指定的端口 8808 。
Naming.rebind("//localhost:8808/SAMPLE-SERVER" , Server); 将远程对象在服务器上注册并指定了将查找远程对象引用的 URL , URL 格式为 //host:port/name 。其中 host 是注册表所在的主机(远程或本地), port 是注册表接受调用的端口号, name 是未经注册表解释的简单字符串。 host 和 port 两者都是可选项。如果省略了 host ,则主机默认为本地主机。如果省略了 port ,则端口默认为 1099 ,该端口是 RMI 的注册表 rmiregistry 使用的“著名”端口。
代码范例 3 的运行结果如图所示:
图 14.1 代码范例 3 运行结果
4、 编写使用远程服务的客户机类
客户机类的主要功能有两个,一是通过 Naming.lookup 方法来构造注册服务程序 stub 程序实例,二是调用服务器远程对象上的远程方法。
代码范例 4
package com.itjob.rmi; import java.rmi.*; |
5、 为远程对象实现创建根和干
客户端是通过 Naming.lookup 方法来构造注册服务程序 stub 程序实例,通过该实例的引用来发起对远程对象方法调用的,所以在运行运行客户端应用前必须为远程对象实现创建根 (stub) 和干 (Skeleton) 。要创建存根程序和骨架文件,应以包含远程对象实现的已编译类包全名运行 rmic 编译器。存根( Stub )是远程对象在客户端的代理,它将 RMI 调用传递给服务器端的骨架( Skeleton ),后者负责将该调用传递给实际的远程方法。在命令行模块下运行 RMIC 调用:
图 14. 2 RMIC 命令
调用 RMIC 命令运行结果如图所示:
图 14.3 RMIC 运行结果
Ø
采用 JDK1.4 版本运行 rmic com.itjob.rmi. RmiSampleImpl 命令系统将会生成存根 (stub)RmiSampleImpl_Stub.class 和骨架( Skeleton ) RmiSampleImpl_ Skeleton .class 两个类文件;
采用 JDK1.5 版本运行 rmic com.itjob.rmi. RmiSampleImpl 命令系统将只会生成存根 (stub)RmiSampleImpl_Stub.class ,骨架( Skeleton ) RmiSampleImpl_ Skeleton .class 的功能将通反射技术由系统在运行时自动实现;
6、 运行程序
依次做完上述步骤后,我们现在来运行一下我们的 RMI 应用。先运行服务端程序,运行结果如图 14.1 所示。
接下来我们运行客户端程序(代码范例 4 ),运行结果如图所示:
图 14.4 客户端程序运行结果
看到上面结果说明我们客户端程序进行 RMI 远程调用已经成功了。
1.3 RMI 接口和类简介
负责指定 rmi 系统远程对象行为的接口和类在 java.rmi 包中定义的,接下来我们了解一下几个核心接口和类:
1、 Java.rmi.Remote 接口
在 rmi 中,远程接口声明了可从远程 java 虚拟机中调用的方法集,远程接口必须满足下列条件:
Ø
Ø
2、 Java.rmi.RemoteException 类
RemoteException 类是在远程方法调用期间由 RMI 运行所抛出的异常,在使用了 rmi 系统的应用程序中,远程接口中申明的远程方法在其 throws 子句中必须指定 RemoteException 或者其超类。
Ø
Ø
3、 Java.rmi.server.RemoteObject 类
Ø
Ø
Ø
Ø
Ø
4、 Java.rmi.registry.LocateRegistry 类
LocateRegistry 类用于获得对特定主机的引导远程对象注册服务程序的引用 ( 创建 stub), 或者创建能在特定端口接受调用的远程对象注册服务程序,注册服务程序实现将远程对象名与远程对象引用关联的简单命名语法,服务器重新启动不会记住这些名字和远程对象之间的绑定。
LocateRegistry 类中的方法:
public static Registry getRegistry() throws RemoteException
Public static Registry getRegistry(int port ) throws RemoteException
Public static Registry getRegistry(String host ) throws RemoteException
Public static Registry getRegistry(String host , int port) throws RemoteException
Public static Registry getRegistry(String host, int port ,RMIClientSocketFactory csf ) throws RemoteException
Public static Registry createRegistry(int port )throws RemoteException
Public static Registry createRegistry(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException
5、 Java.rmi.Naming 类
Naming 类提供了存储和获得远程对象注册服务程序中的远程对象进行引用的方法
Ø
Ø
Public static Remote lookup(String name) throws NotBoundException, MalformedURException, RemoteException
Public static void bind(String name, Remote obj) throws AlreadyBoundException, MalforedURException, RemoteException
Public static void unbind (String name) throws RemoteException, NotBoundException,MalformedURLException
Public static void rebind(String name, Remote obj) throws RemoteException, MalformedURLException
6、 Java.rmi.server.UnicastRemoteObject 类
类 UnicastRemoteObject 创建并导出远程对象,该类实现的远程服务具有以下特点:
Ø
Ø
7、 Stub 和 skeleton
在远程对象的通信过程中, rmi 使用标准机制: stub 和 skeleton
图 14.5 Stub 和 Skeleton
Ø
初始化与包含远程对象的远程机器的连接。
对远程机器参数进行编组(写入并传输)。
等待方法调用结果。
读取返回值或返回的异常。
将值返回给调用程序。
Ø
在远程机器中,每个远程对象都可以有相应的 skeleton , skeleton 负责将调用分配给实际的远程对象实现,他的主要功能如下:
读取远程方法的参数。
调用实际远程对象实现上的方法。
将结果(返回值或异常)编组(写入并传输)给调用程序。
1.4 JNDI 基本概念
JNDI 诞生的理由似乎很简单。随着分布式应用的发展,远程访问对象访问成为常用的方法。虽然说通过 Socket 等编程手段仍然可实现远程通信,但按照模式的理论来说,仍是有其局限性的。 RMI 技术, RMI-IIOP 技术的产生,使远程对象的查找成为了技术焦点。 JNDI 技术就应运而生。 JNDI 技术产生后,就可方便的查找远程或是本地对象。
1、 JNDI 是什么 ?
JNDI(The Java Naming and Directory Interface , Java 命名和目录接口 ) 是一组在 Java 应用中访问命名和目录服务的 API 。为开发人员提供了查找和访问各种命名和目录服务的通用、统一的方式。借助于 JNDI 提供的接口,能够通过名字定位用户、机器、网络、对象服务等。
Ø
Ø
2、 JNDI 的好处 :
Ø
Ø
Ø
Ø
Ø
1.5 JNDI 应用程序结构
JNDI 的结构由一个 API 和一个 SPI 组成, Java 应用程序实用 JNDI API 访问各种各样的命名和目录服务。
图 14.6 JNDI 应用程序结构
1、 JNDI 上下文
前面提到命名服务是将名称与对象相关联。这种关联被称为绑定。一组这样的绑定被称为上下文, Jndi 上下文可以用来查找,捆绑 / 解除捆绑,创建或者破坏绑定名称操作在 JNDI 中,上下文是使用 javax.naming.Context 接口来表示的,而这个接口也正是与命名服务进行交互的主要接口。
Context 接口中的每个命名方法都有两种重载的形式:
lookup(String name): 接受一个字符串名称参数,查找绑定远程对象。
lookup(javax.naming.Name): 接受一个结构化的名称,查找绑定远程对象。
2、 初始化上下文
InitialContext 是一个实现了 Context 接口的类。使用这个类作为您到命名服务的入口点 。创建一个 InitialContext 对象构造器需要采用一组属性,形式为 java.util.Hashtable 或其子类之一,比如:
代码范例 5
Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); InitialContext |
要通过 JNDI 进行资源访问,我们必须设置初始化上下文的参数,主要是设置 JNDI 驱动的类名 (java.naming.factory.initial) 和提供命名服务的 URL(java.naming.provider.url) 。因为 Jndi 的实现产品有很多。所以 java.naming.factory.initial 的值因提供 JNDI 服务器的不同而不同, java.naming.provider.url 的值包括提供命名服务的主机地址和端口号。
下表列出了用于所支持的服务提供程序的工厂类。
表 : Context.INITIAL_CONTEXT_FACTORY 的值
名称 | 服务提供程序工厂 |
文件系统 | com.sun.jndi.fscontext.RefFSContextFactory |
LDAP | com.sun.jndi.ldap.LdapCtxFactory |
RMI | com.sun.jndi.rmi.registry. RegistryContextFactory |
CORBA | com.sun.jndi.cosnaming.CNCtxFactory |
DNS | com.sun.jndi.dns.DnsContextFactory |
1.6 RMI 与 JNDI 集成
通过上面对 JNDI 的了解我们可以利用 JNDI 来管理 RMI 远程对象的注册服务,我们将代码范例 3 进行如下改写:
代码范例 6
package com.itjob.rmi; import java.rmi.*; import java.rmi.registry.*; import javax.naming.*; public class RmiSampleServerJndi{ } |
启动服务端程序如图所示:
图 14.7 JNDI 服务程序启动
表示服务端程序已经将远程对象在 JNDI 是进行了注册,等待客户端进行调用。
接下来我们改写客户端程序采用 JNDI 方式来调用远程对象
代码范例 7
package com.itjob.rmi; import java.rmi.*; import java.rmi.server.*; import javax.naming.*; |
运行客户端程序如图所示:
图 14.8 客户端 JNDI 程序运行结果
表示客户端已经通过 JNDI 调用服务端远程对象成功。
1.7 学习总结
Ø
Ø
n
n
n
n
Ø