RMI 源码解读

很多时候,身边那些对学习有一定方法的人都会这样对我说:不必深入源码的每个细节,知道大概就行了。但每次按照这个方式学习,我都会觉得内心无比空虚,觉得懂了,却又总觉得不太懂!所以今天我想继续深入探究大神编写RMI的真实思路,以及其中用到的知识技巧。下面我们用一句代码来打开Java RMI世界的大门。(以下均为个人整理,如有错误,还望指正!)

Registry registry = LocateRegistry.createRegistry(1099);

这段代码将会创建一个Registry对象。Registry是一个继承了Remote接口的接口,个人认为这就是一个注册工具,通过它我们可以向注册中心注入相应的远程服务。Registry的体系结构如下所示:
RMI 源码解读_第1张图片
代码LocateRegistry.createRegistry(1099)的主要作用就是创建RegistryImpl对象(new RegistryImpl(1099))。RegistryImpl类中的HashTable类型的bingings用于存放远程对象。另外该类中的RmoteRef类型的ref对象(这个属性定义在RemoteObject类)用于存放UnicastServerRef对象,其包装了LiveRef。下面我们看一下RegistryImpl类的构造方法,如下图所示:
RMI 源码解读_第2张图片
由图可知,RegistryImpl的构造方法主要做了以下几件事:

  • 创建LiveRef对象,该对象包含有TCPEndpoint
  • 创建UnicastServerRef对象,该对象包含上面创建的LiveRef对象
  • 调用RegistryImpl类的setup方法,该方法接收上面创建的UnicastServerRef对象
    这里暂且不继续展开,先看一下LiveRef类的结构,如下图所示:
    RMI 源码解读_第3张图片
    下面我们看一下LiveRef类和UnicastServerRef类的继承体系结构,具体如下图所示:
    RMI 源码解读_第4张图片
    下面我们看一下RegistryImpl类的setup方法,该方法的具体代码如下所示:
    RMI 源码解读_第5张图片
    该方法先保存UnicastServerRef对象到RegistryImpl对象的ref属性中。接着调用UnicastServe-rRef类的exportObject方法,该方法接收了三个参数:Remote对象、Object对象、boolean类型的值。由于RegistryImpl类通过继承体系继承了Remote接口,所以这里传递给exportObject方法的Remote对象就是RegistryImpl对象。
    下面我们继续看UnicastServerRef类的exportObject方法。这个方法主要做了以下几件事:
  • 获取Remote对象的实际类型
  • 创建RegistryImpl_Stub对象,此处本质上就是通过反射方式创建的RegistryImpl_Stub对象(具体步骤是:组装RegistryImpl_Stub全路径,加载该类,如果加载到,就继续创建RegistryImpl_Stub类型的对象,创建时接收RemoteRef类型的参数,而这个对象来源于UnicastServerRef对象中的ref属性的包装对象——new
    UnicastRef(ref),ref实际就是创建UnicastServerRef时传递进去的LiveRef对象)
  • 判断上一步创建的RegistryImpl_Stub是否是RemoteStub类型,如果是则创建RegistryImpl_Skel对象,其创建过程和上一步类似
  • 接下来创建Target对象,该对象持有RegistryImpl对象、UnicastSereverRef对象、RegistryImpl_Stub对象、LiveRef对象的ObjID对象、boolean类型的值
  • 接着调用UnicastSereverRef类的LiveRef对象的exportObject()方法,该方法接收一个Target类型的参数
    下图是UnicastServerRef类的exportObject方法的实际代码(上面这段就是参照下图代码翻译的):
    RMI 源码解读_第6张图片
    至此我们总结以下,具体如下图所示:
    在这里插入图片描述
    下面让我们继续看LiveRef类的exportObject方法。该方法代码比较简单,仅一行,具体如下图所示:
    在这里插入图片描述
    这段代码就是调用LiveRef对象持有的TCPEndpoint对象的exportObject()方法。TCPEndpoint类的exportObject()方法的代码如下所示:
    RMI 源码解读_第7张图片
    该段代码也是很简单,继续调用TCPEndpoint对象持有的TCPTransport对象的exportObject()方法。TCPTransport类的exportObject()方法的代码如下图所示:
    RMI 源码解读_第8张图片
    这段代码中有两个重要的地方listen()方法和super.exportObject()方法,listen()方法主要启动一个线程监听1099端口(不指定端口的情况下是这个),super.exportObject()方法则主要是向ObjectTable中添加Target对象,具体代码如下所示:
    RMI 源码解读_第9张图片
    下面我们看一下TCPTransport类的listen()方法,它是一个私有方法,其详细代码如下图所示:
    RMI 源码解读_第10张图片
  • 取TCPEndpoint对象。这个值是从TCPTransport类中LinkedList类型的epList中拿到的,而epList是在创建LiveRef对象时,由TCPEndpoint.getLocalEndpoint(port)操作创建的,最后这个对象会传递给TCPTransport对象,具体代码如下所示:
    RMI 源码解读_第11张图片
    这里我们再看一下TCPTransport类和TCPEndpoint类(该类持有TCP将要监听的地址,要监听的端口,创建ServerSocket对象的工厂类)的结构,具体如下图所示:
    RMI 源码解读_第12张图片
  • 调用TCPEndpoint对象的newServerSocket()方法来创建ServerSocket对象。该方法实际上就是调用TCPDirectSocketFactory类的createServerSocket(listenPort)方法,该方法就一句代码:new
    ServerSocket(port)。注意:TCPTransport类默认会创建RMISocketFactory对象(defaultSocketFactory),是通过调用RMISocketFactory.getDeafaultSocketFactory()方法完成的(先判断RMISocketFactory类中的defaultSocketFactory属性是否为空,如果为空则直接new
    sun.rmi.transport.tcp.TCPDirectSocketFactory())。下面看一下TCPEndpoint类的newTransport()方法的代码,如下图所示:
    RMI 源码解读_第13张图片
  • 接着创建NewThreadAction对象,其实现了PrivilegedAction接口,并指定泛型为Thread类。该类是一个由final修饰的最终类。创建NewThreadAction对象时,会指定一个实现了Runnable接口的AcceptLoop对象。创建完NewThreadAction对象后,会调用AccessorController类的doPrivileged(NewThreadAction对象)方法,其具体调用过程如下图所示:
    RMI 源码解读_第14张图片
    接下来我们看一下NewThreadAction类的run()方法代码,其具体代码如下所示:
    RMI 源码解读_第15张图片
    这段代码会返回一个Thread对象,即该方法会返回一个线程,该线程持有一个Runnable对象——AcceptLoop(接收一个ServerSocket对象作为参数),最后启动这个线程。下面我们看看NewThreadAction类和AcceptLoop类的结构,如下所示:
    RMI 源码解读_第16张图片
    从上面的步骤我们可以得出这样几个结论:NewThreadAction类的主要作用就是创建线程启动ServerSocket监听。AcceptLoop接口就是被启动的ServerSocket监听线程。AcceptLoop类的主要作用就是持续监听客户端的连接。这里主要看一下AcceptLoop# executeAcceptLoop()方法的代码:
    RMI 源码解读_第17张图片
    来看一下connectionThreadPool.execute(new ConnectionHandler(socket, clientHost))这段代码,用线程池的方式启动一个线程ConnectionHandler来执行客户端连接。ConnectionHandler类的结构体系如下所示:
    在这里插入图片描述
    我们主要看一下ConnectionHandler类的run0()方法,这个方法是被ConnectionHandler类的run()方法调用的。run0()方法的具体代码如下所示:
    RMI 源码解读_第18张图片
    该方法首先设置Socket对象的一些属性。接着创建DataInputStream对象(该类的作用是允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据),并读取其中的魔数(dataInputStream#readInt(),占4个字节)和版本号(dataInputStream#readShort(),占2个字节),然后判断魔数和版本号是否合法(魔数默认值为1246907721,版本默认值为2)。紧接着会再读取1个字节的数据,表示协议,默认只处理以下几个协议:
  • public static final byte StreamProtocol = 0x4b; /** Connection uses
    stream protocol -连接使用流协议*/
  • public static final byte SingleOpProtocol = 0x4c; /** Protocol for
    single operation per connection; no ack required -每个连接的单操作协议;不需要ack
    */
  • public static final byte MultiplexProtocol = 0x4d; /** Connection
    uses multiplex protocol -连接使用多路复用协议 */

如果客户端使用的协议符合要求(StreamProtocol或者SingleOpProtocol),那么接下来会将客户端连接(Socket)、客户端IP、RMIClientSocketFactory及RMIClientSocketFactory包装为一个TCPEndpoint对象;将当前TCPTransport对象及刚创建的TCPEndpoint对象包装为TCPChannel对象,最后将TCPChannel对象、客户端Socket、输入流、输出流包装为TCPConnection对象。然后调用TCPTransport类的handleMessage(TCPConnection, boolean persistent)方法来处理客户端请求。该方法的具体代码如下所示:
RMI 源码解读_第19张图片
TCPTransport#handleMessage()方法可以处理的操作类型有:

  • public static final byte Call = 0x50; /** RMI call - RMI调用*/
  • public static final byte Ping = 0x52; /** Ping operation - Ping操作*/
  • public static final byte DGCAck = 0x54; /** Acknowledgment for
    distributed GC -分布式GC的确认*/
  • public static final byte PingAck = 0x53; /** Acknowledgment for Ping
    operation - Ping操作确认(它与Ping配合使用)*/
  • public static final byte Return = 0x51; /** RMI return -
    RMI返回(它与Call配合使用)*/

继续看serviceCall(final RemoteCall call)方法,其代码如下图所示:
RMI 源码解读_第20张图片
上图展示了disp及call的来源。其中disp取自Target对象,而Target对象来源于下面这段代码:ObjectTable.getTarget(new ObjectEndpoint(id, transport)),也就是说Target对象来源于ObjectTable类(ObjectTable中的这个对象来源于上面讲到的super.exportObject()处)。下面一起看一下Dispatcher接口实现类中的dispatcher()方法,这里的实现类是UnicastServerRef,所以这里所讲的实现类中的dispatcher()方法就是UnicastServerRef中的(前面有讲过UnicastServerRef的继承结构,从中可以知道该类实现了Dispatcher接口),由于这个方法的代码比较多,这里就不在全部贴出,直接给出关键部分,如下图所示:
RMI 源码解读_第21张图片
通过翻阅RegistryImpl_Skel类的dispatch()方法的源码可以知道,其完成以下几个操作:远程服务绑定操作、远程服务列表查询操作、远程服务单个查询操作、远程服务重绑定操作、远程服务解绑操作。这些操作最终调用的就是RegistryImpl类中的各个操作方法,比如bind()方法、list()方法、lookup()方法、rebind()方法、unbind()方法。通过上面的讲解,我们可以梳理出RMI服务端对服务进行处理的流程如下图所示:
RMI 源码解读_第22张图片

你可能感兴趣的:(Java,RMI,java,服务器,网络)