RMI框架解析一
在实际应用中,为了合理的分配软硬件资源,会把各个对象分布在不同的网络节点上,这些对象之间能相互发送消息。
分布式对象模型的实现系统应该具备以下功能:
1) 把分布在不同节点上的对象之间发送的消息转换为字节序列,这一过程称为编组(marshalling)。
2) 通过套接字建立连接并且发送编组后的消息,即字节序列。
3) 处理网络连接或传输消息时出现的各种故障。
4) 为分布在不同节点上的对象提供分布式垃圾收集机制。
5) 为远程方法的调用提供安全检查机制。
6) 服务器端运用多线程或非阻塞通信机制,确保远程对象具有很好的并发性能,能同时为多个客户访问。
7) 创建与特定问题领域相关的各种本地对象和远程对象。
RMI框架封装了所有底层通信细节,并且解决了编组,分布式垃圾收集、安全检查和并发性等通用问题。有了现成的框架,开发人员就只需专注开发也特定问题领域相关的各种本地对象和远程对象。
RMI采用客户/服务器通信方式。在服务器上部署了各种服务的远程对象,客户端请求访问服务器上远程对象的方法。
与特定问题领域相关的各种本地对象和远程对象 |
分布式对象模型的框架的接口 |
分布式对象模型的框架的实现 |
Socket通信 |
TCP/IP网络 |
第三方提供 |
负责开发特定软件应用的开发人员创建 |
RMI框架采用代理来负责为客户与远程对象之间通过Socket进行通信的细节。
RMI框架为远程对象分别生成了客户端代理和服务器端代理。位于客户端的代理类称为存根(Stub),位于服务器端的代理类称为骨架(Skeleton)。
客户端 |
3.调用echo()方法
|
远程对象 |
1.调用本地存根的echo()方法
远程对象的的骨架 |
远程对象的存根 |
4.发送被编组的返回值或异常
存根采用一种与平台无关的编码方式,把方法参数编码为字节序列,这个编码过程为参数编组。
RMI主要采用Java序列化机制参数编组。接着,存根把以下请求信息发送给服务器:
为访问的远程对象的名字。
被调用的方法的描述
编组后的参数的字节码序列
服务器端接收到客户端的请求信息,然后由相应的骨架对象来处理这一请求信息,骨架对象执行以下操作:
反编组参数,即把参数的字节序列反编码为参数。
定位要访问的远程对象。
调用远程对象的相应方法。
获取方法调用产生的返回值或者异常,然后对它进行编组。
把编组后的换回值或者异常发送给客户。
大致说来,创建一个RMI应用包括以下步骤:
1) 创建远程接口:继承java.rmi.Remote接口
2) 创建远程类: 实现远程接口
3) 创建服务器程序:负责在rmiregistry注册表中注册远程对象。
4) 创建客户程序:负责定位远程对象,并且调用远程对象的方法。
创建远程接口:
远程接口中声明了可以被客户程序访问的远程方法。RMI规范要求远程对象所属的类实现一个远程接口,并且远程接口符合以下条件:
1) 直接或间接继承java.rmi.Remote接口。
2) 接口中所有方法声明抛出java.rmi.RemoteException。
创建远程类:
远程类就是远程对象所属的类。RMI规范要求远程类必须实现一个远程接口。
此外,为了使远程类的实例变成能为远程客户提供服务的远程对象,可通过以下两种途径之一把它导出export为远程对象。
1) 使远程类继承java.rmi.server.UnicastRemoteObject类,并且远程类的构造子抛出RemoteException。这也是最常用的方式。
2) 导出为远程对象的第二种方式:如果一个远程类已经继承了其他类,无法再继承UnicastRemoteObject类,那么可以在构造方法中调用UnicastRemoteObject类的静态exportObject()方法,同样,远程类的构造方法也必须声明为抛出RemoteException。在其构造方法中调用UnicastRemoteObject.exportObject(this,0)方法,将自身导出为远程对象。
创建服务程序:
RMI采用一种命名服务机制来使得客户程序可以找到服务器上的一个远程对象。在JDK的安装的bin子目录相爱有一个rmiregistry.exe程序,它是提供命名服务的注册表程序。
服务器程序的一大任务就是向rmiregistry注册表注册远程对象。从JDK1.3API被整合到JNDI中。
在JNDI中,javax.naming.Context接口声明了注册、查找、以及注销对象的方法。
bind(String name, Object obj),注册对象,把对象与一个名字绑定。如果该名字已经与其它对象绑定,就会抛出NameAlreadyBoundException。
remind(String name, Object obj):注册对象,把对象与一个名字绑定。如果该名字已经与其它对象绑定,不会抛出NameAlreadyBoundException,而是把气昂前参数obj指定的对象覆盖掉原先的对象。
lookup(String name):查找对象,返回与参数name指定的名字所绑定的对象。
unbind(String name):注销对象,取消对象与名字的绑定。
远程对象工厂设计模式:
rmiregistry注册表只能用来注册少量的远程对象,已完成自举服务。
如果把所有的远程对象都注册到rmiregistry注册表,有以下缺点:
第一、 要保证每个远程对象具有惟一的名字具有一定的难度。
第二、 不管客户是否会访问某个远程对象,都必须事先创建它,有可能在服务器运行的生命周期中,有些远程对象从来没有被客户访问过。服务器上事先创建这些远程对象,并且在注册表中注册它们,白白浪费了服务器的资源。
远程对象工厂的设计模式。客户程序从rmiregistry注册表中找到一个负责创建和查找其它远程对象的工厂对象,然后就可以由它来得到其它远程对象,工厂对象本身当然也是远程对象。
注册表 |
创建或查找其它的远程对象 |
工厂对象 |
客户 |
远程对象 |
远程对象 |
package com.unmi.flight;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Flight extends Remote {
public String fly() throws RemoteException;
}
package com.unmi.flight;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class FlightImpl extends UnicastRemoteObject implements Flight {
private static final long serialVersionUID = 1L;
private String name;
public FlightImpl(String name) throws RemoteException {
this.name = name;
}
public String fly() throws RemoteException {
return "flight fly";
}
}
package com.unmi.flight;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface FlightFactory extends Remote {
public Flight getFlight(String name) throws RemoteException;
}
package com.unmi.flight;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Hashtable;
public class FlightFactoryImpl extends UnicastRemoteObject implements
FlightFactory {
protected Hashtable<String, Flight> flights; // 存放Flight对象的缓存
public FlightFactoryImpl() throws RemoteException {
flights = new Hashtable<String, Flight>();
}
public Flight getFlight(String name) throws RemoteException {
Flight flight = flights.get(name);
if (flight != null) return flight;
flight = new FlightImpl(name);
flights.put(name, flight);
return flight;
}
}
package com.unmi.flight;
import java.rmi.registry.LocateRegistry;
import javax.naming.InitialContext;
public class FlightServer {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
InitialContext ctx = new InitialContext();
ctx.bind("rmi:factory", new FlightFactoryImpl());
ctx.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.unmi.flight;
import javax.naming.InitialContext;
public class FlightClient {
public static void main(String[] args) {
try {
InitialContext ctx = new InitialContext();
FlightFactory factory = (FlightFactory)ctx.lookup("rmi:factory");
FlightFactory factory2 = (FlightFactory)ctx.lookup("rmi:factory");
System.out.println(factory == factory2);
Flight flight1 = (Flight)factory.getFlight("flight1");
Flight flight2 = (Flight)factory.getFlight("flight1");
System.out.println(flight1 == flight2);;
} catch (Exception e) {
e.printStackTrace();
}
}
}