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-客户端实现相关推荐
- 客户连接多个服务端_Dubbo源码解析之客户端Consumer
前面我们学习了Dubbo源码解析之服务端Provider.对服务提供方进行思路上的讲解,我们知道以下知识点.本篇文章主要对消费方进行讲解.废话不多说请看下文. 如何将对象方法生成Invoker 如何将 ...
- Mysql异常问题排查与处理——mysql的DNS反向解析和客户端网卡重启
中午刚想趴一会,不料锅从天降!!!Mysql连不上了....... 现象如下: 现象1:登录mysql所在服务器,连接MySQL 成功: 现象2:通过客户端远程连接MySQL,返回失败,如下: Ent ...
- SpringHttpInvoker解析2-服务端实现
主要的配置文件 <!-- 在Spring的httpInvoker服务 --> <bean id="httpInvokerUserService" class=&q ...
- Redis源码解析:14Redis服务器与客户端间的交互
Redis服务器是典型的一对多服务器程序,通过使用由IO多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信. Redis客户端与服务器之 ...
- HDFS源码解析:教你用HDFS客户端写数据
摘要:终于开始了这个很感兴趣但是一直觉得困难重重的源码解析工作,也算是一个好的开端. 本文分享自华为云社区<hdfs源码解析之客户端写数据>,作者: dayu_dls. 在我们客户端写数据 ...
- Restful API 生成复杂Json数据结构及使用客户端解析该数据结构(三)
前提说明:首先约定接口之前,需要约定接口的参数,接口参数包括输入参数和输出参数 输入参数:指接口调用时输入的参数 输出参数:即接口调用时返回的参数. 那么如果说,约定输入输出参数均需要采用Json结构 ...
- JSON解析与XML解析的区别
JSON与XML的区别比较 1.定义介绍 (1).XML定义 扩展标记语言 (Extensible Markup Language, XML) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记 ...
- Android OkHttp框架解析
Okhttp是由Sqare公司开发的开源网络访问库,是目前比较火的网络框架, 它处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复.如果你的服务器配置了多个IP地址,当第一个IP连接失败的时候, ...
- Java 面试知识点解析(五)——网络协议篇
前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...
- 最近看Kafka源码,着实被它的客户端缓冲池技术优雅到了
最近看kafka源码,着实被它的客户端缓冲池技术优雅到了.忍不住要写篇文章赞美一下(哈哈). 注:本文用到的源码来自kafka2.2.2版本. 背景 当我们应用程序调用kafka客户端 produce ...
最新文章
- 《快学 Go 语言》第 5 课 —— 神奇的切片
- ug10许可证错误一8_落实管理要求 做好证后监管——江西省南昌市开展排污许可证后监管探索与实践...
- cocos2d: 使用TexturePacker , pvr.ccz, CCSpriteBatchNode, CCSpriteFrameCache
- YouTube增加社交功能:邀请联系人聊天 可30人群聊
- 未处理的异常:进程性能计数器已禁用
- Java Web乱码分析及解决方案
- 玩转oracle 11g(42):增加表空间
- git 代理 git_五分钟解释Git的要点
- MSSQLSERVER数据库- 使用C#来操作事务[转]
- PyPI可以使用的几个国内源
- 中国区块链市场被低估?谈谈那些被低估的虚拟货币
- 2023江苏大学计算机考研信息汇总
- Hello China V1.75版本运行截图
- au cs6七线阁教程 笔记
- Python Flask开发简单http api接口 示例
- 今天杂志今天杂志社今天编辑部2022年第6期目录
- 本科科研经历(技术干货篇-论文发表流程)
- 2021年中国可见光通信(VLC)市场趋势报告、技术动态创新及2027年市场预测
- MyCat入门篇-什么是MyCat
- 头对风,暖烘烘;脚对风,请郎中