RMI(Remote Method Invocation)远程方法注入,用来实现远程方法调用,是实现分布式技术的一种方法。RMI提供了客户辅助对象和服务辅助对象,为客户辅助对象创建了和服务对象相同的方法。其好处在于我们不必亲自写任何网络或I/O代码。客户程序在调用远程方法时就和调用本地方法一样(感觉上)。

  RMI分为客户端和服务端。

  服务端的构建步骤如下:

  1. 扩展远程接口Remote(),使其符合项目需求
  2. 实现被扩展的接口
  3. 为服务对象绑定唯一标识
  4. 利用rmic产生stub(客户辅助对象)和skeleton(服务辅助对象,高版本的Java不需要显示的skeleton对象,但服务端仍然有一些东西负责skeleton的行为)
  5. 启动rmireistry(rmireistry类似一个注册表,客户可以从中找到代理的位置)
  6. 启动远程服务

  客户端的构建步骤如下:

  1. 根据注册的服务名查找服务得到实例的引用
  2. 调用服务的相关方法

  通过一个具体的例子来说明:

  假设现在有一个自动售货机公司(销售的主要产品为糖果),为了能尽可能的扩大受益,公司需要知道每个自动售货机的运行状态,从而在售货机中的糖果卖完时,可以及时的添加;如果哪台售货机坏掉了,可以及时的维修。但是售货机分布在一个城市的各个角落,如果用人工去一个个检查,效率十分低下。所以公司希望能有一个监控系统,这样就可以一个人坐在办公室,点点鼠标,就一切尽掌握啦~

  这样一个系统的最大的特殊点在于我们都是远程监控这些自动售货机,所以我们需要用到RMI。

  服务端程序:

  第一步、我们先定义一个对象,这个对象用来存储自动售货机的状态信息:自动售货机的地理位置、运行状态和剩余糖果数。远程方法的变量必须被打包并通过网络传输,这就需要通过序列化来完成。一般的原语类型、字符串和许多API中的内定类型(包括数据和集合),都不会存在问题,但是如果是自己定义的类,就需要实现Serializable接口。具体代码如下:

  等等,还要再这里插入一小段程序:枚举。用于定义机器的运行状态,具体代码如下:

 package pattern.proxy;

 /**
* 机器的运行状态
* RUNNING:正常运行
* BREAKDOWN:坏掉了
* REPAIR:维修中
* @author CS_Xiaochao
*
*/
public enum MachineState {
RUNNING, BREAKDOWN, REPAIR
}

  

 package pattern.proxy;

 import java.io.Serializable;

 /**
* 糖果机对象
* 实现Serializable接口,因为需要在网络中进行传输
* @author CS_Xiaochao
*
*/
public class GumballMachineBean implements Serializable{ /**
*
*/
private static final long serialVersionUID = -6395522198769463162L;
private String localtion; // 糖果机的位置
private int count; // 糖果的剩余数量
private MachineState state; // 机器的运行状态 public GumballMachineBean() {
super();
// TODO Auto-generated constructor stub
} public GumballMachineBean(String localtion, int count, MachineState state) {
super();
this.localtion = localtion;
this.count = count;
this.state = state;
} public String getLocaltion() {
return localtion;
} public void setLocaltion(String localtion) {
this.localtion = localtion;
} public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} public MachineState getState() {
return state;
} public void setState(MachineState state) {
this.state = state;
} }

  第二步、扩展Remote接口,Remote接口中未声明任何方法,只是一个“标记”。所以我们需要扩展该接口来符合自己的项目需求。客户使用远程接口调用服务,也就是说客户会调用实现远程接口的Stub上的方法,而Stub底层用到了网络和I/O,所以存在一定的风险性,必须通过处理或声明远程异常来解决。具体源码如下:

 package pattern.proxy;

 import java.rmi.Remote;
import java.rmi.RemoteException; /**
* 扩展Remote接口,以适应糖果机
* @author CS_Xiaochao
*
*/
public interface GumballMachineMonitor extends Remote {
/**
     * 获取糖果机状态信息
* 网络中存在许多不可测的因素,所以必须对其进行异常控制
* @return
* @throws RemoteException
*/
GumballMachineBean getGumballMachineBean() throws RemoteException; }

  第三步、实现远程扩展的接口。在这里我另开了一个线程用于糖果机的售卖糖果的动作,在实际中糖果机在运行时,销售糖果和上传状态信息是两个独立的动作,所以用两个线程来分别负责比较合理。糖果贩卖源码如下:

 package pattern.proxy;

 /**
* 糖果销售动作对象
* @author CS_Xiaochao
*
*/
public class SellCandy implements Runnable { private GumballMachineBean gumballMachine; public SellCandy() {
// TODO Auto-generated constructor stub
} public SellCandy(GumballMachineBean gumballMachine) {
this.gumballMachine = gumballMachine;
} @Override
public void run() {
while(true){
if(gumballMachine.getState() == MachineState.RUNNING){
int countTemp = gumballMachine.getCount();
if(countTemp > 0){
--countTemp; //每次卖出一颗糖
}
gumballMachine.setCount(countTemp);
}else{
break; //说明糖果机坏掉,停止运作
}
//每隔两秒采集一次数据
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} } }

扩展接口实现类代码如下:

 package pattern.proxy;

 import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject; public class GumballMachineMonitorImpl extends UnicastRemoteObject implements
GumballMachineMonitor { /**
*
*/
private static final long serialVersionUID = -6922251119304810189L; private GumballMachineBean gumballMachine; /**
* 因为父类UnicastRemoteObject的构造方法抛出了RemoteException异常
* 所以子类必须手动定义构造方法并抛出该异常,以防止异常链断掉
* @throws RemoteException
*/
protected GumballMachineMonitorImpl(GumballMachineBean gumballMachine) throws RemoteException {
super();
this.gumballMachine = gumballMachine;
} @Override
public GumballMachineBean getGumballMachineBean() throws RemoteException {
return gumballMachine; //获取糖果机状态对象
} /**
* 驱动函数
* @param args
*/
public static void main(String[] args) { try {
GumballMachineBean gmb =
new GumballMachineBean("湖北省武汉市武昌区八一路武汉大学文理学部自强超市旁", 1000, MachineState.RUNNING);
new Thread(new SellCandy(gmb)).start(); //开始销售
GumballMachineMonitorImpl gmm1 = new GumballMachineMonitorImpl(gmb);
Naming.rebind("GumballMachineMonitor_1", gmm1); GumballMachineBean gmb2 =
new GumballMachineBean("湖北省武汉市武昌区八一路武汉大学文理学部计算机学院软件工程国家重点实验室旁", 1000, MachineState.BREAKDOWN);
new Thread(new SellCandy(gmb2)).start(); //开始销售
GumballMachineMonitorImpl gmm2 = new GumballMachineMonitorImpl(gmb2);
Naming.rebind("GumballMachineMonitor_2", gmm2); GumballMachineBean gmb3 =
new GumballMachineBean("湖北省武汉市武昌区八一路武汉大学文理学部樱花大道旁", 1000, MachineState.REPAIR);
new Thread(new SellCandy(gmb3)).start(); //开始销售
GumballMachineMonitorImpl gmm3 = new GumballMachineMonitorImpl(gmb3);
Naming.rebind("GumballMachineMonitor_3", gmm3); } catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } }

  为了让自己的对象成为远程服务对象,需要让对象具备一些远程的功能,所以要继承UnicastRemoteObject类,由于UnicastRemoteObject类的构造方法抛出了RemoteException异常,为了不让异常链断裂,导出类的构造方法也必须抛出该异常,而这需要手动的去实现。getGumballMachineBean方法用于获取糖果机的状态。在驱动类中,我们创建了三个糖果机,分别将其状态设为:RUNNING、BREAKDOWN和REPAIR。然后也给每个糖果机添加了一些糖果,并“摆放”在三个不同的地方,然后启动线程开始销售。

  Naming.rebind("GumballMachineMonitor_1", gmm1);

  用于将当前服务对象注册到RMI registry的注册表中,因为客户端需要在注册表中查找来获取该远程对象,从而可以达到像操作本地对象一样操作远程对象的目的。

  第四步、通过rmic工具来产生Stub和Skeleton。rmic是JDK内的一个小工具,使用方法:通过命令行进入实现类class文件所在路径,执行命令“rmic 类全名(包名+类名,不需要‘.class’)”。执行完成之后,在类所在路径下会生成两个文件(类名+‘_Stub.class’ 和 类名+‘_Skeleton.class’),高版本的jdk一般只有"类名+‘_Stub.class’ "文件。

  第五步、执行rmiregistry。运用命令行,进入和上一步相同的路径下,执行命令"rmiregistry"。

  第六步、新开一个命令行窗口,执行驱动程序来启动服务。

  客服端程序:

  客户端程序(在本例中,也即是客户需要使用的监控窗口程序)需要查找RMI的注册表来获取远程服务对象所在地址,并获取到该服务对象,然后通过该对象来调用相关方法,对于客户而言,这一系列的操作就如同在操作本地服务对象一样,是透明的。唯一能感受到的就是网络的不可预测性导致的异常。

  第一步、根据注册的服务名查找服务得到实例的引用  

  第二步、通过获取到的远程服务对象来调用相关的方法

  相关代码如下:

 package pattern.proxy;

 import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException; public class ClientManageSystem { /**
* @param args
*/
public static void main(String[] args) {
try {
GumballMachineMonitor gmm1 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_1");
GumballMachineMonitor gmm2 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_2");
GumballMachineMonitor gmm3 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_3"); while(true){
System.out.println("GM_1_Location:" + gmm1.getGumballMachineBean().getLocaltion() + "\n" +
"GM_1_State:" + gmm1.getGumballMachineBean().getState() + "\n" +
"GM_1_Count:" + gmm1.getGumballMachineBean().getCount()); System.out.println("GM_2_Location:" + gmm2.getGumballMachineBean().getLocaltion() + "\n" +
"GM_2_State:" + gmm2.getGumballMachineBean().getState() + "\n" +
"GM_2_Count:" + gmm2.getGumballMachineBean().getCount()); System.out.println("GM_3_Location:" + gmm3.getGumballMachineBean().getLocaltion() + "\n" +
"GM_3_State:" + gmm3.getGumballMachineBean().getState() + "\n" +
"GM_3_Count:" + gmm3.getGumballMachineBean().getCount()); //每隔五秒监控一次
Thread.sleep(5000);
} } catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NotBoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } }

  GumballMachineMonitor gmm1 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_1");
  客户端通过lookup方法到RMI registry中查找远程服务对象,RMI registry返回的是Stub对象,然后对stub进行反序列化。

  在使用RMI的过程中,最易犯的三个错误:(摘自Head First 设计模式)

  1. 忘了在启动远程服务之前先启动rmiregistry(要用Naming.rebind()注册服务,rmiregistry必须是运行的)。
  2. 忘了让变量和返回值的类型成为可序列化的类型(这种错误无法在编译期发现,只会在运行时发现)
  3. 忘了给客户提供stub类

Java学习随笔——RMI的更多相关文章

  1. (。・・)ノ~个人java学习随笔记录

    基本认识 1.编程思维 根据这几天的java学习,编写程序最重要的就是要有一个清晰的思路.语法上的错误可以跟随着不断的联系与学习来弥补,清晰的思维却只有自己来前期模仿,后面慢慢摸索形成一套属于自己的思 ...

  2. JAVA 学习随笔 : JDK Enhancement Process JEP process

    是时候寻找一个学习JAVA的路径了 ---- JDK Enhancement Process Oracle发布了JDK增强提案与路线图进程,目的在于鼓励OpenJDK提交者贡献点子和扩展以改进Open ...

  3. java学习随笔--- 捣蛋vector

    最近比较有时间啦,有时间搞下java,个人觉得学这门语言语法太多啦,不一一去学习啦,心血来潮,挂了个struct2的源代码,一入深似海啊,看得我天花缭乱,从最简单的开始吧 public static ...

  4. Java学习随笔5:Java多线程编程

    1. 线程是程序中单独顺序的控制流,线程本身依靠程序进行运行,线程是程序中的顺序控制流,只能使用分配给程序的资源和环境. 2. 进程是执行中的程序,一个进程可以包含一个或多个线程,但至少要包含一个线程 ...

  5. Java学习随笔4:Java的IO操作

    1. IO流的作用是读写设备上的数据,如硬盘文件.内存.键盘.网络等.根据数据走向,可分为:输入流和输出流:根据处理的数据类型,可分为:字节流和字符流.字节流可以处理所有类型的数据,如MP3.图片.视 ...

  6. Java学习随笔3:遍历文件夹及文件的读取和写入

    import java.io.File; /** * 遍历文件夹 */ public class ScannerFile { public static void main(String[] args ...

  7. Java学习随笔2:Java复合赋值表达式的小问题

    问题:i += j只是i = i + j的简写么? 答案:非也!看下面的程序: int i = 5; long j = 8; i += j; // 可以通过编译且结果正确 i = i + j; // ...

  8. Java学习随笔1:Java是值传递还是引用传递?

    Java always passes arguments by value NOT by reference. Let me explain this through an example: publ ...

  9. JAVA学习随笔-.class

    类名.class是获得这个类所对应的Class实例.从面向对象的角度上来看,类也是对象,它们是类这个类对象,听起来有些抽象,但是在java中的实现就是所有的加载进来的类在虚拟机中都是一个java.la ...

随机推荐

  1. Selenium如何实现dropbar移动

    遇到这个拖拽的dropbar,如何实现呢,, 经过网上查找,可以用Action的方式实现或者js来控制 原理:移动按钮的同时,数字也随着变化 解决方法:1.最简单的就是直接在文本框输入相应的数字 2. ...

  2. java基础(九)面向对象(四)

    这里有我之前上课总结的一些知识点以及代码大部分是老师讲的笔记 个人认为是非常好的,,也是比较经典的内容,真诚的希望这些对于那些想学习的人有所帮助! 由于代码是分模块的上传非常的不便.也比较多,讲的也是 ...

  3. HDOJ(HDU) 2524 矩形A + B(推导公式、)

    Problem Description 给你一个高为n ,宽为m列的网格,计算出这个网格中有多少个矩形,下图为高为2,宽为4的网格. Input 第一行输入一个t, 表示有t组数据,然后每行输入n,m ...

  4. ZOJ 3761 Easy billiards 月赛E DFS

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3761 题目大意:给定n个位置的小球,小球的运动方向只能是水平或者 ...

  5. java对Ldap操作4

    applicationContext.xml <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE  ...

  6. java代码实现对excel加密、解密(设置或去除打开密码)

    使用jxcell组件来完成对excel加密.解密的功能. jxcell.jar[点击下载](此jar没有使用限制,你懂得) 具体代码如下: import java.io.IOException; im ...

  7. c++ 流基本概念

    前言 在程序经常实现数据的输入和输出,c++通过一种称为流(stream)的机制提供了更为精良的输入和输出方法.流是一种灵活且面向对象的I/o方法. 根据操作对象不同分为文件流.字符串流.控制台流. ...

  8. input标签上传图片怎么获取src;

    大家都知道input标签可以上传文件 如: <input type="file"/> 就可以上传文件,当然也可以上传图片,上传的图片的src地址如何取到: var re ...

  9. android 55

    智能家居:可以联网可以用指令操作可以返回状态.智能微波炉智能眼镜智能手表. Android作者Andy Rubin,2007年正式由谷歌推广,开源的. 安卓体系结构和开发一个闹钟的调用结构图: 安卓和 ...

  10. RPM制作

    http://blog.csdn.net/justlinux2010/article/details/9905425