什么是RMI
RMI,全称Remote Method Invoke,远程方法调用。它能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法。它的强大之处就体现在开发分布式网络应用的能力上,是纯Java的网络分布式应用系统的核心解决方案之一。它支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。
RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。由于JRMP是专为Java对象制定的,Java RMI具有Java的"Write Once,Run Anywhere"的优点,是分布式应用系统的百分之百纯Java解决方案。用Java RMI开发的应用系统可以部署在任何支持JRE的平台上。但由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信。
RMI可利用标准Java本机方法接口JNI与现有的和原有的系统相连接。RMI还可利用标准JDBC包与现有的关系数据库连接。RMI/JNI和RMI/JDBC相结合,可帮助您利用RMI与目前使用非Java语言的现有服务器进行通信,而且在您需要时可扩展Java在这些服务器上的使用。RMI可帮助您在扩展使用时充分利用Java的强大功能。
RMI的基础是接口,RMI构建基于一个重要的原理:定义接口和定义接口的具体实现是分开的。下面通过一个例子,演示如何使用Java RMI。
使用Java RMI
话不多说了,先看下项目结构图:

注意这里是两个项目,可以将Server与Client放在不同的机器上执行,我只有一台机器,所以只能将它们分成两个不同的项目了。
然后上代码(我也是一边学习一边测试,然后记录整个过程,因此这个接口就是大家见过的啦)
服务器端
1. 接口定义(RmiService.java):
1package com.abc.rmi.service; 2 3import java.rmi.Remote; 4 5public interface RmiService extends Remote { 6 public double add(double a,double b) throws java.rmi.RemoteException; 7 public double sub(double a,double b) throws java.rmi.RemoteException; 8 public double mul(double a,double b) throws java.rmi.RemoteException; 9 public double div(double a,double b) throws java.rmi.RemoteException; 10 public double max(double a,double b) throws java.rmi.RemoteException; 11 public double min(double a,double b) throws java.rmi.RemoteException; 12 public double avg(double a,double b) throws java.rmi.RemoteException; 13}
这个接口需要继承自Rmi的Remote接口。官方文档中这样说:
Any object that is a remote object must directly or indirectly implement this interface. Only those methods specified in a "remote interface", an interface that extends java.rmi.Remote are available remotely.
注意,每一个方法声明都必须抛出java.rmi.RemoteException,否则在启动Server的时候会报以下错误:
1Rmi Server start failed: java.rmi.server.ExportException: remote object implements illegal remote interface; nested exception is: 2 java.lang.IllegalArgumentException: illegal remote method encountered: public abstract double com.abc.rmi.service.RmiService.add(double,double)
2. 接口实现(RmiServiceImpl.java):
1package com.abc.rmi.service.impl; 2 3import java.rmi.RemoteException; 4import java.rmi.server.UnicastRemoteObject; 5import com.abc.rmi.service.RmiService; 6 7public class RmiServiceImpl extends UnicastRemoteObject implements RmiService { 8 private static final long serialVersionUID = -317455609230764324L; 9 public RmiServiceImpl() throws RemoteException { 10 super(); 11 } 12 13 @Override 14 public double add(double a, double b) throws RemoteException { 15 return a + b; 16 } 17 @Override 18 public double sub(double a, double b) throws RemoteException { 19 return a - b; 20 } 21 @Override 22 public double mul(double a, double b) throws RemoteException { 23 return a * b; 24 } 25 @Override 26 public double div(double a, double b) throws RemoteException { 27 return a / b; 28 } 29 @Override 30 public double max(double a, double b) throws RemoteException { 31 return a > b ? a : b; 32 } 33 @Override 34 public double min(double a, double b) throws RemoteException { 35 return a < b ? a : b; 36 } 37 @Override 38 public double avg(double a, double b) throws RemoteException { 39 return (a + b) / 2; 40 } 41}
这个实现类使用了UnicastRemoteObject去联接RMI系统。这里我们继承了UnicastRemoteObject这个类它的作用是

事实上并不一定要这样做,如果一个类不是从UnicastRmeoteObject上继承,那必须使用它的exportObject()方法去联接到RMI。
如果**一个类继承自UnicastRemoteObject,**那么它必须提供一个构造函数并且声明抛出一个RemoteException对象。当这个构造函数调用了super(),它将激活UnicastRemoteObject中的代码完成RMI的连接和远程对象的初始化。
3. 把方法注册到机器上(RmiServer.java):
1package com.abc.rmi; 2 3import java.rmi.Naming; 4import java.rmi.registry.LocateRegistry; 5 6import com.abc.rmi.service.impl.RmiServiceImpl; 7 8/** 9 * 启动 RMI 注册服务并进行对象注册 10 */ 11public class RmiServer { 12 private static final String RMI_HOST = "192.168.15.44"; 13 private static final int RMI_PORT = 6666; 14 private static final String RMI_URL = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/RmiServer"; 15 16 public static void main(String[] argv) { 17 try { 18 LocateRegistry.createRegistry(RMI_PORT); 19 Naming.bind(RMI_URL, new RmiServiceImpl()); 20 System.out.println("Rmi Server[" + RMI_HOST + ":" + RMI_PORT + "] start success." ); 21 } catch (Exception e) { 22 System.out.println("Rmi Server start failed: " + e); 23 } 24 } 25}
到此为止,Server端的代码结束。
客户端
- 将服务器端的RmiService拷贝到项目结构图的位置。
2. 客户端调用(RmiClient.java):
1package com.abc.rmi; 2 3import java.rmi.Naming; 4import com.abc.rmi.service.RmiService; 5 6public class RmiClient { 7 public static void main(String args[]) throws Exception { 8 RmiService rmi = (RmiService) Naming.lookup("rmi://192.168.15.44:6666/RmiServer"); 9 System.out.println(rmi.add(100, 10)); 10 System.out.println(rmi.sub(100, 10)); 11 System.out.println(rmi.mul(100, 10)); 12 System.out.println(rmi.div(100, 10)); 13 System.out.println(rmi.max(100, 10)); 14 System.out.println(rmi.min(100, 10)); 15 System.out.println(rmi.avg(100, 10)); 16 } 17}
注意这里的URL需要和Server端定义的一样。
运行测试
先运行RmiServer,

再运行RmiClient,看执行结果:

中间可能会遇到类似这样的错误:
1Exception in thread “main” java.security.AccessControlException: access denied ( 2java.net.SocketPermission 192.168.15.44:6666 connect,resolve)
此时需要配置java.policy。打开%JAVA_HOME%/jre/lib/security/java.policy,添加这么一行:
permission java.net.SocketPermission "192.168.15.44:6666","listen,accept,connect,resolve";
保存后重启Server和Client即可。