一、什么RMI
RMI即(Rmote Method Invoke)远程方法调用。换句话说客户对象Client可以调用远程Server上的方法。客户端并不是直接调用远程服务上的方法,而是通过客户辅助对象与远程服务通信。客户辅助对象会联系服务器,传送相应的调用信息,等待服务器的返回。在服务器端,存在一个服务器辅助对象,该服务器辅助对象从客户端辅助对象中接收请求(通过Socket连接),将请求交由真正的服务对象处理。最后服务器辅助对象将从服务中的得到的处理结果打包,然后发送给客户辅助对象(通过Socket输出流),客户辅助对象将得到的结果解包,最后将返回结果交给客户端。在RMI中将客户端辅助对象称为存根Stub,将服务器辅助对象称为骨架Skeleton。
二、RMI通信流程
三、JAVA RMI 的一个简单DEMO
制作一个可供远程调用的对象,需要该类或者接口实现或者继承java.rmi.Remote接口。该接口为一个标记接口,其中不含有任何可供实现的方法。只用实现或集成该接口的类,才能成为一个可供调用的远程对象。
同时应该注意远程方法的参数和返回值应该为java基本类型或者Serializable类型。
为了简单起见一下源码不采用包结构。
MyService接口源码:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MyService extends Remote { // 继承Remote接口
// 定义能被远程调用的方法
String service(String content) throws RemoteException;
}
实现该接口,MyServiceImpl源码:
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// 需要继承UnicastRemoteObject类,用于导出的远程对象和获得与该远程对象通信的存根
public class MyServiceImpl extends UnicastRemoteObject implements MyService {
private static final long serialVersionUID = -3145731787217567264L;
protected MyServiceImpl() throws RemoteException {
super();
}
// 实现服务方法
public String service(String content) {
return "server >> " + content;
}
}
Server类源码:
import java.rmi.Naming;
public class Server {
public static void main(String[] args) {
try {
// 实例化服务类
MyService service = new MyServiceImpl();
// 将该服务注册到RMI Registry中,服务名为service
// 当绑定服务对象时,RMI会把服务换成Stub,然后把Stub放入registry中
Naming.rebind("service", service);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("register");
}
}
Client类源码:
import java.rmi.Naming;
public class Client {
public static void main(String[] args) {
// service为服务端注册的服务名
String url = "rmi://localhost/service";
try {
// 客户端到RMI Registry中查找服务
MyService service = (MyService) Naming.lookup(url);
Class stubClass = service.getClass();
System.out.println(stubClass + " 是 " + stubClass.getName() + "的实例");
Class[] infaces = stubClass.getInterfaces();
for(Class c : infaces) {
System.out.println("stub实现了" + c.getName() + "接口");
}
// 调用服务的方法并打印返回的结果
System.out.println(service.service(args[0]));
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意,为了简单起见所有源码放在一个文件夹下
1.编译源码生成.class文件
// 指定编码为UTF-8
javac -encoding utf-8 *.java
2.通过rmic生成stub和skeleton,rmic为JDK内置工具,通过该工具生成后缀为_Stub和_Skel的文件(实际并未产生后缀为_Skel的骨架文件,经过查询发现Jdk1.2后不需要生成该文件)
// 注意是通过实现类生成,而不是接口
rmic MyServiceImpl
3.开启另一个命令行窗口,执行rmiregistry注意该命令行窗口必须在源码所在目录打开(很重要),不然会报错
4.启动服务(启动服务前必须保证rmiregistry运行)
java Server
5.运行客户端代码
java Client Hello,world!
四、后记
将生成的_Stub后缀文件删除,重新运行Server和Client发现不一样的运行结果。似乎生成了代理类(不是很清楚,待了解!)
参考:
http://haolloyin.blog.51cto.com/1177454/332426/
https://segmentfault.com/a/1190000002737588
《Head First设计模式》