返回介绍

12.2.3 客户端实现

发布于 2025-04-22 22:09:18 字数 8673 浏览 0 评论 0 收藏

分析了服务端的解析以及处理过程后,我们接下来分析客户端的调用过程,在服务端调用的分析中我们反复提到需要从 HttpServletRequest 中提取从客户端传来的 RemoteInvocation 实例,然后进行相应解析。所以,在客户端,一个比较重要的任务就是构建 RemoteInvocation 实例,并传送到服务端。根据配置文件中的信息,我们还是首先锁定 HttpInvokerProxyFactoryBean 类,并查看其层次结构,如图 12-4 所示。

从层次结构中我们看到,HttpInvokerProxyFactoryBean 类同样实现了 InitializingBean 接口。同时,又实现了 FactoryBean 以及 MethodInterceptor。这已经是老生常谈的问题了,实现这几个接口以及这几个接口在 Spring 中会有什么作用就不再赘述了,我们还是根据实现的 InitializingBean 接口分析初始化过程中的逻辑。

figure_0372_0051

图 12-4 HttpInvokerProxyFactoryBean 类的层次结构图

public void afterPropertiesSet() {

 super.afterPropertiesSet();

  if (getServiceInterface() == null) {

   throw new IllegalArgumentException("Property 'serviceInterface' is

  required");

 }

 //创建代理并使用当前方法为拦截器增强

  this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy

 (getBeanClassLoader());

}

在 afterPropertiesSet 中主要创建了一个代理,该代理封装了配置的服务接口,并使用当前类也就是 HttpInvokerProxyFactoryBean 作为增强。因为 HttpInvokerProxyFactoryBean 实现了 MethodInterceptor 方法,所以可以作为增强拦截器。

同样,又由于 HttpInvokerProxyFactoryBean 实现了 FactoryBean 接口,所以通过 Spring 中普通方式调用该 bean 时调用的并不是该 bean 本身,而是此类中 getObject 方法返回的实例,也就是实例化过程中所创建的代理。

public Object getObject() {

  return this.serviceProxy;

}

那么,综合之前的使用示例,我们再次回顾一下,HttpInvokerProxyFactoryBean 类型 bean 在初始化过程中创建了封装服务接口的代理,并使用自身作为增强拦截器,然后又因为实现了 FactoryBean 接口,所以获取 Bean 的时候返回的其实是创建的代理。那么,汇总上面的逻辑,当调用如下代码时,其实是调用代理类中的服务方法,而在调用代理类中的服务方法时又会使用代理类中加入的增强器进行增强。

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:client.xml");

HttpInvokerTestI httpInvokerTestI = (HttpInvokerTestI) context.getBean("remoteService");

System.out.println(httpInvokerTestI.getTestPo("dddd"));

这时,所有的逻辑分析其实已经被转向了对于增强器也就是 HttpInvokerProxyFactoryBean 类本身的 invoke 方法的分析。

在分析 invoke 方法之前,其实我们已经猜出了该方法所提供的主要功能就是将调用信息封装在 RemoteInvocation 中,发送给服务端并等待返回结果。

public Object invoke(MethodInvocation methodInvocation) throws Throwable {

  if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {

   return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";

 }

 //将要调用的方法封装为 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("Invocation of method [" +

    methodInvocation.getMethod() +

    "] failed in HTTP invoker remote service at [" + getServiceUrl()

    + "]", ex);

  }

 }

}

函数主要有 3 个步骤。

(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("Sending HTTP invoker request for service at [" +

   config.getServiceUrl() +

"], with size " + baos.size());

 }

  return doExecuteRequest(config, baos);

}

在 doExecuteRequest 方法中真正实现了对远程方法的构造与通信,与远程方法的连接功能实现中,Spring 引入了第三方 JAR:HttpClient。HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。对 HttpClient 的具体使用方法有兴趣的读者可以参考更多的资料和文档。

protected RemoteInvocationResult doExecuteRequest(

HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)

throws IOException, ClassNotFoundException {

 //创建 HttpPost

  HttpPost postMethod = createHttpPost(config);

 //设置含有方法的输出流到 post 中

  setRequestBody(config, postMethod, baos);

  try {

  //执行方法并等待结果响应

   HttpResponse response = executeHttpPost(config, getHttpClient(), postMethod);

  //验证

   validateResponse(config, response);

  //提取返回的输入流

   InputStream responseBody = getResponseBody(config, response);

  //从输入流中提取结果

   return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());

 }

  finally {

   if (releaseConnectionMethod != null){

    ReflectionUtils.invokeMethod(releaseConnectionMethod, postMethod);

  }

 }

}

接下来我们逐步分析客户端实现的逻辑。

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(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.

  toLanguageTag(locale.getLocale()));

 }

  if (isAcceptGzipEncoding()) {

  //加入 Accept-Encoding 属性

   postMethod.addRequestHeader(HTTP_HEADER_ACCEPT_ENCODING, 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 类型为 application/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(

"Did not receive successful HTTP response: status code = " +

   postMethod.getStatusCode() +

   ", status message = [" + postMethod.getStatusText() + "]");

 }

}

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

 }

}

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。