Spring RestTemplate 详解

原创
2016/12/07 18:30
阅读数 2.7W

1、基本概念

Spring RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率,所以很多客户端比如 Android或者第三方服务商都是使用 RestTemplate 请求 restful 服务。

 

2、RestTemplate 调用流程

调用 RestTemplate 的默认构造函数,RestTemplate 对象在底层通过使用 java.net 包下的实现创建 HTTP 请求,可以通过使用 ClientHttpRequestFactory 指定不同的HTTP请求方式。默认使用 SimpleClientHttpRequestFactory,是 ClientHttpRequestFactory 实现类。如下流程:

1)使用默认构造方法new一个实例

RestTemplate template = new RestTemplate();

2)RestTemplate 内部通过调用 doExecute 方法,首先就是获取 ClientHttpRequest

 

3)RestTemplate 实现了抽象类 HttpAccessor ,所以可以调用父类的 createRequest

private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

public ClientHttpRequestFactory getRequestFactory() {

return this.requestFactory;

}

protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {

ClientHttpRequest request = getRequestFactory().createRequest(url, method);

if (logger.isDebugEnabled()) {

logger.debug("Created " + method.name() + " request for \"" + url + "\"");

}

return request;

}

 

4)SimpleClientHttpRequestFactory 实现了 ClientHttpRequest,同时实现方法

注意 bufferRequestBody 是可以在 RestTemplate 设置,是标志是否使用缓存流的形式,默认是 true,缺点是当发送大量数据时,比如put/post的保存和修改,那么可能内存消耗严重。所以这时候可以设置 RestTemplate.setBufferRequestBody(false);

即使用 SimpleStreamingClientHttpRequest 来实现。

 

5)openConnection 没什么文章,而是 prepareConnection 则是大有文章,这里我们分两个版本来说,因为我们一开始使用 4.1.1 的时候不能使用带请求体的delete,可是在 4.3.2 版本则可以使用,所以特别区分了这两个版本的代码,如下:

SimpleClientHttpRequestFactory -- 4.1.1 版本的代码默认

delete connection.setDoOutput = fase

如果设置false,然后后面又去获取输出流时,会发生如下错误 sun 包的 HttpURLConnection

if(!this.doOutput) {

throw new ProtocolException(

"cannot write to a URLConnection if doOutput=false - call setDoOutput(true)"

);

}

 

SimpleClientHttpRequestFactory -- 4.3.2 版本的代码默认

delete connection.setDoOutput = fase

DoOutput 的属性作用是可以使用 conn.getOutputStream().write() ,这样就能发送请求体了

 

6)接着执行 requestCallback.doWithRequest(request);

RequestCallback 封装了请求体和请求头对象,也就是说在该对象里面可以拿到我们需要的请求参数,在执行 doWithRequest 时,有一个非常重要的步骤,他和前面Connection发送请求体有着密切关系,我们知道请求头就是 SimpleBufferingClientHttpRequest.addHeaders 方法,那么请求体 bufferedOutput 是如何赋值的呢?就是在 doWithRequest 里面,如下 StringHttpMessageConverter (其他 MessageConvert 也一样,这里也是经常乱码的原因)

其中 s 就是请求体,HttpOutputMessage 对象就是我们准备的 ClientHttpRequest 对象,也就是上面的 SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest

这样,先调用父类的流方法,把内容写入流中,然后调用父类的 executeInternal方法在调用自身的该方法 executeInternal ,如下一步

 

7)接着执行 response = request.execute();

然后使用实例 SimpleBufferingClientHttpRequest 封装请求体和请求头

SimpleBufferingClientHttpRequest -- 4.1.1 版本的代码默认

delete 时通过前面设置的 DoOutput 参数和是否可以设置输出流来判断是否需要发送请求体

如果是 delete 请求,那么很明显 DoOutput = false,所以不会有封装请求体的过程,即不执行

FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());

所以服务端无法获取到请求体,会出现 HttpMessageNotReadableException: Required request body is missing

 

SimpleBufferingClientHttpRequest -- 4.3.2 版本的代码默认

delete 时通过请求方式和是否有请求体对象来判断是否需要发送请求体

如果是delete请求,首先设置 DoOutput = true,然后根据是否有请求体数据,然后封装请求体

FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());

 

8)最后解析response

接着就是 response 的解析了,主要还是 Error 的解析。

handleResponseError(method, url, response);

 

3、RestTemplate 的配置项

1)setBufferRequestBody 是否是否缓冲流来存储请求体,默认true

2)setProxy 设置代理对象

3)setChunkSize 设置每次传输字节长度,与 setBufferRequestBody(false) 结合使用

4)setConnectTimeout 设置连接超时时间,默认 -1

5)setReadTimeout 设置读取内容超时时间,默认 -1

6)setOutputStreaming 设置Connection是否设置输出流程

7)setTaskExecutor 设置异步回调执行器

 

4、RestTemplate 设置 RequestFactory

其实任何有连接的地方都会有连接池的概念,比如数据库连接等,这里也不例外,肯定也会有,RestTemplate 默认有两种工厂对象实现方式,都是 ClientHttpRequestFactory 的子类。如下

1)SimpleClientHttpRequestFactory 底层使用 java.net.HttpUrlConnection,可配置证书

2)HttpComponentsClientHttpRequestFactory 底层使用Apache HttpClient访问远程的Http服务,使用HttpClient同样可以配置连接池和证书等信息,而且功能更强大,配置项更多。

 

5、RequestFactory 的配置方式

1)使用XML配置,就是配置JavaBean

2)使用代码配置,就是初始化这个对象

无论上面那种方式配置,都是配置外壳 RestTemplate,真正发送请求的 request 对象其实都是由工厂管理的,所以我们不关心连接池的管理,只是配置连接池初始化的一些参数而已。

这个可以参考:

http://www.open-open.com/lib/view/open1436018677419.html

 

6、请求参数的传递

 

7、关于网上说的无法发送delete请求体

HttpMessageNotReadableException: Required request body is missing

Spring MVC 的 @RequestBody 只支持RestTemplate 的 POST 和 PUT

但是 RestTemplate 的 delete 方法并不支持传入请求体(Request Body)。经测试,通过调用 RestTemplate 类的exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<ResponseResult> responseType, Object... uriVariables)  方法,将 method 指定为 org.springframework.http.HttpMethod.DELETE,并传入 requestEntity(请求体) 对象时,在服务端得到的 Request Body 仍然为 null。可见 RestTemplate 默认并不支持对 DELETE 方法使用请求体。

    通过查阅资料发现 RestTemplate 默认是使用 spring 自身的 SimpleClientHttpRequestFactory 创建请求对象和对其进行相关设置(如请求头、请求体等),它只支持 PUT 和 POST 方法带请求体,RestTemplate 的 DELETE 方法不支持传入请求体是因为 JDK 中 HttpURLConnection 对象的 delete 方法不支持传入请求体(如果对 HttpURLConnection 对象的 delete 方法传入请求体,在运行时会抛出 IOException)。

从代码中也看到了 Spring 对 delete 做了判断,如果是 4.1.1 及以前的版本,确实是会出现上面的问题,但是当我使用 4.3.2 之后的版本,发现完全可以发送请求体,这里面的变化就是前者在代码中把请求体过滤掉了,后者把请求体加上了。至于更细的细节,希望有人能够继续深究下去。

 

展开阅读全文
打赏
4
16 收藏
分享
加载中
对我有用
2018/01/11 15:01
回复
举报
learn_more博主

引用来自“kedadiannao220”的评论

```
ResponseEntity<MixResponse> exchange = RestfulUtil.getDeleteRestTemplate()
.exchange(url, HttpMethod.DELETE, new HttpEntity(param), MixResponse.class);
```
我现在都是直接使用 4.3 以上的版本了,直接使用 exchange , 不再考虑 delete 无法发送 body 体的问题
2017/05/02 09:39
回复
举报
```
ResponseEntity<MixResponse> exchange = RestfulUtil.getDeleteRestTemplate()
.exchange(url, HttpMethod.DELETE, new HttpEntity(param), MixResponse.class);
```
2017/04/27 17:35
回复
举报
4.2.7的spring web也是无法传入body信息的;修改resttemplate的requestFactory
public static RestTemplate getDeleteRestTemplate() {
RestTemplate restTemplate = new RestTemplate()
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory() {
@Override
protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
if (HttpMethod.DELETE == httpMethod) {
return new HttpEntityEnclosingDeleteRequest(uri);
}
return super.createHttpUriRequest(httpMethod, uri);
}
});
return restTemplate;
}

public static class HttpEntityEnclosingDeleteRequest extends HttpEntityEnclosingRequestBase {

public HttpEntityEnclosingDeleteRequest(final URI uri) {
super();
setURI(uri);
}
@Override
public String getMethod() {
return "DELETE";
}
}

2017/04/27 17:34
回复
举报
更多评论
打赏
4 评论
16 收藏
4
分享
返回顶部
顶部