glmapper

聊一聊 RestTemplate

字数统计: 1.9k阅读时长: 6 min
2018/11/10 Share

最近这段时间用了下 RestTemplate 这个类,抽点时间总结下一些东西,希望对大家有所帮助。

从 3.0 版本开始,Spring 提供了 RestTemplate 作为用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率。

本篇文章将从 RestTemplate 提供的 API 入手,先来了解下 RestTemplate 的具体使用,然后再对其中涉及到的几个核心类进行分析,最后再来分析下 RestTemplate 执行的整个流程,篇幅比较长,建议先码为快!

核心 API

在平时的使用中,我们通常都是使用包装好的getForObject/getForEntity,postForObject/postForEntity/postForLocation,put以及delete。

get 请求处理

getForEntity方法的返回值是一个ResponseEntity,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。

  • url:调用的服务的地址
  • responseType:返回的body类型
  • uriVariables:有两种形式:
    • 可以用一个数字做占位符,最后是一个可变长度的参数,来一一替换前面的占位符
    • 也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值

responseType 测试案例

定义的一个controller资源:


这里分别使用不同的 responseType 进行测试:

结果:

1
2
getForEntity(responseType=Map.class):{glmapper=hello glmapper}
getForEntity(responseType=String.class):{"glmapper":"hello glmapper"}

uriVariables 测试案例

先来看下非map方式的,两个controller,两种不同方式的参数获取(本质上是一样的)

  • 使用占位符的方式:

  • 使用 map 的方式:

getForObject

getForObject 函数实际上是对 getForEntity 函数的进一步封装,如果只关注返回的消息体的内容,对其他信息都不关注,那么就可以使用 getForObject。

这里调用就比getForEntity要简单一点了,可以直接拿到对象:

getForObject 的几个重载方法和 getForEntity 基本是一样的。

post 请求处理

在RestTemplate中,POST请求可以通过如下三个方法来发起:postForEntity,postForObject,postForLocation。

postForEntity 案例


调用获取:

1
postForEntity(URI url, @Nullable Object request, Class<T> responseType)
  • 方法的第一参数表示要调用的服务的地址
  • 方法的第二个参数表示上传的参数
  • 方法的第三个参数表示返回的消息体的数据类型

postForObject 案例

和 getForObject 相对应,只关注返回的消息体。

postForLocation 案例

postForLocation也是提交新资源,提交成功之后,返回新资源的URI,postForLocation的参数和前面两种的参数基本一致,只不过该方法的返回值为Uri,这个只需要服务提供者返回一个Uri即可,该Uri表示新资源的位置。

这里有点坑,我们需要把这个uri添加到response的header中,不然后面拿到的是null。

exchange

exchange 方法和上述这些方法差别在于需要多一个请求类型的参数:

AsyncRestTemplate 异步客户端

RestTemplate的异步实现方式。所涉及到的API和RestTemplate基本一致。区别在于RestTemplate直接返回结果,而AsyncRestTemplate返回的是ListenableFuture。

RestTemplate 拦截器

Spring提供了ClientHttpRequestInterceptor和AsyncClientHttpRequestInterceptor两个接口,分别可以对RestTemplate和AsyncRestTemplate发起的请求进行拦截,并在其被发送至服务端之前修改请求或是增强相应的信息。

  • ClientHttpRequestInterceptor 拦截 RestTemplate

  • AsyncClientHttpRequestInterceptor 拦截AsyncRestTemplate

设置拦截器就是通过提供的 setInterceptors 设置即可:

自定义 ResponseErrorHandler

ResponseErrorHandler 接口定义了当response发生错误时需要进行的操作。这里我们自定义一个CustomResponseErrorHandler,当返回的code不是200时,就表示执行出错了。

设置 ResponseErrorHandler:

执行结果:

处理流程

下面来梳理下 RestTemplate 中请求处理的流程。下图中 XXXX 表示我们调用的 API 方法。大体流程就是:api 内部做一些请求相关的处理封装,然后交给 execute 方法执行,最后真正处理则是在 doExecute 方法中完成。

下面以 getForEntity 方法的执行过程来分析:

getForEntity 方法:

  • 基于给定响应类型,返回一个请求回调实现,准备请求。
  • 基于给定响应类型,返回 ResponseEntity 的响应提取器。


execute 方法:

  • 这个方法里面是对url进行urlencode编码处理的,统一转为URL。这里我们也可以手动把参数进行网络编码。


doExecute是请求真正处理的方法,这里来重点看下这个方法的执行过程:

  • createRequest
  • doWithRequest
  • execute
  • handleResponse

1、createRequest

这个方法的作用就是创建一个 ClientHttpRequest 对象。RestTemplate集成了 HttpAccessor这个抽象类,创建ClientHttpRequest的过程就是在其父类HttpAccessor中通过默认的 ClientHttpRequestFactory 实现类 SimpleClientHttpRequestFactory 完成具体的请求创建。

  • 1、创建 java.net.HttpURLConnection 对象

  • 2、设置 connection,包括 connectTimeout、setDoInput 等。

  • 3、bufferRequestBody 用于标志是否使用缓存流的形式,默认是 true。缺点是当发送大量数据时,比如 put/post,存在内存消耗严重。该值可以通过 SimpleClientHttpRequestFactory#setBufferRequestBody来修改。

不同版本的变更还是比较大的,大家在阅读源码时,还是从最新的代码来看。

2、doWithRequest

RequestCallback 封装了请求体和请求头对象。这里会遍历所有的 HttpMessageConverter,解析成所有支持的MediaType,放在allSupportedMediaTypes中。

1
request.getHeaders().setAccept(allSupportedMediaTypes);

RestTemplate中对应了两个内部类的实现:

  • AcceptHeaderRequestCallback.doWithRequest的处理。
    发送请求时,Http头部需要设置Accept字段,该字段表明了发送请求的这方接受的媒体类型(消息格式),也是响应端要返回的信息的媒体类型(消息格式)。
    根据postForEntity方法的第三个参数responseType,程序将选择适合的解析器XXXConverter,并依据该解析器找出所有支持的媒体类型。

    • HttpEntityRequestCallback.doWithRequest的处理。
      如果是POST请求并且消息体存在时,除了设置Accept字段,还可能需要设置Content-Type字段,该字段表明了所发送请求的媒体类型(消息格式),也是响应端接受的媒体类型(消息格式)。
      根据postForEntity方法的第二个参数request,程序将选择适合的解析器XXXConverter,将请求消息写入输出流。

3、execute

这里会把请求头/体封装到connect,然后发送请求。跟踪 execute 方法执行,定位到SimpleBufferingClientHttpRequest#executeInternal方法:


这里是使用实例 SimpleBufferingClientHttpRequest 封装请求体和请求头。从代码中可以看到:

  • delete 时通过前面设置的 DoOutput参数和是否可以设置输出流来判断是否需要发送请求体如果是 delete 请求,那么很明显 DoOutput = false,不会有封装请求体的过程,即不执行FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream())。

4、handleResponse

最后就是 response 的解析了,从代码来看,主要还是 Error 的解析。这里的ErrorHandler我们前面也提到,可以通过实现 ResponseErrorHandler 来自定义 异常处理。

小结

本篇先介绍了RestTemplate的API使用,挑了几个介绍了下,更多使用细节还是要针对不同的场景来决定。接着对拦截器,异步RestTemplate以及错误处理器做了简单的介绍并给出了案例。最后分析了下RestTemplate的执行流程,篇幅原因执行流程部分只是大概捋了捋,其中还是很多细节有时间再补充,这部分主要就是看底层是如何通信的,已经请求参数的传递等。

原文作者:GuoLei Song

原文链接:http://www.glmapper.com/2018/11/10/topic-resttemplate/

发表日期:November 10th 2018, 12:24:19 pm

更新日期:December 14th 2019, 11:07:47 am

版权声明:转载请注明出处

CATALOG
  1. 1. 核心 API
    1. 1.1. get 请求处理
      1. 1.1.1. responseType 测试案例
      2. 1.1.2. uriVariables 测试案例
    2. 1.2. getForObject
    3. 1.3. post 请求处理
      1. 1.3.1. postForEntity 案例
      2. 1.3.2. postForObject 案例
      3. 1.3.3. postForLocation 案例
    4. 1.4. exchange
  2. 2. AsyncRestTemplate 异步客户端
  3. 3. RestTemplate 拦截器
  4. 4. 自定义 ResponseErrorHandler
  5. 5. 处理流程
    1. 5.0.1. 1、createRequest
    2. 5.0.2. 2、doWithRequest
    3. 5.0.3. 3、execute
    4. 5.0.4. 4、handleResponse
  • 6. 小结