zoukankan      html  css  js  c++  java
  • RMI实现-----服务端的实现

    首先我们从服务器端的发布功能开始着手,同样,Spring中的核心还是配置文件,这是所有功能的基础。在服务器端的配置文件中(上篇文章远程服务(RMI)使用示例)我们可以看到,定义了两个bean,其中一个是对接口实现类的发布,另一个则是对RMI服务的发布,使用org.Springframework.remoting.RMI.RmiServiceExporter类进行封装,其中包括了服务类、服务名、服务接口、服务端口等若干属性,因此我们可以断定,org.Springframework.remoting.RMI.RmiServiceExporter应该是发布RMI的关键类。我们可以从此类入手进行分析。

    在上一篇文章的示例中,我们看出,启动Spring的RMI服务并没有多余的操作,仅仅是开启Spring的环境:new ClassPathXmlApplicationContext("spring-config.xml"),仅此一句,于是,我们分析很可能是RmiServiceExporter在初始化的时候做了某些操作完成了端口的发布功能,那么这些操作的入口是在这个类的哪个方法里面呢?先来看一下这个类的类图:

    上述是RmiServiceExporter继承了类,实际上RmiServiceExporter还实现了几个Spring中比较敏感的接口:BeanClassLoaderAware、DisposableBean、InitializingBean,其中,DisposableBean接口保证在实现该接口的bean销毁时调用其destroy方法,BeanClassLoaderAware接口保证在实现该接口的bean的初始化时调用其setBeanClassLoader方法,而InitializingBean接口保证在实现给接口的bean初始化时调用其afterPropertiesSet方法,所以我们推断RmiServiceExporter的初始化函数的入口一定在其afterPropertiesSet或者setBeanClassLoader方法中,查看代码,确认afterPropertiesSet为RmiServiceExporter功能的初始化入口。

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

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

    (1)验证service

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

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

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

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

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

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

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

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

    (5)发布实例

    在发布RMI服务的流程中,有几个步骤可能是我们比较关心的:

    1.获取registry

     获取registry实例时非常简单的,只需要使用一个函数LocateRegistry.createRegistry(registryPort,clientSocketFactory,serverSocketFactory)创建registry实例就可以了。但是,Spring并没有这么做,而是考虑得更多,比如RMI注册机与发布的服务并不在一台机器上,那么需要使用LocateRegistry.getRegistry(registryPort,clientSocketFactory,serverSocketFactory)去远程获取registry实例。

    protected Registry getRegistry(String registryHost, int registryPort,
                @Nullable RMIClientSocketFactory clientSocketFactory, @Nullable RMIServerSocketFactory serverSocketFactory)
                throws RemoteException {
    
            if (registryHost != null) {
                // 远程连接测试
                if (logger.isDebugEnabled()) {
                    logger.debug("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
                }
                //如果registryHost不为空则尝试获取对应主机的Registry
                Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
                testRegistry(reg);
                return reg;
            }
    
            else {
                //获取本机的registry
                return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
            }
        }

    如果并不是从另外的服务器上获取registry连接,那么就需要在本地创建Rmi的registry实例了。当然,这里有一个关键的参数alwaysCreateRegistry,如果此参数配置为true,那么在获取Registry实例时会首先测试是否建立了对指定端口的连接,如果已经建立则复用已经创建的实例,否则重新创建。

    当然,之前也提到过,创建Registry实例时可以使用自定义的连接工厂,而之前的判断也保证了clientSocketFactory与serverSocketFactory要么同时出现,要么同时不出现,所以这里只对clientSocketFactory是否为空进行了判断。

    Registry reg = LocateRegistry.getRegistry(null, registryPort, clientSocketFactory);

    如果创建的Registry实例时不需要使用自定义的套接字工厂,那么就可以直接使用 LocateRegistry.createRegistry(registryPort,clientSocketFactory,serverSocketFactory)方法来创建了,当然复用检查还是必要的。

    2.初始化将要导出的实体对象

     之前有提到过,当请求某个RMI服务的时候,RMI会根据注册的服务名称,将请求引导至远程对象处理类中,这个处理类便是使用getObjectToExport()进行创建。

    protected Remote getObjectToExport() {
            //如果配置的service属性对应的类实现了Remote接口且没有配置serviceInterface属性
            if (getService() instanceof Remote &&
                    (getServiceInterface() == null || Remote.class.isAssignableFrom(getServiceInterface()))) {
                // conventional RMI service
                return (Remote) getService();
            }
            else {
                // RMI invoker
                if (logger.isDebugEnabled()) {
                    logger.debug("RMI service [" + getService() + "] is an RMI invoker");
                }
                //对service进行封装
                return new RmiInvocationWrapper(getProxyForService(), this);
            }
        }

    请求处理类的初始化主要处理规则为:如果配置的service属性对应的类实现了Remote接口且没有配置serviceInterface属性,那么直接使用service作为处理类;否则,使用RMIInvocationWrapper对service的代理类和当前类也就是RMIServiceExporter进行封装。

    经过这样的封装,客户端与服务端便可以达成一致协议,当客户端检测到RMIInvocationWrapper类型的stub的时候便会直接调用其invoke方法,使得调用端与服务端很好的连接在了一起。而RMIInvocationWrapper封装了用于处理请求的代理类,在invoke中便会使用代理类进行进一步处理。

    之前的逻辑已经非常清楚了,当请求RMI服务时会由注册表Registry实例将请求转向之前注册的处理类去处理,也就是之前封装的RMIInvocationWrapper,然后,由RMIInvocationWrapper中的invoke方法进行处理,那么为什么不是在invoke方法中直接使用service,而是通过代理再次将service封装呢?

    这其中的一个关键点是,在创建代理时添加了一个增强拦截器RemoteInvocationTraceInterceptor,目的是为了对方法调用进行打印跟踪,但是如果直接在invoke方法中编码这些日志,会使得代码看起来不优雅,而且耦合度很高,使用代理的方式就会解决这样的问题,而且会有很高的可扩展性。

    protected Object getProxyForService() {
            //验证service
            checkService();
            //验证serviceInterface
            checkServiceInterface();
            //使用JDK的方式创建代理
            ProxyFactory proxyFactory = new ProxyFactory();
            //添加代理接口
            proxyFactory.addInterface(getServiceInterface());
    
            if (this.registerTraceInterceptor != null ? this.registerTraceInterceptor : this.interceptors == null) {
                //加入代理的横切面RemoteInvocationTraceInterceptor并记录Exporter名称
                proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
            }
            if (this.interceptors != null) {
                AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
                for (Object interceptor : this.interceptors) {
                    proxyFactory.addAdvisor(adapterRegistry.wrap(interceptor));
                }
            }
            //设置要代理的目标类
            proxyFactory.setTarget(getService());
            proxyFactory.setOpaque(true);
            //创建代理
            return proxyFactory.getProxy(getBeanClassLoader());
        }
     3.RMI服务激活调用

     由于在之前bean初始化的时候做了服务名称的绑定this.registry.bind(this.serviceName,this.exportedObject),其中的exportedObject其实是被RMIInvocationWrapper进行封装的,也就是说当其他服务器调用serviceName的RMI服务时,Java会为我们封装其内部操作,而直接会将代码转向RMIInvocationWrapper的invoke方法中。

    public Object invoke(RemoteInvocation invocation)
            throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    
            return this.rmiExporter.invoke(invocation, this.wrappedObject);
        }

    而此时this.RmiExporter为之前初始化的RmiServiceExporter,invocation为包含着需要激活的方法参数,而wrappedObject则是之前封装的代理类。

    protected Object invoke(RemoteInvocation invocation, Object targetObject)
                throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    
            return super.invoke(invocation, targetObject);
        }
    protected Object invoke(RemoteInvocation invocation, Object targetObject)
                throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    
            if (logger.isTraceEnabled()) {
                logger.trace("Executing " + invocation);
            }
            try {
                return getRemoteInvocationExecutor().invoke(invocation, targetObject);
            }
            catch (NoSuchMethodException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not find target method for " + invocation, ex);
                }
                throw ex;
            }
            catch (IllegalAccessException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not access target method for " + invocation, ex);
                }
                throw ex;
            }
            catch (InvocationTargetException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Target method failed for " + invocation, ex.getTargetException());
                }
                throw ex;
            }
        }
    public Object invoke(RemoteInvocation invocation, Object targetObject)
                throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{
    
            Assert.notNull(invocation, "RemoteInvocation must not be null");
            Assert.notNull(targetObject, "Target object must not be null");
            //通过反射方式激活方法
            return invocation.invoke(targetObject);
        }
    public Object invoke(Object targetObject)
                throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            //根据方法名获取代理中对应的方法
            Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
            //执行代理中的方法
            return method.invoke(targetObject, this.arguments);
        }

    至此,RMI服务端已经解析完毕。

    参考:《Spring源码深度解析》 郝佳 编著:

  • 相关阅读:
    creat-react-app/dva静态项目,用nginx部署在次级域名路径(如a.com/sub/)需要注意的几点
    如何在 Rails 中搭配 Turbolinks 使用 Vue
    绝对干货!漫谈美团APP对Crash的治理之路
    下载更省心!12月这些应用获得了绿色应用认证!
    代码之外的生存指南,这6本书助你提升软实力
    “社交通讯类”Target SDK≥26优秀应用展示
    “实用工具类”Target SDK≥26优秀应用展示
    “购物比价类”Target SDK≥26优秀应用展示
    知否知否!应用市场上架Target SDK新规来袭
    大咖推荐!今年值得一读的6本技术类书籍
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10307954.html
Copyright © 2011-2022 走看看