第 4 章 JSP 与 J2EE 分布式处理技术
4.2 远程方法调用 RMI 技术
第一部分 JSP 技术与 J2EE 技术
还是不基于Web 在做出这个决定时 我们可能希望考虑平台配置 下载速度 安全 网 络流量和网络服务 例如 包含有用户界面并且经常被大量用户访问的一个Java Applet 可 能需要花很长的时间才能被下载下来 这让用户沮丧 然而 如果知道该Java Applet 要运 行在公司的内部网内的受控环境中 那么 在这种情况下 该Applet 将拥有一个完全可接 受的下载速度
注意 所谓的三层模型有多种看法 上面的叙述只是代表我们的看法 有人认为 JSP/Servlet 程序应该算是业务层 也就是第二层 我们认为 应该把商业逻辑 从JSP/Servlet 程序中最大限度的剥离 让 JSP/Servlet 程序只负责数据的显示与 接受用户请求 这样它们就是属于客户端表示层的了
4.2 远程方法调用 RMI 技术
在这一节中我们将介绍RMI 技术的基础知识 结构 开发和应用 4.2.1 RMI 概述
在大型的企业计算中 需要将整个应用系统分解成若干子系统并运行于相应的部门当 中 以提高整个系统的应用效率和可维护性及安全性 由于不同的工作由不同的系统来完 成 在它们之间需要一种安全便利的通讯机制 从面向对象的角度来说 企业计算需要一 种分布式的对象模型 使得运行于不同主机之间的对象能够互相进行方法调用 RMI 正是 基于Java 的能够满足上述计算的分布式计算模式 实现了在运行于不同虚拟机的对象之间 的方法调用
RMI 的研究工作其实在 1994 年 Java 诞生之前 就已在 Sun 公司的实验室里开始了 研究人员在充分评估了分布计算的趋势并比较了多种分布式系统以后开发了 RMI 对象模 式 在1997 年 JDK1.1 发布时正式推出 成为 Java 分布式计算的重要技术之一 JDK1.1 为RMI 的开发提供了一系列的 API 简化了 RMI 的开发工作
从调用方式来看 RMI 和 RPC 远过程调用 有很大的相似之处 但二者之间最大的 不同在于 RMI 是面向对象 而 RPC 是基于过程调用的 由于 RMI 面向对象的特性 RMI 调用可以直接将对象在调用的两端之间进行传递 不但可以传送数据 而且还可以传递方 法(mobile behavior) 扩展了 RMI 的使用 另外 RMI 还支持两个 RMI 对象之间的方法回调 (callback)
RMI 赋予每个 RMI 对象一个唯一的名字 并将之与实际的对象捆绑在一起 binding 这种对象关系登记在RMI 的注册登记表中 调用者通过对象的名字找到相应的对象后调用 它的方法 而不需要考虑该对象的实际物理存储位置 这不但更符合人们的使用习惯 而 且提高了系统的可扩充性和鲁棒性 RMI 将多个 RMI 对象的名字登记在同一张登记表中 监听于某端口 一个对象有一或多个方法以供远程调用 从而使一个端口可以提供多种 服务 节约了系统的端口资源
目前 RMI 不限于用 Java 语言编写的 RMI 对象之间的调用 它们之间通过 JRMP Java Remote Method Protocol 译为 Java 远程方法协议 进行通信 对于另一种对象模式 CORBA RMI 已经实现同 CORBA 对象之间的互相调用 由 Sun 公司开发的进行二者之间
第 4 章 JSP 与 J2EE 分布式处理技术
相互通讯的产品 RMI over IIOP 已经正式发布 4.2.2 开发 RMI 应用
在这一小节中 我们将介绍如何开发 RMI 应用 要开发 RMI 必须构造 4 个主要的 类 它们分别是 远程对象的本地接口 RMI 客户 远程对象实现和 RMI 服务程序 RMI 服务程序生成远程对象实现的一个实例 并用一个特殊的URL 注册它 RMI 客户在远程服 务器上查找对象 若找到就把它转换成本地接口类型 然后像一个本地对象一样使用它 下面是一个简单的RMI 例子 远程对象只返回一个消息字符串 要使这个例子更有价值 我们需要做的就是完善远程对象实现类
远程对象的本地接口
该类仅仅是一个接口 而不是实现 它声明了RMI 客户端程序可以调用的方法 RMI 客户机可以直接使用它 RMI 服务程序必须产生一个远程对象来实现它 并用某个 URL 注册它的一个实例 请看程序清单4.1 这是一个简单的远程对象的本地接口
程序清单 4.1 //File Name:Rem.java //Author:fancy //Date:2001.5
//Note:Remote interface import java.rmi.*;
public interface Rem extends Remote {
public String getMessage() throws RemoteException;
public String getAuthor() throws RemoteException;
}
在编写远程对象的本地接口的时候 读者应该注意本地接口 Rem 必须是公共的 否 则 客 户 机 在 加 载 一 个 实 现 该 接 口 的 远 程 对 象 时 就 会 出 错 此 外 它 还 必 须 从 java.rmi.Remote 接口继承而来 远程对象的本地接口中的每一个方法都必须抛出远程异常 java.rmi.RemoteException
远程对象实现类
这个类真正实现RMI 客户所调用的远程对象本地接口 它必须从 UnicastRemoteObject 继 承 其 构 造 函 数应 抛出 RemoteException 异常 而 且 每 一 个 实 现方 法都 必 须 抛 出 RemoteException 异常 请看程序清单 4.2
程序清单 4.2 //File Name:RemImpl.java //Author:fancy
//Date:2001.5
//Note:implemention remote interface
import java.rmi.*;
第一部分 JSP 技术与 J2EE 技术
import java.rmi.server.UnicastRemoteObject;
public class RemImpl extends UnicastRemoteObject implements Rem {
public RemImpl() throws RemoteException {
}
public String getMessage() throws RemoteException {
return("Here is a remote message.");
}
public String getAuthor() throws RemoteException {
return("fancy.");
} }
RMI 服务器类
该类将创建远程对象实现RemImpl 的一个实例 然后用一个特定的 URL 来注册它 所谓注册就是通过调用Naming.bind()方法或 Naming.rebind()方法来将 RemImpl 的实例对象 绑定到特定的URL 上 请看程序清单 4.3
程序清单 4.3
//File Name:RemServer.java //Author:fancy
//Date:2001.5 //Note: remote server
import java.rmi.*;
import java.net.*;
public class RemServer {
public static void main(String[] args) {
try {
RemImpl localObject = new RemImpl();
Naming.rebind("rmi://localhost/Rem" localObject);
}
catch(RemoteException re) {
System.out.println("RemoteException:"+re);
}
catch(MalformedURLException mfe)
第 4 章 JSP 与 J2EE 分布式处理技术
{
System.out.println("MalformedURLException: "+mfe);
} } }
RMI 客户类
RMI 客户使用 Naming.lookup()方法在指定的远程主机上查找对象 若找到就把它转换 成本地接口类型(在本例中是 Rem 类型) 然后像一个本地对象一样使用它 与 CORBA 不 同之处在于RMI 客户必须知道提供远程服务主机的 URL 这个 URL 可以通过 rmi://host/path 或rmi://host:port/path 来指定 如果省略端口号 就使用 1099 Naming.lookup()方法可能产 生3 个异常 RemoteException NotBoundException MalformedURLException 这 3 个异 常都需要捕获 RemoteException Naming 和 NotBoundException 在 java.rmi 包中定义 MalformedURLException 在 java.net 包中定义 所以我们要在应用程序中导入这两个包 另 外 客户机将向远程对象传递串行化对象Serializable 所以还应在程序中导入 java.io 包 请看程序清单4.4
程序清单 4.4
//File Name: RemClient.java //Author:fancy
//Note:rmi client //Date:2001.5
import java.rmi.*;
import java.net.*;
import java.io.*;
public class RemClient {
public static void main(String[] args) {
try {
String host = (args.length > 0) ? args[0] : "localhost";
//通过URL在远程主机上查找对象 并把它转化为本地接口Rem类型
Rem remObject=(Rem)Naming.lookup("rmi://" + host + "/Rem");
System.out.println(remObject.getMessage()); //调用远程对象的方法 System.out.println(remObject.getMessage()); //调用远程对象的方法 }
catch(RemoteException re) {
System.out.println("RemoteException: " + re);
}
第一部分 JSP 技术与 J2EE 技术
catch(NotBoundException nbe) {
System.out.println("NotBoundException: " + nbe);
}
catch(MalformedURLException mfe) {
System.out.println("MalformedURLException:"+ mfe);
} } }
如上所述 RMI 应用已经编写好了 下面我们应该编译运行这个 RMI 应用 步骤如下 1 编译 RMI 客户和服务器 这将自动编译远程对象的本地接口和远程对象实现 命令行代码如下
javac RemClient.java javac RemServer.java
2 生成客户存根模块(stub)和服务器框架(skeleton) rmic RemImpl
这将构造RemImpl_Stub.class 和 RemImpl_Skeleton.class
3 请将 Rem.class RemClient.class 和 RemImpl_Stub.class 拷贝到 RMI 客户机 将 Rem.class RemImpl.class RemServer.class 和 RemImpl_Skeleton.class 拷贝到 RMI 服务器
4 启动 RMI 注册程序 在命令行状态下 运行 rmiregistry 程序 rmiregistry 程序 在JDK 的 bin 文件夹内 这个程序在服务器上执行 不论有多少个 RMI 远程对象需要运行 本操作只需做一次
5 运行 RMI 服务程序 在命令行下执行下面的命令 java RemServer.class
这一步的作用是启动RMI 服务器(在服务器上执行) 在命令行下执行下面的命令
java RemClient.class
这一步的作用是启动RMI 客户程序(在客户端计算机上执行) 输出结果为 Here is a remote message.
fancy
4.2.3 使用 JSP 编写 RMI 应用客户端
我们可以编写基于JSP 的 RMI 应用客户端 使得在 JSP 程序中也能够调用远程 RMI 对象的商业方法 这种技术无疑大大地扩展了 JSP 程序的能力 也是利用 JSP+J2EE 技术 构建企业应用系统中关键的一环
实际上 基于JSP 的 RMI 应用客户端可以从程序清单 4.4 改写而来 请看程序清单 4.5 程序清单 4.5
<%--
File Name:rem.jsp Author:fancy
第 4 章 JSP 与 J2EE 分布式处理技术
Date:2001.5
Note:the client for rmi object --%>
<%@ page import=” java.rmi.*” %>
<%@ page import=”java.net.*” %>
<%@ page import=”java.io.*” %>
<%@ page import=”Rem.class” %>
<%
try {
String host = "localhost";
//通过URL在远程主机上查找对象 并把它转化为本地接口Rem类型
Rem remObject=(Rem)Naming.lookup("rmi://" + host + "/Rem");
out.println(remObject.getMessage()+”<br>”); //调用远程对象的方法 out.println(remObject.getMessage()); //调用远程对象的方法
}
catch(RemoteException re) {
out.println("RemoteException: " + re);
}
catch(NotBoundException nbe) {
out.println("NotBoundException: " + nbe);
}
catch(MalformedURLException mfe) {
out.println("MalformedURLException:"+ mfe);
}
%>
程序清单4.5 和程序清单 4.4 的输出结果一致