SpringRMI解析4-客户端实现
根据客户端配置文件,锁定入口类为RMIProxyFactoryBean,同样根据类的层次结构查找入口函数。
<bean id="rmiServiceProxy" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl">
<value>rmi://localhost/rmiService</value>
</property>
<property name="serviceInterface">
<value>org.spring.RmiService</value>
</property>
</bean>
根据层次关系,我们提取出该类实现的比较重要的接口InitializingBean,BeanClassLoaderAware以及MethodInterceptor
public class RmiProxyFactoryBean extends RmiClientInterceptor
implements FactoryBean, BeanClassLoaderAware
其中继承了RMIClientInterceptor这个类,这个类的父类的父类实现了InitializingBean接口,则spirng会确保在此初始化bean时调用afterPropertiesSet进行逻辑的初始化。
public void afterPropertiesSet()
{
super.afterPropertiesSet();
if(getServiceInterface() == null)
{
throw new IllegalArgumentException("Property 'serviceInterface' is required");
} else
{
//根据设置的接口创建代理,并使用当前类this作为增强器
serviceProxy = (new ProxyFactory(getServiceInterface(), this)).getProxy(getBeanClassLoader());
return;
}
}
同时,RMIProxyFactoryBean又实现了FactoryBean接口,那么当获取bean时并不是直接获取bean,而是获取该bean的getObject方法。
public Object getObject()
{
return serviceProxy;
}
这样,我们似乎已经形成了一个大致的轮廓,当获取该bean时,其实返回的是代理类,既然调用的是代理类,那么又会使用当前bean作为增强器进行增强,也就是说会调用RMIProxyFactoryBean的父类RMIClientInterceptor的invoke方法。
afterPropertiesSet()
public void afterPropertiesSet()
{
super.afterPropertiesSet();
prepare();
}
//继续追踪代码,发现父类的父类,也就是UrlBasedRemoteAccessor中的afterPropertiesSet方法只完成了对serviceUrl属性的验证。
public void afterPropertiesSet()
{
if(getServiceUrl() == null)
throw new IllegalArgumentException("Property 'serviceUrl' is required");
else
return;
}
在父类的afertPropertiesSet方法中完成了对serviceUrl的验证,那么prepare函数完成了什么功能呢?
通过代理拦截并获取stub
增强器进行远程连接
通过代理拦截并获取stub
public void prepare() throws RemoteLookupFailureException -{
//如果配置了lookupStubOnStartup属性便会在启动时寻找stub
if(lookupStubOnStartup)
{
Remote remoteObj = lookupStub();
if(logger.isDebugEnabled())
if(remoteObj instanceof RmiInvocationHandler)
logger.debug((new StringBuilder())
.append("RMI stub [")
.append(getServiceUrl())
.append("] is an RMI invoker").toString());
else
if(getServiceInterface() != null)
{
boolean isImpl = getServiceInterface().isInstance(remoteObj);
logger.debug((new StringBuilder())
.append("Using service interface [")
.append(getServiceInterface().getName())
.append("] for RMI stub [")
.append(getServiceUrl()).append("] - ")
.append(isImpl ? "" : "not ")
.append("directly implemented").toString());
}
if(cacheStub)
//将获取的stub缓存
cachedStub = remoteObj;
}
}
从上面的代码中,我们了解到了一个很重要的属性lookupStubOnStartup,如果将此属性设置为true,那么获取stub的工作就会在系统启动时被执行缓存,从而提高使用时候的响应时间。获取stub是RMI应用中的关键步骤,当然你可以使用两种方式进行。
(1)使用自定义的套接字工厂。如果使用这种方式,你需要在构建Registry实例时将自定义套接字工厂传入并使用Registry中提供的lookup方法来获取对应的stub。
(2)套接使用RMI提供的标准方法,Naming.lookup(getServiceUrl()).
protected Remote lookupStub()throws RemoteLookupFailureException{
try{
Remote stub = null;
if(registryClientSocketFactory != null)
{
URL url = new URL(null, getServiceUrl(), new DummyURLStreamHandler());
String protocol = url.getProtocol();
if(protocol != null && !"rmi".equals(protocol))
throw new MalformedURLException((new StringBuilder())
.append("Invalid URL scheme '")
.append(protocol)
.append("'").toString());
String host = url.getHost();
int port = url.getPort();
String name = url.getPath();
if(name != null && name.startsWith("/"))
name = name.substring(1);
Registry registry = LocateRegistry.getRegistry(host, port, registryClientSocketFactory);
stub = registry.lookup(name);
} else
{
stub = Naming.lookup(getServiceUrl());
}
if(logger.isDebugEnabled())
logger.debug((new StringBuilder())
.append("Located RMI stub with URL [")
.append(getServiceUrl())
.append("]").toString());
return stub;
}
catch(MalformedURLException ex)
{
throw new RemoteLookupFailureException((new StringBuilder())
.append("Service URL [")
.append(getServiceUrl())
.append("] is invalid").toString(), ex);
}
catch(NotBoundException ex)
{
throw new RemoteLookupFailureException((new StringBuilder())
.append("Could not find RMI service [")
.append(getServiceUrl())
.append("] in RMI registry").toString(), ex);
}
catch(RemoteException ex)
{
throw new RemoteLookupFailureException("Lookup of RMI stub failed", ex);
}
}
为了使用registryClientSocketFactory,代码量比使用RMI标准获取stub方法多出了很多,那么registryClientSocketFactory到底是做什么用的呢?与之前服务端的套接字工厂类似,这里的registryClientSocketFactory用来连接RMI服务器,用户通过实现RMIClientSocketFactory接口来控制用于连接的socket的各种参数。
增强器进行远程连接
在初始化时,创建了代理并将本身作为增强器加入了代理中(RMIProxyFactoryBean间接实现了MethodInterceptor),那么这样一来,当在客户端调用代理的接口中的某个方法时,就会首先执行RMIProxyFactoryBean中的invoke方法进行增强。
public Object invoke(MethodInvocation invocation) throws Throwable {
//获取服务器中对应的注册的remote对象,通过序列化传输
Remote stub = getStub();
try{
return doInvoke(invocation, stub);
}
catch(RemoteConnectFailureException ex)
{
return handleRemoteConnectFailure(invocation, ex);
}
catch(RemoteException ex)
{
if(isConnectFailure(ex))
return handleRemoteConnectFailure(invocation, ex);
else
throw ex;
}
}
protected Remote getStub()throws RemoteLookupFailureException {
//如果有缓存,直接使用缓存
if(!cacheStub || lookupStubOnStartup && !refreshStubOnConnectFailure){
return cachedStub == null ? lookupStub() : cachedStub;
}else{
synchronized (this.stubMonitor) {
if (this.cachedStub == null) {
this.cachedStub = lookupStub();
}
return this.cachedStub;
}
}
当客户端使用接口进行方法调用时时通过RMI获取stub的,然后再通过stub中封装的信息进行服务器的调用,这个stub就是在构建服务器时发布的对象,那么客户端调用时的最关键的一步也是进行stub的获取了。
当获取到stub后便可以进行远程方法的调用了。Spring中对于远程方法的调用其实是分两种情况考虑的。
- 获取的stub是RMIInvocationHandler类型的,从服务端获取的stub是RMIInvocationHandler,就意味着服务端也同样使用了Spring去构建,那么自然会使用Spring中作的约定,进行客户端调用处理。Spring中的处理方式被委托给了doInvoke方法。
- 当获取的stub不是RMIInvocationHandler类型,那么服务端构建RMI服务可能是通过普通的方法或者借助于Spring外的第三方插件,那么处理方式自然会按照RMI中普通的方式进行,而这种普通的处理方式无非是反射。因为在invocation中包含了所需要调用的方法的各种信息,包括方法名称以及参数等,而调用的实体正是stub,那么通过反射方法完全可以激活stub中的远程调用。
protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
if (stub instanceof RmiInvocationHandler) {
// RMI invoker
try {
return doInvoke(invocation, (RmiInvocationHandler) stub);
}
catch (RemoteException ex) {
throw RmiClientInterceptorUtils.convertRmiAccessException(
invocation.getMethod(), ex, isConnectFailure(ex), getServiceUrl());
}
catch (InvocationTargetException ex) {
Throwable exToThrow = ex.getTargetException();
RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow);
throw exToThrow;
}
catch (Throwable ex) {
throw new RemoteInvocationFailureException("Invocation of method [" + invocation.getMethod() +
"] failed in RMI service [" + getServiceUrl() + "]", ex);
}
}
else {
// traditional RMI stub
try {
return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, stub);
}
catch (InvocationTargetException ex) {
Throwable targetEx = ex.getTargetException();
if (targetEx instanceof RemoteException) {
RemoteException rex = (RemoteException) targetEx;
throw RmiClientInterceptorUtils.convertRmiAccessException(
invocation.getMethod(), rex, isConnectFailure(rex), getServiceUrl());
}
else {
throw targetEx;
}
}
}
在分析服务端发布RMI的方式时,Spring将RMI的到处Object封装成了RMIInvocationHandler类型进行发布,那么当客户端获取stub的时候是包含了远程连接信息代理类的RMIInvacationHandler,也就是说当调用RMIInvacationHandler中的方法时会使用RMI中提供的代理进行远程连接,而此时,Spring中要做的就是将代码引向RMIInvocationHandler接口的invoke方法的调用。
protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
return "RMI invoker proxy for service URL [" + getServiceUrl() + "]";
}
//将methodInvocation中的方法名以及参数等信息重新封装RemoteInvocation,并通过远程代理方法直接调用
return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
}
SpringRMI解析4-客户端实现的更多相关文章
- hessian原理解析一(客户端分析)
hessian 是一款开源的二进制远程通讯协议,使用简单方法提供了RMI功能,主要用于面向对象的消息通信. 优点:跨平台.多语言支持.使用简单 缺点:传递复杂对象性能会下降,不适合安全性高的应用 一 ...
- Mysql异常问题排查与处理——mysql的DNS反向解析和客户端网卡重启
中午刚想趴一会,不料锅从天降!!!Mysql连不上了....... 现象如下: 现象1:登录mysql所在服务器,连接MySQL 成功: 现象2:通过客户端远程连接MySQL,返回失败,如下: Ent ...
- Fabric1.4源码解析:客户端创建通道过程
在使用Fabric创建通道的时候,通常我们执行一条命令完成,这篇文章就解析一下执行这条命令后Fabric源码中执行的流程. peer channel create -o orderer.example ...
- Fabric1.4源码解析:客户端安装链码
看了看客户端安装链码的部分,感觉还是比较简单的,所以在这里记录一下. 还是先给出安装链码所使用的命令好了,这里就使用官方的安装链码的一个例子: #-n 指定mycc是由用户定义 ...
- Linux系统学习 十四、VSFTP服务—配置文件解析、客户端使用
3.配置文件解析 默认配置选项: 一般情况下不允许匿名用户登录 全局配置选项:(手工添加) listen_address=192.168.4.1 #设置监听地址 listen_ ...
- Spring Cloud系列(四):Eureka源码解析之客户端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...
- SpringRMI解析3-RmiServiceExporter逻辑细节
在发布RMI服务的流程中,有几个步骤可能是我们比较关心的. 获取registry 由于底层的封装,获取Registry实例是非常简单的,只需要使用一个函数LocateRegistry.createRe ...
- SpringRMI解析2-RmiServiceExporter逻辑脉络
配置文件是Spring的核心,在配置文件中我们可以看到,定义了两个bean,其中一个是对接口实现类的发布,而另一个则是对RMI服务的发布,使用org.springframework.remoting. ...
- SpringRMI解析1-使用示例
Java远程方法调用,即JavaRMI(JavaRemote Method Invocation),是Java编程语言里一种用于实现远程过程调用的应用程序编程接口.它使客户机上的运行的程序可以调用远程 ...
随机推荐
- codeforces gym 100694 M The Fifth Season (巴什博奕)
题目链接 一直觉得巴什博奕是最简单的博弈遇到肯定没问题,结果被虐惨了,看完标程错了10多遍都没反应过来,当然标程题解和代码的意思也写反了,但是还是想对自己说一句mdzz,傻啊!!!这道题很不错,我觉得 ...
- Hibernate基本CRUD
1 hibernate 框架介绍 冬眠:开发的代码基本不变. 1.1回顾jdbc Java提供的标准的数据访问的API 作用:完成应用程序java程序中的数据和db中的数据进行交换. 工作过程: A ...
- Java代码简化神器-Lombok
一.背景 前段时间在开源社区中发现了一个比较牛逼的简化Java代码的神器-Lombok,接着自己写了demo进行测试和练习,感觉真的很不错,特此分享给需要的小伙伴们~ 二.开发之前的准备 1.lomb ...
- 第二章 C#基本数据类型
第一节 1-关键字和标识符关键字:会不同颜色显示标示符:由字母.中文.数字.下划线组成,不能以数字开头,不能是关键字,不能含有特殊符号,如:@,$思考:下列哪些是合法的标识符?a1.1a.a_b._1 ...
- 413. Arithmetic Slices
/**************************Sorry. We do not have enough accepted submissions.*********************** ...
- centos 安装 py pyhs2
1. yum install gcc-c++ cyrus-sasl-develpip2.7 install pyhs2 --->好像不行,在试试 用于 hive thrift 访问 2. os. ...
- 【Java EE 学习 21 下】【使用java实现邮件发送、邮件验证】
一.邮件发送 1.邮件发送使用SMTP协议或者IMAP协议,这里使用SMTP协议演示. SMTP协议使用的端口号:25 rfc821详细记载了该协议的相关信息 (1)使用telnet发送邮件(使用12 ...
- react.js基本操练
慢慢了解其它的JS前端框架... var data = [{ "when": "2 minutes ago", "who": "J ...
- HTTPCLIENT 学习 (1) 入门
早就如雷贯耳它的大名,却一直不曾相见,昨天下载下来,今天终于测试了一把,用的官网的QUICK START例子,来访问我自己以前开发过的WEB程序,因为这个网站恰好有一个写好的通过POST请求验证用户名 ...
- sqlplus 中spool命令的简单用法
spool基本格式: spool 路径+文件名 select col1||','||col2||','||col3||','||col4||'..' from tablename; spool off ...