RMI是J2EE技术规范之一,英文全称Remote Method Invocation(即远程方法调用)。远程方法调用是一种同一计算机不同Java研发软件系统之间或不同计算机不同Java研发软件系统之间通过调用对方远程方法启动对方进程进而实现交互的一种机制,这种机制为开发Java分布式应用程序带来了极大的方便。
RMI技术的应用通常包括在两个独立的应用程序中:RMI服务端应用程序和RMI客户端应用程序。
RMI服务端应用程序:
RMI典型的服务端代码将创建多个由远程接口实现类创建的远程对象,使这些远程对象能够被引用,然后等待客户端调用这些远程对象内已实现的远程接口中的方法,其实现步骤如下:
1、自定义远程接口
示例代码如下:
package com.ghj.packageofrmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 定义一个继承Remote接口的远程接口。
*
* @author 高焕杰
*/
public interface IHelloWord extends Remote {
/**
* 获取信息
*
* @author 高焕杰
*/
String getMsg() throws RemoteException;
}
在 Java RMI服务端,远程对象是自定义远程接口实现类的实例, 该自定义远程接口声明每个要远程调用的抽象方法。
该接口特点:
a、该接口必须继承java.rmi.Remote接口;
b、该接口中的每个抽象方法必须抛出RemoteException异常或RemoteException 的父类异常;
2、自定义远程接口实现类
示例代码如下:
package com.ghj.packageofrmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 远程接口实现类。
*
* @author 高焕杰
*/
public class HelloWordImp extends UnicastRemoteObject implements IHelloWord{
private static final long serialVersionUID = -918923487973606075L;
/**
* 远程接口实现类构造方法
*
* @author 高焕杰
*/
public HelloWordImp() throws RemoteException {
}
/**
* 获取信息
*
* @author 高焕杰
*/
@Override
public String getMsg() throws RemoteException {
return "Hello World!";
}
}
该实现类特点:
a、该实现类必须实现自定义远程接口内的每个远程抽象方法;
b、该实现类必须继承java.rmi.UnicastRemoteObject类。UnicastRemoteObject类可以让服务端远程对象与客户端建立一对一的连接;
c、由于UnicastRemoteObject类中默认构造方法抛出RemoteException异常,因此该实现类中默认的构造方法必须显示地写出来并且该构造方法必须声明抛出RemoteException异常,别忘了子类构造方法要与参数列表相同的父类构造方法一致,父类无参构造方法抛出了RemoteException异常,那么子类无参构造方法也需要抛出RemoteException异常;
d、该实现类也可以含有其它非远程接口定义的抽象方法或非接口方法(即实现类内部自定义的方法,这些方法不能使用@Override进行注释),但客户端不能调用这些新增的方法(别忘了,这些方法并不是自定义远程接口内的抽象方法)。
3、服务端RMI启动类
示例代码如下:
package com.ghj.packageoftest;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import com.ghj.packageofrmi.HelloWordImp;
import com.ghj.packageofrmi.IHelloWord;
/**
* 启动RMI服务
*
* @author 高焕杰
*/
public class StartRMIServer {
public static void main(String args[]) {
try {
int registryPort = 8888;
IHelloWord helloWord = new HelloWordImp();
LocateRegistry.createRegistry(registryPort);
Naming.bind("rmi://localhost:" + registryPort + "/rmiDemo",helloWord);
// Naming.bind("//localhost:" + registryPort + "/rmiDemo",helloWord);
System.out.println("RMI服务成功启动!");
} catch (RemoteException e) {
System.err.println("远程对象创建失败!");
e.printStackTrace();
} catch (AlreadyBoundException e) {
System.err.println("发生重复绑定远程对象异常!");
e.printStackTrace();
} catch (MalformedURLException e) {
System.err.println("绑定的URL不正确!");
e.printStackTrace();
}
}
}
该类特点:
a、通过java.rmi.registry.LocateRegistry类的createRegistry 方法为RMI服务端远程对象注册表设置端口号;
b、通过Naming类的bind方法将serviceName(即服务名称)和远程接口类型的变量或远程接口实现类类型的变量绑定,为RMI客户端获取该服务名称对应的远程对象引用奠定基础,其中第一个参数的标准格式为“rmi://host:registryPort/serviceName”也可以“//host:registryPort/serviceName”。注意:这里是远程接口类型的变量与服务名绑定,呵呵呵,我要表达的是上述代码第23行也可以改为“HelloWordImp helloWord = new HelloWordImp();”;
如果细心的查看过JDK API文档,那么就会想到服务端RMI类中的代码可以这样写:
package com.ghj.packageoftest;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import com.ghj.packageofrmi.HelloWordImp;
import com.ghj.packageofrmi.IHelloWord;
/**
* 启动RMI服务
*
* @author 高焕杰
*/
public class StartRMIServer {
public static void main(String args[]) {
try {
int registryPort = 8888;//注册表上接受请求的端口号
//声明IHelloWord接口类型的变量并将其地址指向由该接口实现类创建的远程对象 。
IHelloWord helloWord = new HelloWordImp();
//为RMI服务端远程对象注册表设置端口号,这一步不可缺,否则无法将远程对象绑定远程注册表上 。
Registry registry = LocateRegistry.createRegistry(registryPort);
//将serviceName(即服务名称)和远程接口类型的变量或远程接口实现类类型的变量绑定。注意:这里是远程接口类型的变量与服务名绑定;
registry.bind("rmiDemo",helloWord);
System.out.println("RMI服务成功启动!");
} catch (RemoteException e) {
System.err.println("远程对象创建失败!");
e.printStackTrace();
} catch (AlreadyBoundException e) {
System.err.println("发生重复绑定远程对象异常!");
e.printStackTrace();
}
}
}
RMI客户端应用程序:
RMI典型的客户端程序从RMI服务端中得到一个或多个远程对象引用,然后调用远程对象内具体实现的自定义远程接口中的抽象方法。
1、自定义与RMI服务端自定义远程接口名称相同的接口
示例代码如下:
package com.ghj.packageofrmi;
/**
* 自定义与RMI服务端自定义远程接口名称相同的接口
*
* @author 高焕杰
*/
public interface IHelloWord{
/**
* 获取信息
*
* @author 高焕杰
*/
String getMsg();
}
该接口特点:
a、该接口的接口名必须和RMI服务端自定义远程接口名称相同;
b、该接口无须继承java.rmi.Remote接口;
c、该接口中需要通过RMI服务端远程对象调用的接口抽象方法需要和RMI服务端一样(这里的“一样”是指接口名、方法名、参数列表和方法返回值类型一样);
d、该接口中的抽象方法无须抛出RemoteException异常;
e、该接口可以包含RMI服务端自定义远程接口内不存在的抽象方法;
f、该接口无须包含RMI服务端自定义远程接口的所有抽象方法;
g、该接口完全可以直接复制RMI服务端自定义远程接口,但从代码精简的角度讲还是能删则删比较好。
2、客户端RMI调用远程方法测试类
示例代码如下:
package com.ghj.packageoftest;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import com.ghj.packageofrmi.IHelloWord;
/**
* 客户端测试
*
* @author 高焕杰
*/
public class TestClient {
public static void main(String args[]) {
try {
//依据服务名称通过Naming类的lookup方法获取RMI服务端特定的远程对象引用 ,为调用该对象中实现的接口中的方法奠定基础,其中lookup方法的参数标准格式为“rmi://host:registryPort/serviceName”,也可以“//host:registryPort/serviceName”。
IHelloWord helloWord = (IHelloWord)Naming.lookup("rmi://localhost:8888/rmiDemo");
System.out.println(helloWord.getMsg());//调用自定义远程接口实现类中实现的getMsg方法
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
如何运行RMI?
1、先运行RMI服务器端,比如上面RMI服务端中的StartRMIServer类;
2、运行RMI客户端,比如上面RMI客户端中的TestClient类;
个人的一点体悟:
RMI使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。这种协议是专为Java对象制定的,因此RMI技术适用于多个Java语言研发的独立项目之间的交互(即RMI服务端和RMI客户端的应用程序必须使用Java语言实现),该技术对于用非Java语言开发的应用程序之间的交互支持不足(即不能与用非Java语言书写的对象进行通信,对于不同语言开发的独立项目间的通讯交互可以Web Service或者公用对象请求代理体系(CORBA)来实现)。