一个 RemoteObject 在 export 后在 RMI Server 端持有一个 UnicastServerRef 对象(真正被 export 的 Proxy 对象持有的是 UnicastRef 对象),在 RMI Registry 端保存的是 Proxy 对象,RMI Client 通过 RMI Registry lookup 获取到 Proxy 对象,在 RMI Client 端对 RemoteObject 的操作,最终都通过 RemoteObjectInvocationHandler 委托给 UnicastRef#public Object invoke(Object proxy, Method method, Object[] args)。
UnicastRef 和 UnicastServerRef
UnicastRef 提供了invoke 方法,UnicastServerRef提供了 exportObject 方法,在 RMI Client 端,Proxy 远程对象持有一个 UnicastRef 对象,一个 UnicastRef 持有一个 LiveRef 对象;在 RMI Server 端,一个 RemoteObject 持有一个 UnicastServerRef 对象, UnicastServerRef 持有一个 LiveRef 对象(一个 remote object reference = LiveRef = Endpoint(host:port)/ObjID)。LiveRef 持有 Endpoint 对象,Endpoint 对象有两个方法
/**
* Returns the transport for incoming connections to this endpoint.
**/
Transport getInboundTransport();
/**
* Returns transport for making connections to remote endpoints.
**/
Transport getOutboundTransport();
Transport getOutboundTransport() 方法在 RMI Client 调用,获取到 Transport 之后,通过 Trasport#getChannel 获取到 TCPChannel 对象,最后通过 TCPChannel#newConnection() 方法获取到 Connection 对象,对于 TCPConnection 来说,一个 TCPConnection 对象持有一个 Socket 对象,及通过 Socket 获取到的 InputStream 和 OutputStream;
Transport 层的这些对象的构造方法里,只传port的就是给服务器端用的;
TCPTransport 里的 getChannel 是给 RMI Client 用的?(从调用关系上看,此方法最终是通过 UnicastRef#invoke#newCall#free 三个方法调用,都是在 RMI Client 才会调用的;)其他的都是给 RMI Server 用的?
那么 exportObject 返回的对象,如何有这些能力的呢,发布对象的时候,我们需要给 UnicastRemoteObject#exportObject 传入远程对象和端口号,
UnicastRemoteObject 会将传入的信息 new 一个 UnicastServerRef2 对象,然后将发布对象的工作委托给 UnicastServerRef2 对象的 exportObject 方法;
在 UnicastServerRef2 中,host+port 用来构建 LiveRef 对象(根据 host + port 构建 endpoint + ObjID -> liveref),然后将发布对象的工作委托给 LiveRef;LiveRef委托给Endpoint,Endpoint委托给 transport,transport里创建 ServerSocket;
为什么拿到了 RMI Server 发布的远程对象的序列化的反序列化对象之后,就能跟 RMI Server 上远程对象通过 Socket 通信了呢?
export的远程对象并不是开发者直接传入的这个对象,
RemoteHello remoteHello = new RemoteHelloImpl();
RemoteHello stub = (RemoteHello) UnicastRemoteObject.exportObject(remoteHello, 4000);
比如上面这段代码,这个 stub 是 remoteHello(RemoteHelloImpl类的实例),通过JDK动态代理之后的对象,那么动态代理给其加了什么功能呢?
分析 UnicastServerRef # exportObject 得知,exportObject 返回的对象,是使用 JDK 动态代理创建的对象,从 RemoteObjectInvocationHandler 可以看出,这个代理对象的能力是,将对这个代理对象的方法调用,通过 RemoteObject 持有的 UnicastRef 对象的 invoke 方法启动本地 Socket 与远程对象的交互,从而得到远程对象的执行结果。怎么实现这个目标的?
构建代理对象时,先构建一个 UnicastRef 对象(客户端的远程对象引用),这时候注意:
这是 UnicastServerRef 的方法
protected RemoteRef getClientRef() {
return new UnicastRef(ref);
}
这是 UnicastServerRef2 的方法
protected RemoteRef getClientRef() {
return new UnicastRef2(ref);
}
他们在构建 UnicastRef 对象时使用了对应的 UnicastServerRef 对象的 LiveRef 对象(这个对象持有的就是远程的对象的位置信息,endpoint=objid),LiveRef 对象的序列化和返序列化直接调用了其持有的Endpoint 和ObjID的信息,看一下 Endpoint 的信息如下,就是host和port信息;
/**
* Write endpoint to output stream.
*/
public void write(ObjectOutput out) throws IOException {
if (csf == null) {
out.writeByte(FORMAT_HOST_PORT);
out.writeUTF(host);
out.writeInt(port);
} else {
out.writeByte(FORMAT_HOST_PORT_FACTORY);
out.writeUTF(host);
out.writeInt(port);
out.writeObject(csf);
}
}
/**
* Get the endpoint from the input stream.
* @param in the input stream
* @exception IOException If id could not be read (due to stream failure)
*/
public static TCPEndpoint read(ObjectInput in)
throws IOException, ClassNotFoundException
{
String host;
int port;
RMIClientSocketFactory csf = null;
byte format = in.readByte();
switch (format) {
case FORMAT_HOST_PORT:
host = in.readUTF();
port = in.readInt();
break;
case FORMAT_HOST_PORT_FACTORY:
host = in.readUTF();
port = in.readInt();
csf = (RMIClientSocketFactory) in.readObject();
if (csf != null && Proxy.isProxyClass(csf.getClass())) {
throw new IOException("Invalid SocketFactory");
}
break;
default:
throw new IOException("invalid endpoint format");
}
return new TCPEndpoint(host, port, csf, null);
}
到这里,代理对象,通过持有带有远程对象位置信息(LiveRef)的 UnicastRef 对象,已经可以在未来被某个 RMI Client 获取到的时候,可以反过来 export 此对象的 RMI Server 上的此对象发起 Socket 连接了。RemoteObjectInvocationHandler 将 method.invoke 委托给 UnicastRef#invoke 方法发起远程通信;走时序图里的流程。
试一下直接使用 UnicastServerRef2 是否可以将多个对象发布到一个端口?
Registry 接口的实现为 RegistryImpl,其通过继承 java.rmi.server.RemoteServer ,间接实现了 java.rmi.Remote 接口,所以 RegistryImpl 是一个远程对象,sun.rmi.registry.RegistryImpl_Stub 和 sun.rmi.registry.RegistryImpl_Skel 为使用 RMIC 工具生成的存根和骨架类。
Registry 一共由5个方法, bind ,unbind和rebind是 RMI Server 用来注册和解绑远程对象的;list 和 lookup 是 RMI Client 用来查找远程对象的。
此段的主要逻辑在 RegistryImpl_Stub#void bind(java.lang.String $param_String_1, java.rmi.Remote $param_Remote_2) 方法中,
out.writeObject(name);
out.writeObject(Remote);
RMI Registry 接收到调用之后的逻辑在 RegistryImpl_Skel@dispatch(java.rmi.Remote obj, java.rmi.server.RemoteCall remoteCall, int opnum, long hash) 方法中,
name = SharedSecrets.getJavaObjectInputStreamReadString().readString(in);
Remote = (java.rmi.Remote) in.readObject();
其从流中读出 name 和 Remote 对象;然后调用 RegistryImpl#bind,将 name->Remote 放到其持有的全局静态变量中:
private Hashtable bindings
= new Hashtable<>(101);
会写 TransportConstants.NormalReturn ,调用结束(这个逻辑在,StreamRemoteCall#getResultStream 方法中)
name = SharedSecrets.getJavaObjectInputStreamReadString().readString(in);
java.rmi.Remote remote = RegistryImpl.lookup($param_String_1);
try {
java.io.ObjectOutput out = call.getResultStream(true);
out.writeObject($result); 注意这里,将整个对象序列化过去;
} catch (java.io.IOException e) {
throw new java.rmi.MarshalException("error marshalling return", e);
}
ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
endpoint.getClientSocketFactory(),
endpoint.getServerSocketFactory());
ch = new TCPChannel(TCPTransport.this, ep);
conn = new TCPConnection(ch, socket, bufIn, bufOut);