远程方法调用(Remote Method Invocation,RMI)是用Java在JDK1.1中实现的,它大大增强了Java开发分布式应用的能力。
RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信,JRMP是专为Java的远程对象制定的协议。由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足,不能与用非Java语言书写的对象进行通信。
RMI/JNI和RMI/JDBC相结合,可帮助您利用RMI与目前使用非Java语言的现有服务器进行通信,而且在您需要时可扩展Java在这些服务器上的使用。
一、RMI运行原理
RMI应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机调用这些远程对象的方法。而典型的客户机程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI为服务器和客户机进行通信和信息传递提供了一种机制。
在与远程对象的通信过程中,RMI使用标准机制:stub和skeleton。远程对象的stub担当远程对象的客户本地代表或代理人角色。调用程序将调用本地stub的方法,而本地stub将负责执行对远程对象的方法调用。在远程虚拟机中,每个远程对象都可以有相应的skeleton(在JDK1.2环境中无需使用skeleton)。Skeleton负责将调用分配给实际的远程对象实现。stub和skeleton由rmic编译器生成。
在RMI分布式应用程序运行时,服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器上的注册服务程序中用远程对象的名字查找该远程对象,然后调用它的方法。
二、范例源码
1、远程传输对象
/** * 远程传输的对象必须实现Serializable接口 */ public class Person implements Serializable{ private String username; private String password; public Person(String username, String password){ this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
2、远程服务的接口定义
/** * 远程服务的接口定义 * * 该接口必须继承Remote,每个方法都必须抛出RemoteException异常对象 */ public interface HelloWorld extends Remote { public void say(String name) throws RemoteException; public Person calculate(Person p) throws RemoteException; }
3、远程服务接口的具体实现
/** * 远程服务接口的具体实现 * * 如果一个类继承自UnicastRemoteObject,那么它必须提供一个构造函数并且声明抛出一个RemoteException对象。 * 当这个构造函数调用了super(),它就激活UnicastRemoteObject中的代码完成RMI的连接和远程对象的初始化。 */ public class HelloWorldImpl extends UnicastRemoteObject implements HelloWorld { private static final long serialVersionUID = -8328854041003669643L; //必须有一个显式的构造函数,并且要抛出RemoteException异常对象 public HelloWorldImpl() throws RemoteException{ super(); } public void say(String name) throws RemoteException { System.out.println("Hello " + name); } public Person calculate(Person p) throws RemoteException { System.out.println(p.getUsername() + " _ " + p.getPassword()); p.setPassword("new pwd"); return p; } }
4、服务器程序
/** * 服务器程序 */ public class HelloWorldServer { public HelloWorldServer(){ try{ HelloWorld obj = new HelloWorldImpl(); //Naming.rebind("rmi://localhost:1099/HelloWorldServer", obj); //通过程序创建RMI注册服务,从而不需要执行rmiregistry命令 Registry reg = LocateRegistry.createRegistry(1099); reg.rebind("HelloWorldServer", obj); }catch(Exception e){ e.printStackTrace(); } } public static void main(String[] args) { new HelloWorldServer(); } }
5、客户端程序
/** * 客户端程序 */ public class HelloWorldClient { public static void main(String[] args) { try{ HelloWorld obj = (HelloWorld)Naming.lookup("rmi://localhost:1099/HelloWorldServer"); obj.say("chenjumin"); Person p = new Person("cjm", "123"); Person p2 = obj.calculate(p); System.out.println(p2.getUsername() + " _ " + p2.getPassword()); }catch(MalformedURLException e){ e.printStackTrace(); }catch(RemoteException e){ e.printStackTrace(); }catch(NotBoundException e){ e.printStackTrace(); } } }
*6、用Ant命令生成桩(Stub)和框架(Skeleton)文件(自JDK1.5之后不再需要手工创建这两个文件,而由JDK通过代理机制动态生成)
<project name="rmiTask" basedir="." default="rmic"> <target name="rmic" > <rmic classname="rmi.server.HelloWorldImpl" base="${basedir}/bin"/> </target> </project>
*7、启动RMI注册服务(注意:DOS命令必须在最顶层包路径所在的目录下执行)
DOS命令为:rmiregistry 1099
8、启动服务端程序
DOS命令为:java rmi.server.HelloWorldServer
9、启动客户端程序
DOS命令为:java rmi.server.HelloWorldClient