项目中经常需要请求别的服务的HTTP接口,经常用到OkHttp或者HttpClient等,经别人推荐,发现Spring提供的RestTemplate也是挺方便的,对HTTP请求技术框架(如OkHttp/HttpClient)又进行了再一层的封装。如果不了解RestTemplate的原理,用起来总是出问题,此文章会深入的分析RestTemplate的原理,以及探究如何使用。
//构造HttpClient
PoolingHttpClientConnectionManager pollingConnectionManager =
new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
pollingConnectionManager.setMaxTotal(1000);
pollingConnectionManager.setDefaultMaxPerRoute(1000);
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setConnectionManager(pollingConnectionManager);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true));
httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
httpClientBuilder.setDefaultHeaders(headers);
HttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
clientHttpRequestFactory.setConnectTimeout(5 * 1000);
clientHttpRequestFactory.setReadTimeout(5 * 1000);
clientHttpRequestFactory.setConnectionRequestTimeout(5 * 1000);
//定义消息转换器
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
//构造restTemplate,指定消息转换器/http客户端工厂和错误处理器
restTemplate = new RestTemplate(messageConverters);
restTemplate.setRequestFactory(clientHttpRequestFactory);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
// GET请求,返回body转换成String
restTemplate.getForObject(url, String.class);
// POST请求,返回body转换成String,requestEntity中可设置请求头和请求body
restTemplate.postForObject(url, requestEntity, String.class);
//exchange方法可以是任意类型的HTTP方法,返回ResponseEntity类型,可获取response的更多信息
restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class).getBody();
可以看出,主要做了四步操作,接下来逐步分析
uriVariables可以是Object…,也可以是Map
String url = "http://xxx/{id}";
String result = restTemplate.exchange(url, HttpMethod.GET,
new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class,"1").getBody();
expand后的URI是http://xxx/1
初始化RestTemplates时设置了ClientHttpRequestFactory,此处会获取到ClientHttpRequestFactory来生成ClientHttpRequest。选用怎样的ClientHttpRequestFactory则确定了使用什么样的Http请求技术。
观察HttpEntityRequestCallback的doWithRequest方法
获取初始化时设置的Converter,逐个遍历,直至找到符合的Converter,则调用其write方法写到outputStream中,Converter的具体细节将在后面讲到。
此步实际是在调用各种Http请求技术,如HttpClient、okhttp的执行方法,具体使用哪种技术,由初始化时传入的ClientHttpRequestFactory确定。
与4.3类似,也是先取出所有converter进行遍历,然后匹配canRead的converter,然后进行数据读取
深入分析中经常出现converter,究竟converter有什么神奇的作用,应该如何设置converter呢,此处对一些常用的converter进行分析。
StringHttpMessageConverter比较简单,以下是检查canWrite的过程
public StringHttpMessageConverter(Charset defaultCharset) {
super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
}
public boolean supports(Class<?> clazz) {
return String.class == clazz;
}
从StringHttpMessageConverter的初始化方法和support方法可知,其支持String类型的对象,以及 MediaType.TEXT_PLAIN, MediaType.ALL这两个mediaType,即所有类型的mediaType都支持。
canRead方法也类似,此处不再赘述,任意类型的mediaType以及返回类型为String时,则StringHttpMessageConverter的canRead方法返回为true。
StringHttpMessageConverter的readInternal和writeInternal方法也比较简单,就是以某种指定的charset将字符串写进流里。charset在初始化StringHttpMessageConverter时指定,如果不指定,默认是StandardCharsets.ISO_8859_1。
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
return StreamUtils.copyToString(inputMessage.getBody(), charset);
}
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
if (this.writeAcceptCharset) {
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
}
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
StreamUtils.copy(str, charset, outputMessage.getBody());
}
MediaType.All
MediaType.APPLICATION_FORM_URLENCODED
MediaType.MULTIPART_FORM_DATA
public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (!isMultipart(map, contentType)) {
writeForm((MultiValueMap<String, String>) map, contentType, outputMessage);
}
else {
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
}
}
writeForm方法比较简单,将map拼接成key1=value1&key2=value2格式的字符串,然后写到流里。
writeMultipart则比较复杂,其body格式如下
------WebKitFormBoundarywcZ8mD7QHKl1baDP
Content-Disposition: form-data; name="index"
1
------WebKitFormBoundarywcZ8mD7QHKl1baDP
Content-Disposition: form-data; name="file"; filename="xxx.png"
Content-Type: image/png
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
------WebKitFormBoundarywcZ8mD7QHKl1baDP
FormHttpMessageConverter会生成这个boundary,然后逐个part逐个part的写入到流中,最后再写一个boundary来标识结束。
MuiltValueMap中的每一个值会作为一个part,根据值的类型来选择对应的partConverter来进行转换,如果需要指定每个part的header,可以使用类型为HttpEntity的值,实际使用哪个converter由HttpEntity的body的类型决定。
FormHttpMessageConverter默认是支持这三种partConverter,所以当使用multipart/form-data时,multiValueMap中的值必须为String、byte[]、Resource或者HttpEntity类型,但是HttpEntity中的T也必须为String、byte[]、Resource这三种之一。
this.partConverters.add(new ByteArrayHttpMessageConverter());
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
String useStringConvertor() {
String url = "http://xxx";
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
JSONObject params = new JSONObject();
params.put("name", "小强");
params.put("phone", "13595688956");
//自行将body转成string,使用StringHttpMessageConverter
String result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<String>(params.toJSONString(), headers), String.class).getBody();
return result;
}
以上写法,是自行将对象转化为application/json形式的字符串,使用StringHttpMessageConverter写到请求的outputStream中,更优的是直接使用MappingJackson2HttpMessageConverter,无需手动将对象转换成json格式,以免传入错误的格式。
String useJsonConverter() {
String url = "http://xxx";
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
//此处使用对象/Map/JSONObject都可以
Map<String, Object> params = new HashMap<>();
params.put("name", "小强");
params.put("phone", "13595688956");
//MappingJackson2HttpMessageConverter会使用jackson将对象转成json字符串
String result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<Map<String, Object>>(params, headers), String.class).getBody();
return result;
}
当需要传文件时经常会使用multipart/form-data格式的body
String useResourceHttpMessageConverter() {
String url = "http://xxx";
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("file", new FileSystemResource(new File("C:\\xxx.png")));
String result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class).getBody();
return result;
}
如上,会先调用到FormHttpMessageConverter的转换方法,然后再使用ResourceHttpMessageConverter的转换方法来实现params到body内容的转换。
也可以使用ByteArrayHttpMessageConverter。
String useByteArrayHttpMessageConverter() throws IOException {
String url = "http://xxx";
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("file", new ByteArrayResource(FileUtils.readFileToByteArray(new File("C:\\xxx.png"))) {
@Override
public String getFilename() {
return "test.png";
}
});
String result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class).getBody();
return result;
}
为每一个部分设定header
String useResourceHttpMessageConverter() {
String url = "http://xxx";
//设置上传文件的header
MultiValueMap<String, String> fileHeaders = new LinkedMultiValueMap<>();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_JPEG_VALUE);
HttpEntity<Resource> fileEntity= new HttpEntity<Resource>(
new FileSystemResource(new File("C:\\xxx.png")), fileHeaders);
//整个请求的header
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);
//参数使用HttpEntity类型,使得可以指定某个part的header
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("file", fileEntity);
String result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class).getBody();
return result;
}
public byte[] download(String url) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.ALL));
HttpEntity<byte[]> requestEntity = new HttpEntity<byte[]>(headers);
return restTemplate.exchange(url, HttpMethod.GET, requestEntity, byte[].class).getBody();
}
如上写法使用ByteArrayHttpMessageConverter