logo头像

待到风起时,扬帆济沧海

RestTemplate使用技巧

本文于574天之前发表,文中内容可能已经过时。

1. 规范背景

1.1. http client选择

  • 如无特殊情况(比如:单机tps上千),建议选Spring Rest Template做门面,Apache HttpClient 4.x做实现

1.2. rest template 运行环境

  • jdk 1.8

  • spring boot项目

2. 配置 rest template

2.1. 引入jar包

  • Spring Rest Template在spring-web模块中内置了,spring boot会自动帮你引进来,因此无需再引入

  • 引入Apache HttpClient 4.x包:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<!-- 如果不配异步(AsyncRestTemplate),则不需要这个依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.5.Final</version>
</dependency>

2.2. 编写 yml 文件配置(可选)

1
2
3
4
5
6
7
8
9
10
11
# yml配置的优先级高于java配置;如果yml配置和java配置同时存在,则yml配置会覆盖java配置
####restTemplate的yml配置开始####
---
spring:
restTemplate:
maxTotalConnect: 1000 #连接池的最大连接数,0代表不限;如果取0,需要考虑连接泄露导致系统崩溃的后果
maxConnectPerRoute: 200
connectTimeout: 3000
readTimeout: 5000
charset: UTF-8
####restTemplate的 yml配置开始####

2.3. 编写java配置(必备,不可省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//xxx代表你的项目,例如:
//com.douyu.wsd.adx.gateway.config
//com.douyu.wsd.venus.config
//可以写一级,也可以写多级,具体自己随意
package com.douyu.wsd.xxx.config;



import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;

import org.apache.http.Header;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

@Configuration
@ConfigurationProperties(prefix = "spring.restTemplate")
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class RestTemplateConfiguration {

// java配置的优先级低于yml配置;如果yml配置不存在,会采用java配置
// ####restTemplate的 java配置开始####

private int maxTotalConnection = 500; //连接池的最大连接数

private int maxConnectionPerRoute = 100; //同路由的并发数

private int connectionTimeout = 2 * 1000; //连接超时,默认2s

private int readTimeout = 30 * 1000; //读取超时,默认30s

private String charset = "UTF-8";

// ####restTemplate的 java配置结束####

public void setMaxTotalConnection(int maxTotalConnection) {
this.maxTotalConnection = maxTotalConnection;
}

public void setMaxConnectionPerRoute(int maxConnectionPerRoute) {
this.maxConnectionPerRoute = maxConnectionPerRoute;
}

public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}

public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}

public void setCharset(String charset) {
this.charset = charset;
}

//创建HTTP客户端工厂
@Bean(name = "clientHttpRequestFactory")
public ClientHttpRequestFactory clientHttpRequestFactory() {
return createClientHttpRequestFactory(this.connectionTimeout, this.readTimeout);
}

//初始化RestTemplate,并加入spring的Bean工厂,由spring统一管理
@Bean(name = "restTemplate")
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return createRestTemplate(factory);
}

//初始化支持异步的RestTemplate,并加入spring的Bean工厂,由spring统一管理
//如果你用不到异步,则无须创建该对象
@Bean(name = "asyncRestTemplate")
@ConditionalOnMissingBean(AsyncRestTemplate.class)
public AsyncRestTemplate asyncRestTemplate(RestTemplate restTemplate) {
final Netty4ClientHttpRequestFactory factory = new Netty4ClientHttpRequestFactory();
factory.setConnectTimeout(this.connectionTimeout);
factory.setReadTimeout(this.readTimeout);
return new AsyncRestTemplate(factory, restTemplate);
}

private ClientHttpRequestFactory createClientHttpRequestFactory(int connectionTimeout, int readTimeout) {
//maxTotalConnection 和 maxConnectionPerRoute 必须要配
if (this.maxTotalConnection <= 0) {
throw new IllegalArgumentException("invalid maxTotalConnection: " + maxTotalConnection);
}
if (this.maxConnectionPerRoute <= 0) {
throw new IllegalArgumentException("invalid maxConnectionPerRoute: " + maxTotalConnection);
}

//全局默认的header头配置
List<Header> headers = new LinkedList<>();
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6"));

//禁用自动重试,需要重试时,请自行控制
HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(0, false);

//创建真正处理http请求的httpClient实例
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultHeaders(headers)
.setRetryHandler(retryHandler)
.build();

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
httpClient);
factory.setConnectTimeout(connectionTimeout);
factory.setReadTimeout(readTimeout);
return factory;
}

private RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);

//我们采用RestTemplate内部的MessageConverter
//重新设置StringHttpMessageConverter字符集,解决中文乱码问题
modifyDefaultCharset(restTemplate);

//设置错误处理器
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());

return restTemplate;
}

private void modifyDefaultCharset(RestTemplate restTemplate) {
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (StringHttpMessageConverter.class == item.getClass()) {
converterTarget = item;
break;
}
}
if (null != converterTarget) {
converterList.remove(converterTarget);
}
Charset defaultCharset = Charset.forName(charset);
converterList.add(1, new StringHttpMessageConverter(defaultCharset));
}

}

做完上述配置,就生成了可用的RestTemplate实例

采用上述配置,可以做到开箱即用;自己配,可能会踩些坑,比如:spring boot 配置技巧

3. rest template基本用法

3.1. get演示

  • 演示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import com.douyu.wsd.framework.common.lang.StringUtils;

import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class TestController {

@Resource
private RestTemplate restTemplate;

@Resource
private AsyncRestTemplate asyncRestTemplate;

//最简单的get操作
@RequestMapping("/get")
public String testGet(String keyword) throws Exception {
String kw = StringUtils.defaultString(URLEncoder.encode(keyword, "UTF-8"));
String html = restTemplate.getForObject("https://www.douyu.com/search/?kw=" + kw, String.class);
return html;//返回的是斗鱼主站的html
}

//需要自定义header头的get操作
@RequestMapping("/get2")
public String testGet2(String keyword) throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.set("MyHeaderKey", "MyHeaderValue");
HttpEntity entity = new HttpEntity(headers);

String kw = StringUtils.defaultString(URLEncoder.encode(keyword, "UTF-8"));
ResponseEntity<String> response = restTemplate.exchange("https://www.douyu.com/search/?kw=" + kw, HttpMethod.GET, entity, String.class);
return response.getBody();//返回的是斗鱼主站的html
}

}
  • 实际效果:

图片名称

3.2. post表单演示

  • 演示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import com.douyu.wsd.framework.common.lang.StringUtils;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import com.google.common.collect.ImmutableMap;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class TestController {

@Resource
private RestTemplate restTemplate;

@RequestMapping("/postForm")
public String testPostForm(String posid) throws Exception {//测试用例:posid=804009
String url = "http://www.douyu.com/lapi/sign/app/getinfo?aid=android1&client_sys=android&mdid=phone&time=1524495658&token=&auth=789c4f732d6aa4d0a5c8fb33765af8cf";
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.add("app", "{\"aname\":\"斗鱼直播\",\"pname\":\"air.tv.douyu.android\"}");
form.add("mdid", "phone");
form.add("cate1", "0");
form.add("client_sys", "ios");
form.add("cate2", "0");
form.add("auth", "789c4f732d6aa4d0a5c8fb33765af8cf");
form.add("roomid", "0");
form.add("posid", posid);
form.add("imei", "863254010282712");

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//headers.add("xx", "yy");//可以加入自定义的header头
HttpEntity<MultiValueMap<String, String>> formEntity = new HttpEntity<>(form, headers);
String json = restTemplate.postForObject(url, formEntity, String.class);
return json;//返回的是广告api的json
}
}
  • 实际效果:

图片名称

  • 与postman的对应关系

图片名称

3.3. post请求体演示

  • 演示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import com.douyu.wsd.framework.common.lang.StringUtils;

import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class TestController {

@Resource
private RestTemplate restTemplate;

@RequestMapping("/postBody")
public String testPostBody() throws Exception {
String url = "https://venus.dz11.com/venus/release/pc/checkUpdate";
String jsonBody = "{\n"
+ " \"channelCode\": \"official\",\n"
+ " \"appCode\": \"Douyu_Live_PC_Client\",\n"
+ " \"versionCode\": \"201804121\",\n"
+ " \"versionName\": \"V5.1.9\",\n"
+ " \"deviceUid\": \"02-15-03-59-5C-E2\",\n"
+ " \"deviceResolution\": \"1920*1080\",\n"
+ " \"token\": \"token\",\n"
+ " \"webView\": \"\",\n"
+ " \"osInfo\": \"10.0\",\n"
+ " \"osType\": \"64\",\n"
+ " \"cpuInfo\":\n"
+ " {\n"
+ " \"OemId\": \"0\",\n"
+ " \"ProcessorArchitecture\": \"0\",\n"
+ " \"PageSize\": \"4096\",\n"
+ " \"MinimumApplicationAddress\": \"00010000\",\n"
+ " \"MaximumApplicationAddress\": \"7FFEFFFF\",\n"
+ " \"ActiveProcessorMask\": \"15\",\n"
+ " \"NumberOfProcessors\": \"4\",\n"
+ " \"ProcessorType\": \"586\",\n"
+ " \"AllocationGranularity\": \"65536\",\n"
+ " \"ProcessorLevel\": \"6\",\n"
+ " \"ProcessorRevision\": \"40457\"\n"
+ " },\n"
+ " \"diskInfo\": \"931.507GB\",\n"
+ " \"memoryInfo\": \"15.8906GB\",\n"
+ " \"driveInfo\": \"Intel(R) HD Graphics 630:23.20.16.4973;\",\n"
+ " \"startTime\": \"-501420357\"\n"
+ "}\n";

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//headers.add("xx", "yy");//可以加入自定义的header头
HttpEntity<String> bodyEntity = new HttpEntity<>(jsonBody, headers);

//1.直接拿原始json串
String json = restTemplate.postForObject(url, bodyEntity, String.class);

//2.将原始的json传转成java对象,rest template可以自动完成
ResultVo resultVo = restTemplate.postForObject(url, bodyEntity, ResultVo.class);
if (resultVo != null && resultVo.success()) {
Object res = resultVo.getData();//data节点的实际类型是java.util.LinkedHashMap
logger.info("处理成功,返回数据: {}", resultVo.getData());
} else {
logger.info("处理失败,响应结果: {}", resultVo);
}

return json;//返回的是分包api的json
}
}
  • 实际效果

图片名称

  • 与postman的对应关系

图片名称

3.4. post文件上传

场景说明:只适合小文件(20MB以内)上传

  • 演示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import com.douyu.wsd.framework.common.codec.CodecUtils;
import com.douyu.wsd.framework.common.lang.StringUtils;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class TestController {

@Resource
private RestTemplate restTemplate;

@RequestMapping("/postFile")
public String testPostBody() throws Exception {
String filePath = "D:/config.png";

//通过磁盘文件上传,如果产生了临时文件,一定要记得删除,否则,临时文件越积越多,磁盘会爆
FileSystemResource resource = new FileSystemResource(new File(filePath));

String url = "http://dev.resuploader.dz11.com/Resource/Dss/put";
String appId = "***";//测试的时候换成自己的配置
String secureKey = "***";
String time = String.valueOf(System.currentTimeMillis());
String pubStr = "1";
String tempStr = String.format("app_id=%s&is_public=%s&time=%s&vframe=0%s", appId, pubStr, time, secureKey);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("is_public", pubStr);
form.add("vframe", "0");
form.add("file", resource);
form.add("app_id", appId);
form.add("time", time);
form.add("sign", CodecUtils.md5(tempStr));

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
//headers.add("xx", "yy");//可以加入自定义的header头
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(form, headers);
String json = restTemplate.postForObject(url, formEntity, String.class);
return json;
}
}
  • 实际效果

图片名称

  • 与postman的对应关系

图片名称

3.5. 文件下载

场景说明:只适合小文件(10MB以内)下载

  • 演示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import com.douyu.wsd.framework.common.lang.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class TestController {

@Resource
private RestTemplate restTemplate;

@RequestMapping("/downloadFile")
public ResponseEntity testDownloadFile() throws Exception {
String url = "http://editor.baidu.com/editor/download/BaiduEditor(Online)_5-9-16.exe";
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM));
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.GET, entity, byte[].class);
byte[] bytes = response.getBody();
long contentLength = bytes != null ? bytes.length : 0;
headers.setContentLength((int) contentLength);
headers.setContentDispositionFormData("baidu.exe", URLEncoder.encode("百度安装包.exe", "UTF-8"));
return new ResponseEntity<>(response.getBody(), headers, HttpStatus.OK);
}
}
  • 实际效果

图片名称

3.6. 更多API

3.6.1. RestTemplate API 与http动词的对象关系:

HTTP动词 对应的RestTemplate API
DELETE delete(String, String…)
GET getForObject(String, Class, String…)
HEAD headForHeaders(String, String…)
OPTIONS optionsForAllow(String, String…)
POST postForLocation(String, Object, String…)
PUT put(String, Object, String…)

3.6.2. (post|get)ForEntity API 和 (post|get)ForObject 的区别

ForEntity API拿到的是ResponseEntity,通过ResponseEntity可以拿到状态码,response header等信息

ForObject API拿到的是java对象,用在不关心response状态码和header的场合中

3.6.3. getXXX、postXXX 和 exchange 方法的区别

getXXX、postXXX 用于比较简单的调用

exchange 用于比较复杂的调用

4. rest template高阶用法

4.1. 带泛型的响应解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

import com.douyu.wsd.framework.common.lang.StringUtils;

import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class TestController {

private static final Logger logger = LoggerFactory.getLogger(TestController.class);

@Resource
private RestTemplate restTemplate;

@RequestMapping("/postBody")
public String testPostBody() throws Exception {//测试用例:posid=804009
String url = "https://venus.dz11.com/venus/release/pc/checkUpdate";
String jsonBody = "{\n"
+ " \"channelCode\": \"official\",\n"
+ " \"appCode\": \"Douyu_Live_PC_Client\",\n"
+ " \"versionCode\": \"201804121\",\n"
+ " \"versionName\": \"V5.1.9\",\n"
+ " \"deviceUid\": \"02-15-03-59-5C-E2\",\n"
+ " \"deviceResolution\": \"1920*1080\",\n"
+ " \"token\": \"token\",\n"
+ " \"webView\": \"\",\n"
+ " \"osInfo\": \"10.0\",\n"
+ " \"osType\": \"64\",\n"
+ " \"cpuInfo\":\n"
+ " {\n"
+ " \"OemId\": \"0\",\n"
+ " \"ProcessorArchitecture\": \"0\",\n"
+ " \"PageSize\": \"4096\",\n"
+ " \"MinimumApplicationAddress\": \"00010000\",\n"
+ " \"MaximumApplicationAddress\": \"7FFEFFFF\",\n"
+ " \"ActiveProcessorMask\": \"15\",\n"
+ " \"NumberOfProcessors\": \"4\",\n"
+ " \"ProcessorType\": \"586\",\n"
+ " \"AllocationGranularity\": \"65536\",\n"
+ " \"ProcessorLevel\": \"6\",\n"
+ " \"ProcessorRevision\": \"40457\"\n"
+ " },\n"
+ " \"diskInfo\": \"931.507GB\",\n"
+ " \"memoryInfo\": \"15.8906GB\",\n"
+ " \"driveInfo\": \"Intel(R) HD Graphics 630:23.20.16.4973;\",\n"
+ " \"startTime\": \"-501420357\"\n"
+ "}\n";

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> bodyEntity = new HttpEntity<>(jsonBody, headers);

//1. 直接拿原始的json串
String json = restTemplate.postForObject(url, bodyEntity, String.class);

//2. 将原始json传转java对象,跟上文不同的是,这个java对象里面有泛型(ResultVo<PcUpdateRes>)
//大家实际使用的时候,把ResultVo<PcUpdateRes>换成自己的类,比如:List<MemberInfo>
ResponseEntity<ResultVo<PcUpdateRes>> response = restTemplate
.exchange(url, HttpMethod.POST, bodyEntity, new ParameterizedTypeReference<ResultVo<PcUpdateRes>>() {});
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null && response.getBody().success()) {
ResultVo<PcUpdateRes data = > resultVo = response.getBody();
PcUpdateRes data = resultVo.getData();
logger.info("处理成功,返回数据: {}", data);
} else {
logger.info("处理失败,响应结果: {}", response);
}

return json;
}
}

4.2. 上传文件流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import com.douyu.wsd.framework.common.codec.CodecUtils;
import com.douyu.wsd.framework.common.io.IOUtils;
import com.douyu.wsd.framework.common.lang.StringUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class TestController {

@Resource
private RestTemplate restTemplate;

@RequestMapping("/postFile")
public String testPostBody() throws Exception {
String filePath = "D:/config.png";
MultipartFileResource resource = new MultipartFileResource(new FileInputStream(new File(filePath)), "config.png");
String url = "http://dev.resuploader.dz11.com/Resource/Dss/put";
String appId = "***";//测试的时候换成自己的配置
String secureKey = "***";
String time = String.valueOf(System.currentTimeMillis());
String pubStr = "1";
String tempStr = String.format("app_id=%s&is_public=%s&time=%s&vframe=0%s", appId, pubStr, time, secureKey);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("is_public", pubStr);
form.add("vframe", "0");
form.add("file", resource);
form.add("app_id", appId);
form.add("time", time);
form.add("sign", CodecUtils.md5(tempStr));

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
//headers.add("xx", "yy");//可以加入自定义的header头
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(form, headers);
String json = restTemplate.postForObject(url, formEntity, String.class);
return json;
}

private class MultipartFileResource extends InputStreamResource {

private String filename;

public MultipartFileResource(InputStream inputStream, String filename) {
super(inputStream);
this.filename = filename;
}

@Override
public String getFilename() {
return this.filename;
}

@Override
public long contentLength() throws IOException {
return -1; // we do not want to generally read the whole stream into memory ...
}
}
}

4.3 异步操作

  • AsyncRestTemplate 可支持异步,与同步API基本一致,返回的是future:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.douyu.wsd.framework.common.lang.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;

import org.springframework.http.ResponseEntity;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.AsyncRestTemplate;

@RestController
public class TestController {

@Resource
private AsyncRestTemplate asyncRestTemplate;

@RequestMapping("/douyu")
public String douyu() throws Exception {
ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate
.getForEntity("http://www.douyu.com", String.class);
return future.get(2 * 1000, TimeUnit.SECONDS).getBody();
}
}

4.4. 不同的超时时间

假如我碰到这种场景:

ServiceA | 10s

ServiceB | 25s

有3个套路可解决:

  • 套路一,创建多个实例,每个实例有自己的超时时间,比如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 超时时间短的实例
@Bean(name = "clientHttpRequestFactoryA")
public ClientHttpRequestFactory clientHttpRequestFactoryA() {
return createClientHttpRequestFactory(2*1000, 10*1000);
}

@Bean(name = "restTemplateA")
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplateA() {
return createRestTemplate(clientHttpRequestFactoryA());
}

// 超时时间长的实例
@Bean(name = "clientHttpRequestFactoryB")
public ClientHttpRequestFactory clientHttpRequestFactoryB() {
return createClientHttpRequestFactory(5*1000, 25*1000);
}

@Bean(name = "restTemplateB")
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplateB() {
return createRestTemplate(clientHttpRequestFactoryB());
}
  • 套路二,AsyncRestTemplate
1
2
3
ListenableFuture<ResponseEntity<String>> future = asyncRestTemplate
.getForEntity("http://www.douyu.com", String.class);
return future.get(2 * 1000, TimeUnit.SECONDS).getBody();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@EnableCircuitBreaker
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp .class, args);
}
}

@Service
public class MyService {
private final RestTemplate restTemplate;

public BookService(RestTemplate rest) {
this.restTemplate = rest;
}

@HystrixCommand(
fallbackMethod = "fooMethodFallback",
commandProperties = {
@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds",
value="5000"
)
}
)
public String fooMethod() {
// Your logic here.
restTemplate.exchange(...);
}

public String fooMethodFallback(Throwable t) {
log.error("Fallback happened", t);
return "Sensible Default Here!"
}
}

4.5. 如何设置连接池

4.6. 全局统一的异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//实现异常处理接口
public class CustomErrorHandler extends DefaultResponseErrorHandler {

@Override
public void handleError(ClientHttpResponse response) throws IOException {

}

}

//将自定义的异常处理器加进去
@Configuration
public class RestClientConfig {

@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new CustomErrorHandler());
return restTemplate;
}

}

5. 小技巧

5.1. 参数模板

  • 数组传参
1
2
3
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", 
String.class, "42", "21");
//实际效果等同于:GET http://example.com/hotels/42/bookings/21
  • map传参
1
2
3
4
5
6
Map<String, String> vars = new HashMap<String, String>();
vars.put("hotel", "42");
vars.put("booking", "21");
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}",
String.class, vars);
//实际效果等同于:GET http://example.com/hotels/42/rooms/42

5.2. 文件上传注意点

  • 如果使用了本地临时文件,一定要在finally代码块中删除,否则可能会撑爆磁盘

6. FAQ

6.1. 获取状态码

使用xxForEntity类方法调用接口,将返回ResponseEntity对象,通过它能取到状态码。

1
2
3
4
5
6
7
8
9
10
11
12
//判断接口返回是否为200
public static Boolean ping(){
String url = "xxx";
try{
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
HttpStatus status = responseEntity.getStatusCode();//获取返回状态
return status.is2xxSuccessful();//判断状态码是否为2开头的
}catch(Exception e){
log.error("处理失败: {}", url, e);
return false; //502 ,500是不能正常返回结果的,需要catch住,返回一个false
}
}

6.2. 我需要手工释放连接吗?

6.2. 如何调试rest template

可以在logback里单独配一个debug级别的logger,把org.apache.http下面的日志定向到控制台:

1
2
3
<logger name="org.apache.http" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT" />
</logger>

能看到很详细的报文:

图片名称

评论系统未开启,无法评论!