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 之后的版本,发现完全可以发送请求体,这里面的变化就是前者在代码中把请求体过滤掉了,后者把请求体加上了。至于更细的细节,希望有人能够继续深究下去。
来源:oschina
链接:https://my.oschina.net/u/1989321/blog/801653