Java Remote Method Invocation(Java远程方法调用)允许程序员创建分布式Java技术为基础的应用,可以调用不同JVM远程对象上的远程方法。RMI使用对象序列化去marshal(编码),unmarshal(解码)参数进行,不截断类型,支持面向对象的多态性。
2. Stub
RMI相当于一个代理模式。客户对象想要调用远程服务对象上的远程方法,我们需要一些辅助对象。这些辅助的对象使客户就像在调用本地对象的方法一样。客户对象调用客户辅助对象上的方法,仿佛客户辅助对象就是真正的服务。而客户辅助对象负责为我们转发这些请求,但是客户辅助对象不是真正的远程服务。虽然操作很像,但是不具有真正远程服务对象的方法逻辑。在服务端,服务辅助对象从客户辅助对象中接受请求,将调用的信息解包,然后调用真正服务对象上的真正方法。服务辅助对象从服务中得到返回值,将它打包,然后返回客户辅助对象,客户服务对象对信息解包,最后将返回值交给客户端。
Stub(存根)就相当于这里的客户辅助对象。存根将所需要的参数进行编码后传递给远程方法,并将调用结果或者异常传递给客户端。从JDK1.5开始,存根类可以采用动态代理的方式自动生成。在这之前,必须使用rmic工具手动创建。
3. java.rmi.Remote
3.1 扩展java.rmi.Remote接口
Remote是一个标记接口,没有任何方法,所有远程服务对象必须间接或者直接的扩展Remote接口。由于远程服务对象可以被其他的JVM调用,所有可以实现一个或多个远程接口。如果远程服务对象被同时的访问,必须在其实现中保证线程的安全。
public interface MyRemote extends java.rmi.Remote{}
3.2 接口声明中的所有方法都将抛出RemoteException异常。
客户调用远程接口的stub上的方法,而stub底层调用了网络和I/O,所以可能会发生各种异常,所以必须声明远程异常来解决。java.rmi.RemoteException继承自java.io.IOException。
public interface MyRemote extends Remote{ public String sayHello() throws RemoteException; }
3.3 远程方法的变量和返回值必须是原语类型或者是可序列化类型。
这不难理解,因为远程方法的变量必须被打包并通过网络发送。(所以,在传送自己定义的类时,必须保证实现Serializable接口)
4. java.rmi.RemoteException
RemoteException继承自java.io.IOException。它是一个检查型异常,通常用来标识:通信失败、远程调用参数/返回值在编码(marshalling)/解码(unmarshalling)过程中出错,协议错误。
5. java.rmi.server.RemoteObject
RemoteObject类实现远程对象的java.lang.Object 行为,并提供了远程对象默认的hashCode、equals 和 toString 方法实现。创建远程对象并将其导出(使远程对象具有某些"远程的"功能)所需的方法由类UnicastRemoteObject 和 Activatable 提供。
6. java.rmi.registry.Registry
Registry是简单远程对象注册表的一个远程接口(默认的端口号为1099
),它提供绑定和查找的功能。其内部维护了一个名到对象的映射。LocateRegistry 用于获得对特定主机(包括本地主机)上引导远程对象注册表的引用,或用于创建一个接受对特定端口调用的远程对象注册表。 注意,getRegistry 调用并不实际生成到远程主机的连接。它只创建对远程注册表的本地引用,即便远程主机上没有正运行的注册表,它也会成功创建一个引用。LocateRegistry.createRegistry()方法在本机指定的端口上创建并发布一个registry远程对象。
7. 示例程序:
7.1 定义远程接口
public interface MyRemote extends Remote { public String sayHello() throws RemoteException; }
7.2 实现远程接口
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{ private static final long serialVersionUID = -3510887331988311760L; protected MyRemoteImpl() throws RemoteException { } //这里必须抛出RemoteException异常,因为超类会抛出这个异常 @Override public String sayHello() throws RemoteException { return "server say hello to client"; } }
7.3 用RMI Registry注册此服务
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{ private static final long serialVersionUID = -3510887331988311760L; protected MyRemoteImpl() throws RemoteException { } @Override public String sayHello() throws RemoteException { return "server say hello to client"; } public static void main(String[] args) { try { MyRemote remote = new MyRemoteImpl(); Naming.bind("myRemoteServer", remote); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (AlreadyBoundException e) { e.printStackTrace(); } } }
当注册这个实现对象时,RMI系统实际注册的是stub。
7.4 客户端定义
public class Client { public static void main(String[] args) { try { MyRemote remote = (MyRemote)Naming.lookup("rmi://127.0.0.1/myRemoteServer"); System.out.println(remote.sayHello()); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } } }
启动:
1. 启动remiregistry服务(位于Java\jdk1.6.0_18\bin,启动的目录必须为你workspace的bin目录)
rmiregistry -J-Djava.rmi.server.codebase=file:/D:/FXworkspace/rmi/bin/
2. 执行MyRemoteImpl
3. 执行Client
结果输出:
server say hello to client
工作方式:
1)客户到RMI registry中寻找
2)RMI registry返回stub对象。
3)客户调用stub方法,就像stub是真正的服务对象一样。
注:rmic是JDK内的一个工具,用来为一个服务类产生stub和skeleton。rmic有一些选项可以调整,包括不要产生skeleton、查看源代码、甚至使用IIOP作为协议。