什么是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):
package com.abc.rmi.service;
import java.rmi.Remote;
public interface RmiService extends Remote {
public double add(double a,double b) throws java.rmi.RemoteException;
public double sub(double a,double b) throws java.rmi.RemoteException;
public double mul(double a,double b) throws java.rmi.RemoteException;
public double div(double a,double b) throws java.rmi.RemoteException;
public double max(double a,double b) throws java.rmi.RemoteException;
public double min(double a,double b) throws java.rmi.RemoteException;
public double avg(double a,double b) throws java.rmi.RemoteException;
}
这个接口需要继承自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的时候会报以下错误:
Rmi Server start failed: java.rmi.server.ExportException: remote object implements illegal remote interface; nested exception is:
java.lang.IllegalArgumentException: illegal remote method encountered: public abstract double com.abc.rmi.service.RmiService.add(double,double)
2. 接口实现(RmiServiceImpl.java):
package com.abc.rmi.service.impl;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import com.abc.rmi.service.RmiService;
public class RmiServiceImpl extends UnicastRemoteObject implements RmiService {
private static final long serialVersionUID = -317455609230764324L;
public RmiServiceImpl() throws RemoteException {
super();
}
@Override
public double add(double a, double b) throws RemoteException {
return a + b;
}
@Override
public double sub(double a, double b) throws RemoteException {
return a - b;
}
@Override
public double mul(double a, double b) throws RemoteException {
return a * b;
}
@Override
public double div(double a, double b) throws RemoteException {
return a / b;
}
@Override
public double max(double a, double b) throws RemoteException {
return a > b ? a : b;
}
@Override
public double min(double a, double b) throws RemoteException {
return a < b ? a : b;
}
@Override
public double avg(double a, double b) throws RemoteException {
return (a + b) / 2;
}
}
这个实现类使用了UnicastRemoteObject去联接RMI系统。这里我们继承了UnicastRemoteObject这个类它的作用是
事实上并不一定要这样做,如果一个类不是从UnicastRmeoteObject上继承,那必须使用它的exportObject()方法去联接到RMI。
如果一个类继承自UnicastRemoteObject,****那么它必须提供一个构造函数并且声明抛出一个RemoteException对象。当这个构造函数调用了super(),它将激活UnicastRemoteObject中的代码完成RMI的连接和远程对象的初始化。
3. 把方法注册到机器上(RmiServer.java):
package com.abc.rmi;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import com.abc.rmi.service.impl.RmiServiceImpl;
/**
* 启动 RMI 注册服务并进行对象注册
*/
public class RmiServer {
private static final String RMI_HOST = "192.168.15.44";
private static final int RMI_PORT = 6666;
private static final String RMI_URL = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/RmiServer";
public static void main(String[] argv) {
try {
LocateRegistry.createRegistry(RMI_PORT);
Naming.bind(RMI_URL, new RmiServiceImpl());
System.out.println("Rmi Server[" + RMI_HOST + ":" + RMI_PORT + "] start success." );
} catch (Exception e) {
System.out.println("Rmi Server start failed: " + e);
}
}
}
到此为止,Server端的代码结束。
客户端
- 将服务器端的RmiService拷贝到项目结构图的位置。
2. 客户端调用(RmiClient.java):
package com.abc.rmi;
import java.rmi.Naming;
import com.abc.rmi.service.RmiService;
public class RmiClient {
public static void main(String args[]) throws Exception {
RmiService rmi = (RmiService) Naming.lookup("rmi://192.168.15.44:6666/RmiServer");
System.out.println(rmi.add(100, 10));
System.out.println(rmi.sub(100, 10));
System.out.println(rmi.mul(100, 10));
System.out.println(rmi.div(100, 10));
System.out.println(rmi.max(100, 10));
System.out.println(rmi.min(100, 10));
System.out.println(rmi.avg(100, 10));
}
}
注意这里的URL需要和Server端定义的一样。
运行测试
先运行RmiServer,
再运行RmiClient,看执行结果:
中间可能会遇到类似这样的错误:
Exception in thread “main” java.security.AccessControlException: access denied (
java.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即可。