主要的配置文件

<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);
}
}

函数主要有三个步骤。

  1. 构建RemoteInvocation实例。因为是代理中增强方法的调用,调用的方法及参数信息会在代理中封装至MethodInvocation实例中,并在增强器中进行传递。也就意味着当程序进入invoke方法时其实是已经包含了调用的相关信息的,那么,首先要做的就是将MethodInvocation中的信息提取并构建RemoteInvocation实例。
  2. 远程执行方法。
  3. 提取结果。

在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-客户端实现的更多相关文章

  1. hessian原理解析一(客户端分析)

    hessian 是一款开源的二进制远程通讯协议,使用简单方法提供了RMI功能,主要用于面向对象的消息通信. 优点:跨平台.多语言支持.使用简单 缺点:传递复杂对象性能会下降,不适合安全性高的应用 一 ...

  2. Mysql异常问题排查与处理——mysql的DNS反向解析和客户端网卡重启

    中午刚想趴一会,不料锅从天降!!!Mysql连不上了....... 现象如下: 现象1:登录mysql所在服务器,连接MySQL 成功: 现象2:通过客户端远程连接MySQL,返回失败,如下: Ent ...

  3. Fabric1.4源码解析:客户端创建通道过程

    在使用Fabric创建通道的时候,通常我们执行一条命令完成,这篇文章就解析一下执行这条命令后Fabric源码中执行的流程. peer channel create -o orderer.example ...

  4. Fabric1.4源码解析:客户端安装链码

          看了看客户端安装链码的部分,感觉还是比较简单的,所以在这里记录一下.       还是先给出安装链码所使用的命令好了,这里就使用官方的安装链码的一个例子: #-n 指定mycc是由用户定义 ...

  5. Linux系统学习 十四、VSFTP服务—配置文件解析、客户端使用

    3.配置文件解析 默认配置选项: 一般情况下不允许匿名用户登录 全局配置选项:(手工添加) listen_address=192.168.4.1             #设置监听地址 listen_ ...

  6. Spring Cloud系列(四):Eureka源码解析之客户端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...

  7. SpringHttpInvoker解析2-服务端实现

    主要的配置文件 <!-- 在Spring的httpInvoker服务 --> <bean id="httpInvokerUserService" class=&q ...

  8. SpringHttpInvoker解析1-使用示例

    HTTP invoker是一个新的远程调用模型,作为Spring框架的一部分,来执行基于HTTP的远程调用(让防火墙可以接受),并使用Java的序列化机制. 服务端 定义服务接口UserService ...

  9. OPC协议解析-OPC客户端与服务器通讯解析

    1      OPC服务器 OPC服务器, 是指按照OPC基金组织规定的OPC规范群开发的软件驱动.OPC服务器作为中间媒介负责从数据源读取数据再跟另外一端的客户端通信.在 OPC客户端/服务器 的结 ...

随机推荐

  1. codeforces 577B. Modulo Sum 解题报告

    题目链接:http://codeforces.com/problemset/problem/577/B 题目意思:就是给出 n 个数(a1, a2, ..., an) 和 m,问能不能从这 n 个数中 ...

  2. 【leetcode】Number of 1 Bits (easy)

    做太多遍了,秒杀. class Solution { public: int hammingWeight(uint32_t n) { ; ), num++); return num; } };

  3. 【C语言】pragma

    ① #pragma comment (lib, "libgsl.a") 这是告诉编译器在编译形成的.obj文件和.exe文件中加一条信息,使得 链接器在链接库的时候要去找libgs ...

  4. IOS- DocumentInteraction Controllerl的使用

    iOS提供了使用其他app预览文件的支持,这就是Document Interaction Controller.此外,iOS也支持文件关联,允许其他程序调用你的app打开某种文件.而且,从4.2开始, ...

  5. DOM动画效果基础入门

    一.什么是DOM? Document Object Model文档对象模型 当创建一个网页并把它加载到web浏览器中时,如果没有document(文档): DOM根据你编写的网页文档创建一个文档对象 ...

  6. Qt 获取cmd运行结果

    http://www.cnblogs.com/gisbeginner/archive/2012/12/08/2809063.html BOOL ExecDosCmd(){ #define EXECDO ...

  7. September 21st 2016 Week 39th Wednesday

    Don't try so hard, the best things come when you least expect them. 不要着急,最好的总会在最不经意的时候出现. Always tur ...

  8. linux vi 中按了ctrl+s后没法退出

    linux vi 中按了ctrl+s后无法退出 Linux 中使用vi编辑文件 不小心按了Ctrl + S (习惯了) 结果终端就跟死了一样, 解决办法: Ctrl+Q

  9. java的system.arraycopy()方法

    java.lang.System的静态方法arraycopy()可以实现数组的复制,讲课的老师说这个方法效率比较高,如果数组有成千上万个元素,那么用这个方法,比用for语句循环快不少.于是我试了试,发 ...

  10. UVA 10252

    按照字典序输出最长公共子序列 #include<time.h> #include <cstdio> #include <iostream> #include< ...