分布式对象 ---- 力求“无处不在的对象”。RMI(远程方法调用),支持java分布式对象的方法调用。是分布式对象软件包,简化了在多个计算机中的java应用之间的通信。
What is RMI?
RMI is 一种计算机之间对象互相调用对方方法,启动对方进程的机制,某一台计算机上的对象在调用另外一台计算机上的方法时,使用的程序语法规则和在本地机上对象间的方法调用的语法规则一样。
优点
这种机制给分布计算的系统设计、编程都带来了极大的方便。
只要按照RMI规则设计程序,可以不必再过问在RMI之下的网络细节了,如:TCP和Socket等等。
任意两台计算机之间的通讯完全由RMI负责。调用远程计算机上的对象就像本地对象一样方便。
说白了,RMI就是提供一种远程方法调用,所有的远程服务的底层交互都有RMI来完成(RMI将其内部实现细节隐藏),使得我们调用远程对象的方法就像是在访问本地对象一样,使得分布式编程轻松、简单。
下面来创建简单的RMI程序:
步骤->
1、 定义远程接口,该接口必须继承自java.rmi.Remote接口,且声明的可以被远程调用的方法必须抛出RemoteException异常。
2、 定义一个实现该接口的类。
3、 使用rmic生成存根和框架。
4、 创建一个服务器,用于发布远程对象(如2中的对象)。
5、 创建一个客户端程序进行RMI调用。
6、 启动rmiRegistry并运行服务器和客户端程序。
定义接口
package org.shniu.rmi.simple2;
import java.rmi.Remote; import java.rmi.RemoteException;
/** * 远程方法调用的接口,必须继承java.rmi.Remote * 所有可以被远程调用的对象都必须实现Remote * * @author shniu */ publicinterface IProduct extends Remote { /** * 通过产品id获取产品名称 * * @throws RemoteException 接口中定义的方法必须声明抛出RemoteException异常 */ String getProductNameById(long productId) throws RemoteException;
/** * 返回产品的相关信息 * * @throws RemoteException 接口中定义的方法必须声明抛出RemoteException异常 */ Object getProductInfo() throws RemoteException;
/** * @注:必须声明抛出RemoteException的原因 * * 由于任何远程方法调用实际上要进行许多低级网络操作,因此网络错误可能在调用过程中随时发生。 * * 因此,所有的RMI操作都应放到try-catch块中。 */ }
|
接口实现:
package org.shniu.rmi.simple2;
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;
/** * * 实现类,该类必须实现IProduct接口和继承UnicastRemoteObject类 * * UnicastRemoteObject,是让客户机与服务器对象实例建立一对一的连接。 * * @author shniu */ publicclass Book extends UnicastRemoteObject implements IProduct { private String bookName;
private String bookDesc;
private String author;
privatelongcode;
public String getBookName() { returnthis.bookName; }
publicvoid setBookName(String bookName) { this.bookName = bookName; }
public String getBookDesc() { returnthis.bookDesc; }
publicvoid setBookDesc(String bookDesc) { this.bookDesc = bookDesc; }
public String getAuthor() { returnthis.author; }
publicvoid setAuthor(String author) { this.author = author; }
publiclong getCode() { returnthis.code; }
publicvoid setCode(long code) { this.code = code; }
/** <默认构造函数> */ protected Book() throws RemoteException { super(); }
/** * 带参构造 */ public Book(String bookName, String bookDesc, String author, long code) throws RemoteException { super(); this.bookName = bookName; this.bookDesc = bookDesc; this.author = author; this.code = code; }
/** * @return * @throws RemoteException */ public Object getProductInfo() throws RemoteException { returnthis.toString(); }
/** * @param productId * @return * @throws RemoteException */ public String getProductNameById(long productId) throws RemoteException { if (productId >= 0) { returnthis.bookName; } returnnull; }
@Override public String toString() { return"Book [author=" + this.author + ", bookDesc=" + this.bookDesc + ", bookName=" + this.bookName + ", code=" + this.code + "]"; } }
|
服务端实现:
package org.shniu.rmi.simple2;
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException;
/** * 用于发布远程对像 的类 * * * @author shniu */ public class ProductServer { public static void main(String[] args) { try { // 实例化 要发布的类 Book book1 = new Book("java程序设计", "讲述java知识,包括javaSE等基础内容", "shniu", 123456789L); Book book2 = new Book("SSH详解", "讲述SSH三大框架的使用与集成", "shniu", 987654321L);
// 绑定RMI名称进行发布 // 由于是本地测试,省略了,缺省为"rmi://localhost:1099/java_book" Naming.rebind("java_book", book1); Naming.rebind("java_SSH", book2);
System.out.println("Ready and Waiting for invocation ..."); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } |
客户端实现
package org.shniu.rmi.simple2;
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RMISecurityManager; import java.rmi.RemoteException;
/** * 客户端调用 * * @author shniu */ public class ClientInvocation { public static void main(String[] args) { try { // 注册安全管理器 System.setSecurityManager(new RMISecurityManager());
// 通过RMI查找远程对象 IProduct product = (IProduct)Naming.lookup("java_book");// lookup方法中的参数,组成应该是"rmi://ip地址:port/RMI名称" IProduct SSH = (IProduct)Naming.lookup("java_SSH"); // 由于是本地测试,省略了,缺省为"rmi://localhost:1099/java_book" // 应与服务端url一致
// 调用远程对象 String name = product.getProductNameById(123456789L); Object obj = product.getProductInfo(); System.out.println("#productName : " + name); System.out.println("#productInfo : " + obj);
String ssh_name = SSH.getProductNameById(987654321L); Object ssh_obj = SSH.getProductInfo(); System.out.println(); System.out.println("#productName : " + ssh_name); System.out.println("#productInfo : " + ssh_obj); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } } } |
使用rmic编译Book.class(先javac编译)生成Stub存根Book_Stub.class
rmic org.shniu.rmi.simple2.Book
启动rmiregistry,并运行服务器
DOS下运行:start rmiregistry [port]
Port 是可选的 ,如果不加,默认启动1099端口
启动服务进程
start java org.shniu.rmi.simple2.ProductServer
客户端运行
在运行客户端之前,要将Stub和IProduct加入类路径
做法1、将Stub和IProduct打成jar包,加入classpath
做法2、运行客户端程序是使用
java –Djava.rmi.server.codebase=file:/Stub和IProduct的存放位置 ClientInvocation (此法还未验证)
注:在运行客户端是可能会出现的问题
通常会报的错误是Access denied(java.net.SocketPermission .....)
问题原因:这是访问权限限制的问题,RMI的服务需要授权,外部的程序才能访问,所以改动jre的安全配置文件,来开放权限。
打开jdk目录下的%JAVA_HOME%/jre/lib/security/java.policy,在文件的最后加入下面代码:
Grant
{
Permission java.net.SocketPermission “*:1024-65535”
“connect,accept”;
Permission java.net.SocketPermission “*:80” “connect”;
};
应该修改的是服务端的java.policy文件。
RMI运行机理:
RMI应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机调用这些远程对象的方法。而典型的客户机程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI为服务器和客户机进行通信和信息传递提供了一种机制。
理解客户与服务器
在RMI中,如果一个对象的方法发起远程调用,则该对象称为客户端对象,相应的远程对象则称为服务器端对象。客户/服务器的概念是针对一次调用而言的,而他们的角色是随时发生变化的。