您的位置:首页 > 编程语言 > Java开发

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

2018-03-14 18:00 323 查看
该文章基于《Spring源码深度解析》撰写,感谢郝佳老师的奉献

RMI的实际作用,是通过暴露对应方法的URL,从而实现高解耦。

RMI服务的流程是通过服务的发布服务的调用组成,小Demo如下:

其工程架构如下



其对应文件代码如下

/*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的类层次结构如下:



RmiServiceExporter实现了几个比较敏感的接口:BeanClassLoaderAware(保证实现该接口的Bean的初始化时调用其setBeanClassLoader方法)、DisposableBean(销毁Bean时调用其destory方法)、InitializingBean(初始化Bean时调用其afterPropertiesSet方法),所以RmiServiceExporter的初始化应该位于BeanClassLoaderAware或者InitializingBean中,实际上初始化方法位于Init
b20c
ializingBean
的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,其类层次结构如下所示:



该类实现了几个比较关键的接口,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中的远程调用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: