远程服务RMI源码解析(一)

时间:2022-10-31 16:56:35

Java远程方法调用,即JavaRMI(Java Remote Method Invocation),是Java编程语言里一种用于实现远程过程调用的应用程序编程接口。它使客户机上的运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能地简化远程接口对象的使用。

java RMI极大地依赖于接口。在需要创建一个远程对象时,程序员通过传递一个接口来隐藏底层的实现细节。客户端得到的远程对象句柄正好与本地的根代码链接,由后者负责透过网络通信。这样一来,程序员只需关心如何通过自己的接口句柄发送消息。

RMI

在Spring中,同样提供了对RMI的支持,使得在Spirng下的RMI开发变得更方便,具体使用RMI的例子,我们在此就不做介绍了,有兴趣的可以百度:RMI实例。我们一起来分析一下Spring对RMI功能的实现原理

服务端实现

在服务端,我们配置Spring的时候需要定义两个bean,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 服务端 -->
<bean id="helloRMIServiceImpl" class="org.tarena.note.service.impl.HelloRMIServiceImpl"/>
<!-- 将类为一个RMI服务 -->
<bean id="myRMI" class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- 服务类 -->
<property name="service" ref="helloRMIServiceImpl"/>
<!-- 服务名 -->
<property name="serviceName" value="helloRMI"/>
<!-- 服务接口 -->
<property name="serviceInterface" value="org.tarena.note.service.HelloRMIService"/>
<!-- 服务端口 -->
<property name="registryPort" value="1099"/>
</bean>
</beans>

配置文件是Spring的核心,在配置文件中我们可以看到,定义了两个bean,其中一个是对接口实现类的发布,而另一个则是对RMI服务的发布,使用
org.springframework.remoting.rmi.RmiServiceExporter
类进行封装,其中包括了服务类,服务名,服务接口,服务端口等若干属性,因此我们可以断定,
org.springframework.remoting.rmi.RmiServiceExporter
类应该是发布RMI的关键类。我们从此类进行分析。

根据示例,启动Spring中的RMI服务并没有多余的操作,仅仅是开启Spring的环境:读取xml文件,于是我们分析可能是

RmiServiceExporter
这个类初始化的时候做了某些操作完成了端口的发布功能,我们看下这个类的结构:
public class RmiServiceExporter extends RmiBasedExporter    implements InitializingBean, DisposableBean
RmiServiceExporter实现了Spring中几个比较敏感的接口:<pre name="code" class="java">InitializingBean,<pre name="code" class="java">DisposableBean

 
其中,DisposableBean接口保证在实现该接口的bean销毁时调用其destroy方法,InitializingBean接口则是保证在实现该接口的bean初始化时调用其afterPropertiesSet方法,所以我们推断RmiServiceExporter的初始化函数入口一定在其afterPropertiesSet方法中。经过查看代码,确认afterPropertiesSet为RmiServiceExporter功能的初始化入口。 
public void afterPropertiesSet()
throws RemoteException
{
prepare();
}

public void prepare()
throws RemoteException
{
  //检查验证service
checkService();
if(serviceName == null)
throw new IllegalArgumentException("Property 'serviceName' is required");
//如果用户在配置文件中配置了clientSocketFactory或者serverSocketFactory的处理
/*
*如果配置中的clientSocketFactory同时又实现了RMIServerSocketFactory接口那么会忽略配置
*中的serverSocketFactory而使用clientSocketFactory代替
*/
 if(clientSocketFactory instanceof RMIServerSocketFactory)
serverSocketFactory = (RMIServerSocketFactory)clientSocketFactory;
//clientSocketFactory和serverSocketFactory要么同时出现,要么都不出现
 if(clientSocketFactory != null && serverSocketFactory == null || clientSocketFactory == null && serverSocketFactory != null)
throw new IllegalArgumentException("Both RMIClientSocketFactory and RMIServerSocketFactory or none required");
//如果配置中的registryClientSocketFactory同时实现了RMIServerSocketFactory接口那么会忽略配置张的
//registryServerSocketFactory而使用registryClientSocketFactory代替
 if(registryClientSocketFactory instanceof RMIServerSocketFactory)
registryServerSocketFactory = (RMIServerSocketFactory)registryClientSocketFactory;
//不允许出现只配置registryServerSocketFactory而不配置registryClientSocketFactory的情况
 if(registryClientSocketFactory == null && registryServerSocketFactory != null)
throw new IllegalArgumentException("RMIServerSocketFactory without RMIClientSocketFactory for registry not supported");
createdRegistry = false;
//确定RMI registry
 if(registry == null)
{
registry = getRegistry(registryHost, registryPort, registryClientSocketFactory, registryServerSocketFactory);
createdRegistry = true;
}
 //初始化以及缓存到处的Object
 //此时通常情况下是使用RMIInvocationWrapper封装的JDK代理类,切面为RemoteInvocationTraceInterceptor
 exportedObject = getObjectToExport();
        if(logger.isInfoEnabled())
            logger.info((new StringBuilder()).append("Binding service '").append(serviceName).append("' to RMI registry: ").append(registry).toString());
        //Export RMI object
        if(clientSocketFactory != null)
            //使用给定的套接字工厂指定的传送方式导出远程对象,以便能够接收传入的调用
            //clientSocketFactory:进行远程对象调用的客户端套接字工厂
            //serverSocketFactory:接收远程调用的服务端套接字工厂
            UnicastRemoteObject.exportObject(exportedObject, servicePort, clientSocketFactory, serverSocketFactory);
        else
            //导出remote object,以使它能够接收特定端口的调用
            UnicastRemoteObject.exportObject(exportedObject, servicePort);
        try
        {
            //绑定服务名称到remote object,外界调用serviceName的时候会被erportObject接收
            if(replaceExistingBinding)
                registry.rebind(serviceName, exportedObject);
            else
                registry.bind(serviceName, exportedObject);
        }
        catch(AlreadyBoundException ex)
        {
            unexportObjectSilently();
            throw new IllegalStateException((new StringBuilder()).append("Already an RMI object bound for name '").append(serviceName).append("': ").append(ex.toString()).toString());
        }
        catch(RemoteException ex)
        {
            unexportObjectSilently();
            throw ex;
        }
    }

果然,在afterProperties函数中将实现委托给了prepare,而在prepare方法中我们找到了RMI服务发布的功能实现,同时,我们也大致清楚了RMI服务发布的流程。

(1)验证service

此处的service对应的是配置中类型为RMIServiceExporter的service属性,它是实现类,并不是接口。尽管后期会对RMIServiceExporter做一系列的封装,但是,无论怎么封装,最终还是会将逻辑引向至RMIServiceExporter来处理,所以,在发布之前需要进行验证。

(2)处理用户自定义的SocketFactory属性。

在RMIServiceExporter中提供了4个套接字工厂配置,分别是clientSocketFactory,serverSocketFactory和registryClientSocketFactory,registryServerSocketFactory。那么这两队配置又有什么区别或者说分别是应用在什么样的不同场景呢?

registryClientSocketFactory于registryServerSocketFactory用于主机与RMI服务器之间连接的创建,也就是当使用LocateRegistry.createRegistry(registryProt,clientSocketFactory,serSocketFactory)方法创建Registry实例时会在RMI主机使用serverSocketFacotry创建套接字等待连接,而服务端驭RMI主机通信会使用clientSocketFactory创建连接套接字。

clientSocketFactory,serverSocketFactory同样是创建套接字,但是使用的位置不同,clientSocketFactory,serverSocketFactory用于导出远程对象,serverSocketFactory用于在服务端建立套接字等待客户端连接,而clientSocketFactory用于调用端建立套接字发起连接。

(3)根据配置参数获取Registry

(4)构造对外发布的实例。

构建对外发布的实例,当外界通过注册的服务名调用响应的方法时,RMI服务会将请求引入此类来处理。

(5)发布实例

在发布RMI服务的流程中,有几个步骤可能是我们比较关心的。下一节会有上面步骤的拆分详细分析