RMI(即Remote Method Invoke 远程方法调用)。在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象,供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用。
注意:extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常,则表明该方法可被客户端远程访问调用。
同时,远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为“存根”,而服务器端本身已存在的远程对象则称之为“骨架”。其实此时的存根是客户端的一个代理,用于与服务器端的通信,而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
RMI 框架的基本原理大概如下图,应用了代理模式来封装了本地存根与真实的远程对象进行通信的细节。
下面给出一个简单的RMI 应用,其中类图如下:其中IService接口用于声明服务器端必须提供的服务(即service()方法),ServiceImpl类是具体的服务实现类,而Server类是最终负责注册服务器远程对象,以便在服务器端存在骨架代理对象来对客户端的请求提供处理和响应。
各个类的源代码如下:
IService接口:
package limeRMI.service; import java.rmi.Remote; import java.rmi.RemoteException; public interface IService extends Remote{ // 声明服务器端必须提供的服务 String service(String context) throws RemoteException; }
ServiceImpl实现类:
package limeRMI.service; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; //UnicastRemoteObject用于导出的远程对象和获得与该远程对象通信的存根。 public class ServiceImpl extends UnicastRemoteObject implements IService{ /** * */ private static final long serialVersionUID = 1L; private String name; protected ServiceImpl(String name) throws RemoteException { this.name = name; } public String service(String context) throws RemoteException { return "server >> " + context; } }
Server类:
package limeRMI.service; import java.rmi.RemoteException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class Server { public static void main(String[] args) { try { // 实例化实现了IService接口的远程服务ServiceImpl对象 IService serviceLime = new ServiceImpl("serviceLime"); // Context接口表示一个命名上下文,它由一组名称到对象的绑定组成。 它包含检查和更新这些绑定的一些方法。 // InitialContext类是执行命名操作的初始上下文。 该初始上下文实现 Context 接口并提供解析名称的起始点 // 初始化命名空间 Context namingContext = new InitialContext(); // 将名称绑定到对象,即向命名空间注册已经实例化的远程服务对象 namingContext.rebind("rmi://192.168.1.5:6666/serviceLime", serviceLime); System.out.println("服务器向命名表注册了1个远程服务对象! -- " + serviceLime.getClass().getSimpleName()); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NamingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Client类:
package limeRMI.client; import java.rmi.RemoteException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import limeRMI.service.IService; public class Client { public static void main(String[] args) { String url = "rmi://192.168.1.5:6666/"; try { Context namingContext = new InitialContext(); // 检索指定的对象。 即找到服务器端相对应的服务对象存根 IService serviceLime = (IService) namingContext.lookup(url + "serviceLime"); Class<? extends IService> serviceLimeClazz = serviceLime.getClass(); System.out.println(serviceLime + " 是 " + serviceLimeClazz.getName() + " 的实例。 "); Class<?>[] interfaces = serviceLimeClazz.getInterfaces(); for(Class inter : interfaces){ System.out.println("存根类实现了 " + inter.getName() + " 接口!"); } System.out.println(serviceLime.service("你好!")); } catch (NamingException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } } }
将以上代码保存于某一目录下,先运行“start rmiregistry”来启动JDK自带的注册表程序,它用于保存Server类注册的远程对象并允许远程客户端的请求访问;然后运行服务器端的Server类,即“start java Server”,该程序向注册表中注册具体的远程对象;最后才是运行客户端程序来查找并获得服务器端的远程对象存根,此时才能使用存根对象与服务器进行通信,命令是“java Client”。注意:上面命令中的start的功能是重新打开一个DOS窗口。
其实整个简单的RMI 应用中各个类的交互时序如下图:
啦啦啦