之前看过一个RMI的简单示例。这篇文章简单的分析一下RMI的原理。
RMI应用程序的体系结构
上一个例子中,我们编写了两个程序,一个服务端(Server)和一个客户端(Client)。
- 在Server内创建一个远程对象(CalcServiceImpl),并生成存根,注册到Registry中。
- 客户端请求远程对象,并调用对象的方法。
- Transport Layer:RMI传输层实现网络连接与通信的数据传递。
- Stub:存根是远程对象在客户端的代理,它驻留在客户端系统中,充当客户端程序的网关。
- Skeleton:骨架驻留在服务端系统中,Stub通过Skeleton传递参数给远程服务对象。
- RRL(Remote Reference Layer):RRL是负责管理客户端到远程对象的引用。
RMI的工作原理
- 当客户端调用远程对象时,它会被Stub接收,并把请求传递给RRL
- 当客户端RRL接收到请求后,它会调用RemoteRef的invoke()方法。最终这个请求会被传递到服务端的RRL中。
- 服务端RRL将请求发给Skeleton(服务端的一个代理类),最终调用服务端中的服务对象方法。
- 服务方法返回的结果按原路返回给客户端。
Marshalling与Unmarshalling
当客户端调用远程对象的方法时,可能需要传递一些参数。这些参数将会被包装在消息中通过网络发送给服务端。这些参数会被序列化成二进制数据流传输到服务端,这个过程也被叫做Marshalling;相反服务端需要把二进制数据流反序列化还原,这个过程也被叫做Unmarshalling。
事实上这两个过程使用的使我们非常熟悉的ObjectOutputStream和ObjectInputStream。只不过,RMI进一步包装了这两个类:
这两个过程之间涉及到的消息通信协议,也被称为JRMP协议,Java Remote Message Protocol
RmiRegistry的作用
RmiRegistry用于管理所有的远程服务。
服务器端创建对象时,都会使用Registry.bind
或Registry.rebind
方法向RmiRegistry注册对象。每个服务对象都对应着一个唯一的服务名。
客户端调用远程对象时,客户端需要远程对象的引用。客户端可以根据服务名调用Registry.lookup
方法从RmiRegistry中获取对象。
分析Stub中的源代码
在上面的体系架构中,提到了Stub与Skeleton,但是在程序中我们并没有看到这两个类,那是因为从Java 5.0之后,Java使用java.lang.reflect.Proxy
为我们动态生成了这两个类。
service.getClass()
打印得到的是:class com.sun.proxy.$Proxy0
在Java 5.0之前需要我们使用rmic
命令根据服务实现类编译生成存根。
# -keep选项:保留生成的源代码
rmic -keep cn.hff.service.CalcServiceImpl
生成源码的Stub对象如下:
// Stub class generated by rmic, do not edit.
// Contents subject to change without notice.
package cn.hff.service;
public final class CalcServiceImpl_Stub extends java.rmi.server.RemoteStub implements cn.hff.service.ICalcService {
private static final long serialVersionUID = 2;
private static java.lang.reflect.Method $method_add_0;
private static java.lang.reflect.Method $method_minus_1;
static {
try {
// 获取add方法
$method_add_0 = cn.hff.service.ICalcService.class.getMethod("add",
new java.lang.Class[] { int.class, int.class });
// 获取minus方法
$method_minus_1 = cn.hff.service.ICalcService.class.getMethod("minus",
new java.lang.Class[] { int.class, int.class });
} catch (java.lang.NoSuchMethodException e) {
throw new java.lang.NoSuchMethodError("stub class initialization failed");
}
}
// constructors
// 创建CalcServiceImpl_Stub时需要指定RemoteRef
// RemoteRef就是远程对象的引用
public CalcServiceImpl_Stub(java.rmi.server.RemoteRef ref) {
super(ref);
}
// methods from remote interfaces
// implementation of add(int, int)
public int add(int $param_int_1, int $param_int_2) throws java.rmi.RemoteException {
try {
/**
* @param obj:包含RemoteRef的存根对象
* @param method:被调用的方法
* @param params:方法的参数
* @param opnum:被调方法的哈希值
* @return result:返回结果
*/
Object $result = ref.invoke(this, $method_add_0,
new java.lang.Object[] { new java.lang.Integer($param_int_1), new java.lang.Integer($param_int_2) },
-7734458262622125146L);
return ((java.lang.Integer) $result).intValue();
} catch (java.lang.RuntimeException e) {
throw e;
} catch (java.rmi.RemoteException e) {
throw e;
} catch (java.lang.Exception e) {
throw new java.rmi.UnexpectedException("undeclared checked exception", e);
}
}
// implementation of minus(int, int)
public int minus(int $param_int_1, int $param_int_2) throws java.rmi.RemoteException {
try {
Object $result = ref.invoke(this, $method_minus_1,
new java.lang.Object[] { new java.lang.Integer($param_int_1), new java.lang.Integer($param_int_2) },
6403391039451205508L);
return ((java.lang.Integer) $result).intValue();
} catch (java.lang.RuntimeException e) {
throw e;
} catch (java.rmi.RemoteException e) {
throw e;
} catch (java.lang.Exception e) {
throw new java.rmi.UnexpectedException("undeclared checked exception", e);
}
}
}
RemoteRef的实现类UnicaseRef的源码可以点击这里查看。