RMI的概念:
RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制。使用这种机制,某一台计算机上的对象可以调用另外一台计算机上的对象来获取远程数据。RMI是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。在过去,TCP/IP套接字通讯是远程通讯的主要手段,但此开发方式没有使用面向对象的方式实现开发,在开发一个如此的通讯机制时往往令程序员感觉到乏味,对此RPC(Remote Procedure Call)应运而生,它使程序员更容易地调用远程程序,但在面对复杂的信息传讯时,RPC依然未能很好的支持,而且RPC未能做到面向对象调用的开发模式。针对RPC服务遗留的问题,RMI出现在世人面前,它被设计成一种面向对象的通讯方式,允许程序员使用远程对象来实现通信,并且支持多线程的服务,这是一次远程通讯的革命,为远程通信开辟新的里程碑。
RMI的理解:
在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象,供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用。
注意:extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常,则表明该方法可被客户端远程访问调用。
同时,远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为“存根”,而服务器端本身已存在的远程对象则称之为“骨架”。其实此时的存根是客户端的一个代理,用于与服务器端的通信,而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
RMI 框架的基本原理大概如下图,应用了代理模式来封装了本地存根与真实的远程对象进行通信的细节。
下面给出一个简单的RMI 应用,其中类图如下:其中IService接口用于声明服务器端必须提供的服务(即service()方法),ServiceImpl类是具体的服务实现类,而Server类是最终负责注册服务器远程对象,以便在服务器端存在骨架代理对象来对客户端的请求提供处理和响应。
RMI的开发步骤
- 先创建远程接口及声明远程方法,注意这是实现双方通讯的接口,需要继承Remote
- 开发一个类来实现远程接口及远程方法,值得注意的是实现类需要继承UnicastRemoteObject
- 通过javac命令编译文件,通过java -server 命令注册服务,启动远程对象
- 最后客户端查找远程对象,并调用远程方法
RMI的实例
实体类(远程传输需要实现可序列化接口):
package remote.entity;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 实体类(如果在远程调用方法时需要被传输,则必须实现可序列化接口)
* @Title:User
* @Description:Comment for created type
* @author 张颖辉
* @date 2016年11月13日下午3:18:27
* @version 1.0
*/
public class User implements Serializable{
/**
* 序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的
*/
private static final long serialVersionUID = 1L;
private int id;
private String name;
private Date birthday;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", birthday=" +new SimpleDateFormat("yyyy-MM-dd").format(birthday) + "]";
}
}
远程服务接口:
package remot.service;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import remote.entity.User;
/**
* 远程服务接口
* @Title:IService
* @Description:Comment for created type
* @author 张颖辉
* @date 2016年11月13日下午3:18:07
* @version 1.0
*/
public interface IService extends Remote{
/**
* 远程对象的接口必须所有的方法都抛出throws RemoteException异常,不然会报错
*/
public String speakString(String name) throws RemoteException;
public List<User> getUserLs() throws RemoteException;
}
远程服务实现:
package remot.service.impl;
import remot.service.IService;
import remote.entity.User;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 远程服务实现( 远程服务接口实现类)
* @Title:ServiceImpl
* @Description:Comment for created type
* @author 张颖辉
* @date 2016年11月13日下午3:15:56
* @version 1.0
*/
public class ServiceImpl extends UnicastRemoteObject implements IService {
private String name;
public ServiceImpl(String name) throws RemoteException {
super();
this.name = name;
}
@Override
public String speakString(String spkStr) {
System.out.println("服务器收到:" + spkStr);
return name+":您說了“" + spkStr+"”";
}
@Override
public List<User> getUserLs() {
List<User> users=new ArrayList<>();
for (int i = 1; i < 11; i++) {
User user=new User();
user.setId(i);
user.setName("用户"+i);
user.setBirthday(new Date(2016-1900, 11, 13+i));
users.add(user);
}
return users;
}
}
服务端:
package remot.server;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import remot.service.IService;
import remot.service.impl.ServiceImpl;
/**
* 服务器
* @Title:Server
* @Description:Comment for created type
* @author 张颖辉
* @date 2016年11月13日下午3:20:29
* @version 1.0
*/
public class Server {
public static String ip="localhost";
public static int port=1987;
public static void main(String[] args) {
String url = "rmi://"+ip+":"+port+"/";
try {
//实例化实现了IService接口的远程服务ServiceImpl对象
IService service = new ServiceImpl("service");
//初始化命名空间
Context namingContext = new InitialContext();
//注册通讯端口
LocateRegistry.createRegistry(1987);
//将名称绑定到对象,即向命名空间注册已经实例化的远程服务对象
namingContext.rebind(url+"service01", service);
System.out.println("服务器向命名表注册了1个远程服务对象!");
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端:
package remot.client;
import javax.naming.Context;
import javax.naming.InitialContext;
import remot.service.IService;
/**
* 客户端(根据)
* @Title:Client
* @Description:Comment for created type
* @author 张颖辉
* @date 2016年11月13日下午3:20:45
* @version 1.0
*/
public class Client {
public static String ip="localhost";
public static int port=1987;
public static void main(String[] args) {
String url = "rmi://"+ip+":"+port+"/";
try {
//初始化命名空间
Context namingContext = new InitialContext();
// 检索指定的对象。 即找到服务器端相对应的服务对象存根
IService service = (IService) namingContext.lookup(url + "service01");
Class stubClass = service.getClass();
System.out.println("service="+service + "\n 是 " + stubClass.getName() + " 的实例!");
// 获得本地存根已实现的接口类型
Class[] interfaces = stubClass.getInterfaces();
for (Class c : interfaces) {
System.out.println("存根类实现了 " + c.getName() + " 接口!");
}
System.out.println(service.speakString("你好!"));
System.out.println(service.getUserLs());
} catch (Exception e) {
e.printStackTrace();
}
}
}
启动服务端server.main:
执行客户端client.main
其中打印出客户端获取的远程对象类型是com.sun.proxy.$Proxy0 显然是一个代理类,是服务端serviceImpl实例对象的客户端本地拷贝。
交互流程:
其中LocateRegistryAPI:
http://tool.oschina.net/uploads/apidocs/jdk-zh/java/rmi/registry/LocateRegistry.html
参考文章:http://www.cnblogs.com/leslies2/archive/2011/05/20/2051844.html
http://haolloyin.blog.51cto.com/1177454/332426