创建一个rmi应用主要包括以下步骤:
1 创建远程接口:继承java.rmi.Remote接口
2 创建远程类:实现远程接口
3 创建服务器程序:负责向rmiregistry注册表中注册远程对象
4 创建客户程序:负责定位远程对象,并且调用远程对象的方法
(一)创建远程接口
(1)直接或间接继承java.rmi.Remote接口
(2)接口中所有方法声明抛出java.rmi.RemoteExeption
远程方法调用依赖于网络通信,一旦服务器或客户端有一方突然断开连接,或者网络出现故障,通信就会失败。RMI框架会把遇到的网络故障通信失败转化为RemoteException,客户端可以捕获这种异常并进行处理
package hello;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Date;
public interface HelloService extends Remote{
String echo(String msg)throws RemoteException;
Date getTime()throws RemoteException;
}
创建远程接口时,还可以采用以下方式:
public interface A {
String echo(String msg)throws IOException;
Date getTime()throws Exception;
}
public interface B extends A,Remote{}
以上接口A不是远程接口,而子接口B是远程接口。由于A中声明的方法抛出的异常都是RemoteException的父类,因此这些方法在接口B中都可以作为远程方法。这种创建接口的方法有以下优点:
对本来不是为RMI而设计的接口A,无须对他作任何变动,只需创建一个继承Remote接口的子接口B,就能增加对RMI的支持
接口A不依赖于RMI,能隐藏系统中RMI的细节,使系统可以在保持接口A不变的情况下灵活的改变实现细节。
(二) 创建远程类
RMI规范要求远程类必须实现远程接口
package hello;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Date;
// 在此远程类继承了一个UnicastRemoteObject 类
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
private String name;
public HelloServiceImpl(String name) throws RemoteException {
this.name = name;
}
public String echo(String msg) throws RemoteException {
System.out.println(name + ":调用echo()方法");
return "echo:" + msg + " from " + name;
}
public Date getTime() throws RemoteException {
System.out.println(name + ":调用getTime()方法");
return new Date();
}
}
远程类就是实际要调用的那些业务类等,用来创建远程对象。
所有的远程对象,都必须通过调用类java.rmi.server.UnicastRemoteObject的方法exportObjext(Remote r, int port)
导出为远程对象(能够生成对应的stub和骨架对象)
在上面的例子中通过继承UnicastRemoteObject,在构造函数中会调用UnicastRemoteObject的构造子执行exportObjext(this,0),若要指定其他端口,调用super(int port)
public HelloServiceImpl(String name) throws RemoteException {
super(111);
this.name = name;
}
也可以
public HelloServiceImpl(String name) throws RemoteException {
this.name = name;
UnicastRemoteObject.exportObjext(this,0);
}
以上的构造函数要抛出RemoteException
(三 创建服务器程序)
RMI采用一种命名服务机制来使客户端程序可以找到服务器上的一个远程对象。在jdk安装目录的bin目录下有一个rmiregistry.exe程序,它是提供命名服务的注册表程序。
服务器的一大任务就是向注册表中注册远程对象。
从JDK1.3以上版本开始,RMI命名服务的API被整合到JNDI中,在JNDI中,javax.naming.Context接口声明了注册,查找以及注销对象的方法:
bind(String name , Object obj):注册对象,把一个对象与一个名字绑定。如果该名字已经和其他对象绑定,就会抛出NameAlreadyBoundException
rebind(String name, Object obj):注册对象,把对象与一个名字绑定。如果该名字已经和其他对象绑定,不会抛出NameAlreadyBoundException,而是覆盖
lookup(String name):查找对象,返回参数name指定的名字所绑定的对象
unbind(String name):注销对象,取消绑定。
package hello;
import java.rmi.Naming;
import javax.naming.Context;
import javax.naming.InitialContext;
public class SimpleServer {
public static void main(String[] args) {
try {
HelloService service1 = new HelloServiceImpl("service1");
HelloService service2 = new HelloServiceImpl("service2");
// 使用JNDI的API将远程对象注册
Context namingContext = new InitialContext();
namingContext.rebind("rmi:HelloService1", service1);
namingContext.rebind("rmi:HelloService2", service2);
// 也可以直接使用RMI的API进行绑定
// Naming.rebind("HelloService1", service1);
// Naming.rebind("HelloService2", service2);
System.out.println("服务器注册了两个HelloService对象");
String url = "rmi://localhost/";
HelloService service01 = (HelloService) Naming.lookup(url+"HelloService1");
Class stubClass = service1.getClass();
System.out.println("service1 是"+ stubClass+"的实例");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在将远程对象绑定时的名称并没有写全,都省略了一部分内容:
对JNDI API注册,名称必须以rmi:开头,默认情况下IntialContext的rebind方法会把HelloServieceImpl对象注册到本地主机上的监听1099端口的rmiregistry注册表进程中(RMI整合到JNDI中了)
对直接应用RMI API注册无此限制,不用一rmi:开头就可以实现以上默认
而该HelloServiceImpl对象的全名为:rmi://localhost:1099/HelloService1 因此给出全名称时:
namingContext.rebind("rmi://localhost:1099/HelloService1", service1); 或
Naming.rebind("rmi://localhost:1099/HelloService1", service1);
而客户端在通过名称访问远程对象时,必须向lookUp方法提供远程对象的完整名字:
rmi://服务器名字:端口号/对象注册的名字
服务器名字指的是rmiregistry注册表程序所在的主机名,端口号是指rmiregistry监听的端口号。默认情况下,rmiregistry监听1099端口,在这种情况下以上url可省略端口号。