Spring源码与分析总结——RMI整合

 2019-10-17 21:48  阅读(1023)
文章分类:Spring boot

该文章基于《Spring源码深度解析》撰写,感谢郝佳老师的奉献
RMI的实际作用,是通过暴露对应方法的URL,从而实现高解耦。
RMI服务的流程是通过服务的发布服务的调用组成,小Demo如下:

其工程架构如下
20191017100178\_1.png
其对应文件代码如下

/*HelloRMIService*/
    package RMIService.Impl;

    public class HelloRMIService implements RMIService.HelloRMIService{
        @Override
        public int getAdd(int a, int b) {
            return a+b;
        }
    }

/*RMIService*/
    package RMIService;

    public interface HelloRMIService {
        public int getAdd(int a,int b);
    }

/*RMIService*/
    package RMIService;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class RMIServer {

        public static void main(String[] args) throws InterruptedException {
            new ClassPathXmlApplicationContext("server.xml");
        }
    }

/*RMIClient*/
    package RMIClient;

    import RMIService.HelloRMIService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class RMIClient {

        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
                    "Client.xml");
            HelloRMIService accountService = (HelloRMIService) ctx
                    .getBean("mobileAccountService");
            Integer result = accountService.getAdd(1,2);
            System.out.println(result);
        }

    }

<!--Client-->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

        <bean id="mobileAccountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
            <property name="serviceUrl" value="rmi://localhost:8080/MobileAccountService" />
            <property name="serviceInterface" value="RMIService.HelloRMIService" />
        </bean>

    </beans>

<!--server.xml-->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

        <bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
            <property name="serviceName" value="MobileAccountService" />
            <property name="service" ref="accountService" />
            <property name="serviceInterface" value="RMIService.HelloRMIService" />
            <property name="registryPort" value="8080" />
            <property name="servicePort" value="8088" />
        </bean>

        <bean id="accountService" class="RMIService.Impl.HelloRMIService" />

    </beans>

需要注意的是,我们需要将HelloRMIService.class文件打包为jar文件后放入lib中。
现在我们开始进行流程分析:

服务器端实现

org.Springframework.remoting.RMI.RMIServiceExporter是RMI服务发布的关键类,该类定义在server.xml中。Spring中的消息开启很简单,通过RMIServer类中的

new ClassPathXmlApplicationContext("server.xml");

开启服务。RMIServiceExporte的类层次结构如下:
20191017100178\_2.png
RmiServiceExporter实现了几个比较敏感的接口:BeanClassLoaderAware(保证实现该接口的Bean的初始化时调用其setBeanClassLoader方法)、DisposableBean(销毁Bean时调用其destory方法)、InitializingBean(初始化Bean时调用其afterPropertiesSet方法),所以RmiServiceExporter的初始化应该位于BeanClassLoaderAware或者InitializingBean中,实际上初始化方法位于InitializingBean的afterPropertiesSet方法,该方法的处理逻辑为:
(1)验证Service
(2)处理用户自定义的SocketFactory属性,Spring中提供了4个套接字工厂配置分别是(client/server)+SocketFactory,register+(client/server)+SocketFactory,register+(client/server)+SocketFactory用于当创建registry实例时在RMI主机通过serverSocketFactory创建套接字并等待连接,而在服务器端与RMI主机通信时通过clientSocketFacotry创建套接字
(3)根据配置参数获取Registry
如果RMI注册主机与发布服务的机器是同一台机器,那么可以直接调用函数LocateRegistry.createRegistry(…)创建Registry实例。如果并不在同一台机器上,那么使用LocateRegistry.getRegistry(registryHost,registryPort,clientSocketFactory)进行远程获取,如果已经建立了连接,那么将直接复用连接,除非alwaysCreateRegistry为true如果我们自定义了连接工厂——由于clientSocketFactory和serverSocketFactory只能可同时存在或者同时不存在,所以我们只对clientSocketFactory是否为空进行判断如果不存在自定义的连接工厂——直接进行复用检测,如果不存在或者alwaysCreateRegistry为true,那么直接通过LocateRegistry.createRegistry(…)创建新连接。
(4)构造对外发布的实例当外界通过注册的服务名调用响应方法时,RMI服务会将请求引入此类来处理,如果配置的service属性对应的类实现了remote接口但是没有配置serviceInterface接口,那么直接使用service作为处理类,否则使用用RMIInvocationWrapper对RMIServiceExporter进行封装,通过封装使客户端与服务器端达成了一致,并且在创建代理时添加了增强拦截器RemoteInvocationTraceIntercaptor,如果直接通过硬编码,那么会很不优雅,通过动态代理使代码具有高扩展性。正是由于封装的情况,当调用服务时实际上调用的是RMIInvocationWrappe的invoke方法。
(5)发布实例

客户端实现

客户端的入口类为RMIProxyFactoryBean,其类层次结构如下所示:
20191017100178\_3.png
该类实现了几个比较关键的接口,InitailizingBean接口(通过afterPropertiesSet进行逻辑初始化),FactoryBean接口(getBean方法实际上返回的是实现类的getObject方法返回的实例),下面重点介绍InitailizingBean接口接口在初始化时的逻辑:
需要注意的是,afterPropertiesSet实际上的处理逻辑位于RmiClientInterceptor类中的prepare方法中,所以此处对该方法进行逻辑分析
(1)通过代理拦截并获取stub
获取stub可以通过两种方式进行:使用自定义的套接字工厂该方法需要在构造Registry实例时将自定义套接字工厂传入,并使用lookup来获取对应stub,直接使用RMI提供的标准方法Naming.lookup(getServiceUrl()),该方法中将会使用registryClientSocketFactory(实现了RMIClientSocketFactory接口)用于控制用于连接的socket的各种参数然后连接RMI服务器
(2)增强器用于远程连接
由于RmiProxyFactoryBean间接实现了MethodInterceptor,那么当客户端调用某个方法时,会首先调用invoke方法进行增强,invoke方法的逻辑如下:
(1)获取服务器中对应注册的remote对象,通过序列化传输,在通过getstub方法获取stub时,会首先检查有没有stub的缓存
(2)远程方法调用分为两种情况,第一种情况是:获取的stub是RMIInvocationHandler类型的,这就意味着客户端和服务端都是通过Spring框架搭建的,处理方式为将处理方式委托给doInvoke方法第二种情况是:获取的stub不是RMIInvocationHandler类型的,那么这也意味着我们将通过反射的方法激活stub中的远程调用


来源:[]()

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Spring源码与分析总结——RMI整合

相关推荐