远程代理:远程代理就好比“远程对象的本地代表”。所谓远程对象,是一种对象,活在不同的java虚拟机堆中(更一般的说法是,在不同的地址空间运行的远程对象)。所谓的本地代表,是一种可以由本地方法调用的对象,其行为会转发到远程对象中。
1、RMI定义和功能
RMI是Remote Method Invocation的简称,是J2SE的一部分,能够让程序员开发出基于Java的分布式应用。一个RMI对象是一个远 程Java对象,可以从另一个Java虚拟机上(甚至跨过网络)调用它的方法,可以像调用本地Java对象的方法一样调用远程对象的方法,使分布在不同的JVM中的对象的外表和行为都 像本地对象一样。
RMI将客户辅助对象称为Stub(桩),服务辅助对象称为skeleton(骨架)
2、Stub和Skeleton介绍
在学习RMI的时,我们不能不讨论stub和skeleton作用和相关问题。他们是我们理解RMI原理的关键。我做个比方说明这两个概念。假如你是A, 你想借D的工具,但是又不认识D的管家C,所以你找来B来帮你,B认识C。B在这时就是一个代理,代理你的请求,依靠自己的话语去借。C呢他负责D家东西 收回和借出 ,但是要有D的批准。在得到D的批准以后,C再把东西给B,B呢再转给A。stub和skeleton在RMI中就是角色就是B和C,他们都是代理角色, 在现实开发中隐藏系统和网络的的差异,这一部分的功能在RMI开发中对程序员是透明的。Stub为客户端编码远程命令并把他们发送到服务器。而 Skeleton则是把远程命令解码,调用服务端的远程对象的方法,把结果在编码发给stub,然后stub再解码返回调用结果给客户端。
从JDK5.0以后,这两个类就不需要rmic来产生了,而是有JVM自动处理,实际上他们还是存在的。Stub存在于客户端,作为客户端的代理,让我们总是认为客户端产生了stub,接口没有作用。实际上stub类是通过Java动态类下载 机制下载的(具体内容请参考:Java RMI实现代码动态下载http://blog.csdn.net/QB2049_XG/article/details/3170514),它是由服 务端产生,然后根据需要动态的加载到客户端,如果下次再运行这个客户端该存根类存在于classpath中,它就不需要再下载了,而是直接加载。(具体的内部细节,需要参考Sun 的Rmi - Java Remote Method Invocation – Specification)。总的来说,stub是在服务端产生的,如果服务端的stub内容改变,那么客户端的也是需要同步更新。
3、Rmiregistry介绍
Rmiregistry需要在提供远程对象服务端启动,它提供了一个环境,说白了就是在内存中,开辟了一片空间,用来接受服务端服务程序的注册,产生一个 类似于数据库,提供存储检索远程对象功能的注册表。这个RMI注册表,就是我们常听到的RMI名字服务。远程客户端,就是依靠它获得存根,调用远程方法。 说到 这里有一个端口的问题,如果你在启动rmiregistry时,设定了非默认端口,那么需要在服务端和客户端统一使用该端口,否则就会有 RemoteException的异常抛出,rmiregistry提 供的服务是针对特定的端口号的,不然在同一台机器上也是无法提供服务。
二、RMI实例开发
实现的具体步骤主要是由:
step1:制作远程的接口。远程接口定义出可以让客户远程调用的方法。客户将它作为服务的类类型。stub和实际的服务都需要实现此接口。
要注意:所有的方法都必须跑出RemoteException。确定变量和返回值是属于原语(primitive)类型或者可序列化的类型(Serializable)。
Step2:制作远程的实现
这个是实际工作的类。为远程接口中定义的远程方法提供了真正的实现。需要实现上面定义的远程接口。
Step3:利用rmic产生stub和skeleton
这就是客户和服务的辅助类。默认的在java5以后此步可以省去
step4:启动RMI registry
可以在cmd或者shell利用rmiregistry
step5:开始远程服务让服务对象开始运行。
一定要注意调用rebind方法前,一定要先启动rmiregistry服务。
工作原理如图所示:
对象关系示意图:
一个正常工作的RMI系统由下面几个部分组成:
● 远程服务的接口定义
● 远程服务接口的具体实现
● 桩(Stub)和框架(Skeleton)文件
● 一个运行远程服务的服务器
● 一个RMI命名服务,它答应客户端去发现这个远程服务
● 类文件的提供者(一个HTTP或者FTP服务器)
● 一个需要这个远程服务的客户端程序
下面我们一步一步建立一个简单的RMI系统。首先在你的机器里建立一个新的文件夹,以便放置我们创建的文件,为了简单起见,我们只使用一个文件夹存放客户端和服务端代码,并且在同一个目录下运行服务端和客户端。
假如所有的RMI文件都已经设计好了,那么你需要下面的几个步骤去生成你的系统:
1、编写并且编译接口的Java代码
package lib.idc; public interface Calculator extends Remote { public long add(long a, long b) throws java.rmi.RemoteException; }
package lib.idc; import java.rmi.server.UnicastRemoteObject; public class CalculatorImpl extends UnicastRemoteObject implements Calculator{ //这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常 public CalculatorImpl() throws java.rmi.RemoteException { } public long add(long a, long b) throws java.rmi.RemoteException { return a + b; } }
同样按照以上步骤编译CalculatorImpl.java
>>javac lib/idc/ CalculatorImpl.java
3、从接口实现类中生成桩(Stub)和框架(Skeleton)类文件
>>rmic lib.idc.CalculatorImpl
在你的目录下运行上面的命令,成功执行完上面的命令你可以发现一个Calculator_stub.class文件,假如你是使用的Java2SDK,那么你还可以发现Calculator_Skel.class文件。(我的没有)
4、编写远程服务的主运行程序
远程RMI服务必须是在一个服务器中运行的。CalculatorServer类是一个非常简单的服务器。
package lib.idc; import java.rmi.Naming; public class CalculatorServer { public CalculatorServer() { try{ Calculator c = new CalculatorImpl(); Naming.rebind("rmi://localhost:1099/CalculatorService", c); }catch (Exception e) { System.out.println("Trouble: " + e); } } public static void main(String args[]) { new CalculatorServer(); } }建立这个服务器程序,然后保存到你的目录下。
package lib.idc; import java.rmi.Naming; import java.rmi.RemoteException; import java.net.MalformedURLException; import java.rmi.NotBoundException; import java.rmi.RMISecurityManager; public class CalculatorClient { public static void main(String[] args) { if(System.getSecurityManager()==null) System.setSecurityManager(new RMISecurityManager()); try { Calculator c = (Calculator) Naming.lookup( "rmi://localhost/CalculatorService"); System.out.println( c.add(4, 5) ); } catch (MalformedURLException murle) { System.out.println("MalformedURLException"); } catch (RemoteException re) { System.out.println("RemoteException"); } }6、运行RMI系统
参考:
http://stevencjh.blog.163.com/blog/static/1218614612010102145853184/
简单的看一下其原理图: