SpringHttpInvoker解析3-客户端实现
主要的配置文件
<bean id="httpInvokerUserService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/jws/httpInvokerUserService.service"/>
<property name="serviceInterface" value="com.gosun.jws.httpinvoker.UserService" />
</bean>
在服务端调用的分析中我们反复提到需要从HttpServletRequest中提取从客户端传来的RemoteInvocation实例,然后进行相应解析。所以客户端,一个比较重要的任务就是构建RemoteInvocation实例,并传送到服务器。根据配置文件中的信息,我们还是首先确定HttpInvokerProxyFactoryBean类,并查看其层次结构。
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor
implements FactoryBean
httpInvokerProxyFactoryBean的父类的父类的父类实现了InitializingBean接口,同时又实现了FactoryBean以及其父类又实现了MethodInterceptor。我们还是根据实现的InitializingBean接口分析初始化过程中的逻辑。
public void afterPropertiesSet()
{
super.afterPropertiesSet();
if(getServiceInterface() == null)
{
throw new IllegalArgumentException("Property 'serviceInterface' is required");
} else
{
//创建代理并使用当前方法为拦截器增强
serviceProxy = (new ProxyFactory(getServiceInterface(), this)).getProxy(getBeanClassLoader());
return;
}
}
在afterPropertiesSet中主要创建了一个代理,该代理封装了配置的服务接口,并使用当前类也就是HttpInvokerProxyFactoryBean作为增强。因为HttpInvokerProxyFactoryBean实现了MethodPInterceptor方法,所以可以作为增强拦截器。同样,又由于HttpInvorkerProxyFactoryBean实现了FactoryBean接口,所以通过spring中普通方式调用该bean时调用的并不是该bean本身,而是getObject方法中返回的实例,也就是实例化过程中所创建的代理。
public Object getObject(){
return serviceProxy;
}
HttpInvokerProxyFactoryBean类型bean在初始化过程中创建了封装服务接口的代理,并使用自身作为增强拦截器,然后又因为实现了FactoryBean接口,所以获取Bean的时候返回的就是创建的代理。那么,当调用如下代码时,其实是调用代理类中的服务方法,而在调用代理类中的服务方法时又会使用代理类中加入的增强器进行增强。
ApplicationContext ac = new ClassPathXmlApplicationContext("client-application/applicationContext-httpInvoker.xml");
UserService us = (UserService) ac.getBean("httpInvokerUserService");
Users user = us.getUser("a001");
因为HttpInvokerProxyFactoryBean实现了methodIntercepter接口,所有的逻辑分析其实已经转向了对于增强器也就是HttpInvokerProxyFactoryBean类本身的invoke方法的分析。该方法所提供的主要功能就是将调用信息封装在RemoteInvocation中,发送给服务端并等待返回结果。
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if(AopUtils.isToStringMethod(methodInvocation.getMethod()))
return (new StringBuilder())
.append("HTTP invoker proxy for service URL [")
.append(getServiceUrl())
.append("]").toString();
//将要调用的方法封装RemoteInvocation
RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
RemoteInvocationResult result = null;
try
{
result = executeRequest(invocation, methodInvocation);
}
catch(Throwable ex)
{
throw convertHttpInvokerAccessException(ex);
}
try
{
return recreateRemoteInvocationResult(result);
}
catch(Throwable ex)
{
if(result.hasInvocationTargetException())
throw ex;
else
throw new RemoteInvocationFailureException((new StringBuilder())
.append("Invocation of method [")
.append(methodInvocation.getMethod())
.append("] failed in HTTP invoker remote service at [")
.append(getServiceUrl()).append("]").toString(), ex);
}
}
函数主要有三个步骤。
- 构建RemoteInvocation实例。因为是代理中增强方法的调用,调用的方法及参数信息会在代理中封装至MethodInvocation实例中,并在增强器中进行传递。也就意味着当程序进入invoke方法时其实是已经包含了调用的相关信息的,那么,首先要做的就是将MethodInvocation中的信息提取并构建RemoteInvocation实例。
- 远程执行方法。
- 提取结果。
在Spring中约定使用HttpInvoker方式进行远程方法调用时,结果使用RemoteInvocationResult进行封装,那么在提取结果后还需要从封装的结果中提取对应的结果。而在三个步骤中最为关键的就是远程方法的执行。执行远程调用的首要步骤就是将调用方法的实例写入输出流中。
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation, MethodInvocation originalInvocation)
throws Exception
{
return executeRequest(invocation);
}
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation)
throws Exception
{
return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
}
public final RemoteInvocationResult executeRequest(HttpInvokerClientConfiguration config, RemoteInvocation invocation)
throws Exception
{
//获取输出流
ByteArrayOutputStream baos = getByteArrayOutputStream(invocation);
if(logger.isDebugEnabled())
logger.debug((new StringBuilder())
.append("Sending HTTP invoker request for service at [")
.append(config.getServiceUrl()).append("], with size ")
.append(baos.size()).toString());
return doExecuteRequest(config, baos);
}
在doExecuteRequest方法中真正实现了对远程方法的构造与通信,与远程方法的连接功能实现中,Spring引入了第三方JAR:HttpClient。HttpClient是Apache Jakarta Common下的子项目,可以用来提供高效的,最新的,功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。
protected RemoteInvocationResult doExecuteRequest(HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
throws IOException, ClassNotFoundException
{
//创建httpPost
PostMethod postMethod = createPostMethod(config);
RemoteInvocationResult remoteinvocationresult;
//设置含有方法的输出流到post中
setRequestBody(config, postMethod, baos);
//执行方法
executePostMethod(config, getHttpClient(), postMethod);
//验证
validateResponse(config, postMethod);
//提取返回的输入流
InputStream responseBody = getResponseBody(config, postMethod);
//从输入流中提取结果
remoteinvocationresult = readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
postMethod.releaseConnection();
return remoteinvocationresult;
}
1.创建HttpPost
由于对于服务端方法的调用是通过Post方式进行的,那么首先要做的就是构建HttpPost。构建HttpPost过程中可以设置一些必要的参数。
protected PostMethod createPostMethod(HttpInvokerClientConfiguration config)
throws IOException
{
//设置需要访问的url
PostMethod postMethod = new PostMethod(config.getServiceUrl());
LocaleContext locale = LocaleContextHolder.getLocaleContext();
if(locale != null)
//加入Accept-Language属性
postMethod.addRequestHeader("Accept-Language", StringUtils.toLanguageTag(locale.getLocale()));
if(isAcceptGzipEncoding())
//加入Accept-Encoding属性
postMethod.addRequestHeader("Accept-Encoding", "gzip");
return postMethod;
}
2.设置RequestBody
构建好PostMethod实例后便可以将存储RemoteInvocation实例的序列化形象的输出流设置进去,当然这里需要注意的是传入的ContentType类型,一定要传入application/x-Java-serialized-object以保证服务端解析时会按照序列化对象的解析方式进行解析。
protected void setRequestBody(HttpInvokerClientConfiguration config, PostMethod postMethod, ByteArrayOutputStream baos)
throws IOException
{
//将序列化流加入到postMethod中并声明ContentType类型为appliction、x-java-serialized-object
postMethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), getContentType()));
}
3.执行远程方法
通过HttpClient所提供的方法来直接执行远程方法。
protected void executePostMethod(HttpInvokerClientConfiguration config, HttpClient httpClient, PostMethod postMethod)
throws IOException
{
httpClient.executeMethod(postMethod);
}
4.远程相应验证
对于HTTP调用的响应码处理,大于300则是非正常调用的响应码。
protected void validateResponse(HttpInvokerClientConfiguration config, PostMethod postMethod)
throws IOException
{
if(postMethod.getStatusCode() >= 300)
throw new HttpException((new StringBuilder())
.append("Did not receive successful HTTP response: status code = ")
.append(postMethod.getStatusCode())
.append(", status message = [")
.append(postMethod.getStatusText())
.append("]").toString());
else
return;
}
5.提取响应信息
从服务器返回的输入流可能是经过压缩的,不同的方式采用不同的办法进行提取
protected InputStream getResponseBody(HttpInvokerClientConfiguration config, PostMethod postMethod)
throws IOException
{
if(isGzipResponse(postMethod))
return new GZIPInputStream(postMethod.getResponseBodyAsStream());
else
return postMethod.getResponseBodyAsStream();
}
6.提取返回结果
提取结果的流程主要是从输入流中提取响应的序列化信息。
protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl)
throws IOException, ClassNotFoundException {
ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);
try {
return doReadRemoteInvocationResult(ois);
}
finally {
ois.close();
}
}
protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)
throws IOException, ClassNotFoundException { Object obj = ois.readObject();
if (!(obj instanceof RemoteInvocationResult)) {
throw new RemoteException("Deserialized object needs to be assignable to type [" +
RemoteInvocationResult.class.getName() + "]: " + obj);
}
return (RemoteInvocationResult) obj;
}
许多公司的分布式框架中都用到了远程服务调用,无论是dubbo,还是别的,了解远程调用的原理都是大同小异的。都是通过http请求,封装序列化的对象,通过动态代理的方式进行信息获取。只不过互联网公司的远程调用是布在分布式上罢了。
SpringHttpInvoker解析3-客户端实现的更多相关文章
- 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的 ...
- SpringHttpInvoker解析2-服务端实现
主要的配置文件 <!-- 在Spring的httpInvoker服务 --> <bean id="httpInvokerUserService" class=&q ...
- SpringHttpInvoker解析1-使用示例
HTTP invoker是一个新的远程调用模型,作为Spring框架的一部分,来执行基于HTTP的远程调用(让防火墙可以接受),并使用Java的序列化机制. 服务端 定义服务接口UserService ...
- OPC协议解析-OPC客户端与服务器通讯解析
1 OPC服务器 OPC服务器, 是指按照OPC基金组织规定的OPC规范群开发的软件驱动.OPC服务器作为中间媒介负责从数据源读取数据再跟另外一端的客户端通信.在 OPC客户端/服务器 的结 ...
随机推荐
- NSOJ A fairy tale of the two(最小费用最大流、SPFA版本、ZKW版本)
n,m<=20,给两个n×m布尔矩阵,每次操作可将第一个矩阵的2个相邻元素互换.输出最少操作次数使得两个矩阵完全一样. 比赛的时候想过按照二分图完美匹配的类似做法构图,不过想到边太多以及卡各种题 ...
- nyoj133_子序列_离散化_尺取法
子序列 时间限制:3000 ms | 内存限制:65535 KB 难度:5 描述 给定一个序列,请你求出该序列的一个连续的子序列,使原串中出现的所有元素皆在该子序列中出现过至少1次. 如2 8 ...
- Divide and conquer:Subset(POJ 3977)
子序列 题目大意:给定一串数字序列,要你从中挑一定个数的数字使这些数字和绝对值最小,求出最小组合数 题目的数字最多35个,一看就是要数字枚举了,但是如果直接枚举,复杂度就是O(2^35)了,显然行不通 ...
- 【leetcode】Swap Nodes in Pairs (middle)
Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1->2-&g ...
- 【hihoCoder】第20周 线段树
题目: 输入 每个测试点(输入文件)有且仅有一组测试数据. 每组测试数据的第1行为一个整数N,意义如前文所述. 每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品 ...
- 【Git】参与github上其他人的项目
来源:廖雪峰 访问感兴趣的项目主页.,点“Fork”就在自己的账号下克隆了该项目仓库,然后,从自己的账号下clone到本地,就可以工作啦~ 以bootstrap项目为例,这个关系如下图所示: 一定要从 ...
- 无限轮播的N+2 策略
N张照片把contentsSize设置为N+2个图片的宽度,例子如下,两端填充如图,当处于一端时,且即将进入循环状态的时候,如第二张图,从状态1滑动到状态2,在滑动结束的时候,将当前的位置直接转到状态 ...
- IOS - Foundation和Core Foundation掺杂使用桥接
Foundation和Core Foundation掺杂使用桥接 Toll-Free Bridging 在cocoa application的应用中,我们有时会使用Core Foundation(CF ...
- GoF23种设计模式
创建型模式 1.ABSTRACT FACTORY-追MM少不了请吃饭了,麦当劳的套餐和肯德基的套餐都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说"两个B ...
- 35. Search Insert Position
题目: Given a sorted array and a target value, return the index if the target is found. If not, return ...