Spring Boot 2系列(二十一):RestTemplate 远程调用 REST 服务
互联网项目经常存在远程调用的情况,如果调用的是 REST 远程服务,可以使用 Spring Web 提供的RestTemplate.
RestTemplate 是原始的 Spring REST 同步请求客户端,通过 HTTP 客户端提供更高级别的 API,使得调用 REST 端点变更更容易。
Spring Boot 没有自动配置 RestTemplate,但自动注册了 RestTemplateBuilder Bean,用于构建 RestTemplate,并且 HttpMessageConverters 会自动应用到 RestTemplate 实例中。
如果是 WebFlux 项目,可以使用 WebClient 来远程调用 REST 服务,相比 RestTemplate,WebClient 拥有更多的功能,并且是完全响应式, 后续再对 WebClient 进行详解。
Spring Boot > Calling REST Services with RestTemplate,Spring Framework > Using RestTemplate
RestTemplate
初始化
RestTemplate 默认使用的是 java.net.HttpURLConnection 来执行请求,可以切换成不同的实现了ClientHttpRequestFactory 接口的 HTTP 库,如:Apache HttpComponents,Netty,OkHttp。
示例:使用 Apache HttpComponents
1 | RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); |
每个 ClientHttpRequestFactory 公开的配置项都是基于 HTTP Client 底层库的支持,如:凭据,连接池等。
注意: java.net 实现的 HTTP 请求可能在访问表示错误的响应状态时会引发异常,如:401。如果是这个问题,切换到其它的 HTTP Client 库。
URIs
RestTemplate 方法可以接收 URI 模板和 URI 模板变量,可以是 String 变量,也可以是 Map<String, String>。
1 | // uri 路径传参 |
注意:URI templates are automatically encoded(自动编码),如下:
1 | restTemplate.getForObject("http://example.com/hotel list", String.class); |
也可以使用 RestTemplate 的 uriTemplateHandler 属性来自定义 URI 的编码方式,或创建一个 java.net.URI 传给接收 URI 的 RestTemplate方法。如下,更多 URI编码请详见。
Headers
可以使用 exchange() 方法来指定请求头信息,如下:
1 | String uriTemplate = "http://example.com/hotels/{hotel}"; |
通过 RestTemplate 灵活的方法,可以让返回的 ResponseEntity 会包含响应头。
Body
传入并从 RestTemplate 方法返回的对象,是通过 HttpMessageConverter 使原始内容和对象之间实现相互转换。
在一个 POST 请求中,一个输入对象被序列化到请求体中:
1
URI location = template.postForLocation("http://example.com/people", person);
默认情况下,可以不显式设置请求的 Content-Type, 底层会根据 源对象类型 找到合适的消息转换器来设置 Content-Type,若有特殊需求,可以使用 exchange() 方法来显式设置 Content-Type,相当于显式指定了消息转换器。
在一个 GET 请求中,响应体被反序列化为输出对象。
1
Person person = restTemplate.getForObject("http://example.com/people/{id}", Person.class, 42);
不需要显式设置请求的 Accept 头。大多数情况下,可以根据预期的响应类型找到兼容的消息转换器,然后帮助填充 Accept 头。如果有特殊需求,可使用 exchange() 方法显式提供 Accept 头。
默认情况下,RestTemplate 会注册所有内置消息转换器,具体取决于在类路径下能检查到的可选转换库。还可以显式设置消息转换器。
Message Conversion
参考 Spring Framework > Message Conversion
Jackson JSON Views
可以指定 Jackson JSON View 来只序列化对象属性的子集,如以下示例所示:
1 | MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23")); |
Multipart
要发送 Multipart 数据,需要提供 MultiValueMap<String, ?>,其值为部分内容的 Object 实例,或表示部分内容和头的 HttpEntity 实例。
MultipartBodyBuilder 提供了一个方便的 API 来准备 Multipart 请求,如下示例所示:
1 | MultipartBodyBuilder builder = new MultipartBodyBuilder(); |
在大多数情况下,不必为每个部件(part)指定 Content-Type。内容类型是根据选择用于序列化它的 HttpMessageConverter 自动确定的,在有资源文件的情况下,是基于文件扩展名的。若有必要,可以通过重载构建部分方法为每个部件(part)显式提供 MediaType。
一旦 MultiValueMap 准备好后,可以将其传递给 RestTemplate ,如下示例:
1 | MultipartBodyBuilder builder = ...; |
如果 MultiValueMap 包含至少一个非 String 值,该值也可以表示常规表单数据(即 application / x-www-form-urlencoded ),则无需将 Content-Type 设置为 multipart / form-data 。当使用 MultipartBodyBuilder 确保 HttpEntity 包装器时,情况总是如此。
RestTemplateBuilder
Build
RestTemplateBuilder 包含多个有用的方法来快速的配置 RestTemplate ,如添加 BASIC auth 支持,可以使用 builder.basicAuthorization(“user”, “password”).build();
RestTemplate 默认会注册所有内置的消息转换器,具体的选择取决于类路径下有那些转换库可被选择使用。可以人为显式设置消息转换器。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyService {
private final RestTemplate restTemplate;
public MyService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
public Details someRestCall(String name) {
return this.restTemplate.getForObject("/{name}/details", Details.class, name);
}
}自定义
Spring Boot 提供了 RestTemplateBuilder 的自动配置, 几乎不需要通过自定义来创建 RestTemplateBuilder, 如果自定义则会关闭 RestTemplateBuilder 的自动配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
public class RestTemplateConfig {
public RestTemplate customRestTemplate(){
//设置连接和读超时时间,毫秒
// 2.1.0 版本方式
return new RestTemplateBuilder().setConnectTimeout(Duration.ofMillis(1000)).setReadTimeout(Duration.ofMillis(1000)).build();
// 2.0.x 版本方式
//return new RestTemplateBuilder().setConnectTimeout(1000).setReadTimeout(1000).build();
}
}配置代理
示例:为除 192.168.0.5 之外的所有主机配置代理请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22static class ProxyCustomizer implements RestTemplateCustomizer {
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create()
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
public HttpHost determineProxy(HttpHost target,
HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}).build();
restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
使用 RestTemplate
RestTemplate 请求方法
RestTemplate 提供了多个内部重载的方法组,其中 xxxForObject 和 xxxForEntity 两组是用的比较多的,两者在使用上基本没有区别,区别是在返回的内容上:
- xxxForObject:返回的消息只有响应体,没有响应头。
- xxForEntity:返回的消息中有包含响应头和响应体; 返回值是一个 ResponseEntit,ResponseEntity是Spring对HTTP 请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。
方法组 | 描述 |
---|---|
getForObject | 使用 Get 请求,返回数据的类型是 Object,只有实体数据,不包含响应头数据 |
getForEntity | 使用 Get 请求,返回数据的类型是 ResponseEntity,包含状态码,响应头,响应体数据 |
headForHeaders | 使用 HEAD 请求,获取资源的所有头信息 |
postForLocation | 使用 POST 创建新资源并返回新资源的 URI |
postForObject | 使用 POST 创建新资源并返回响应体中的数据 |
postForEntity | 使用 POST 创建新资源并返回 ResponseEntity<T> |
put | 使用 PUT 创建或更新资源 |
patchForObject | 使用 PATCH 更新资源并返回响应体 JDK自带的 HttpURLConnection 不支持,但Apache HttpComponents和其它支持 |
delete | 使用 DELETE 删除指定 RUI 的资源 |
optionsForAllow | 检查资源允许 HTTP 访问的请求方式 |
exchange | 提供额外的灵活的操作,可接收 RequestEntity(包括 HTTP 方法,URL,Header,输入的Body),返回 ResponseEntity |
execute | 执行请求最常用的方式,通过回调接口完全控制请求前的准备工作和取出响应消息 |
RestTemplate 请求传参
没有请求参数
1
Object object = restTemplate.getForObject("https://example.com/hotels", Object.class);
路径变量传参
1
2
3//按顺序取值填充到 URI 中
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");Map 传参
1
2
3
4
5
6
7
8
9
10
11
12//Map 传参,map 的 Key 与路径变量名相同
Map<String, Long> varMap = new HashMap<>(1);
varMap.put("cityId", cityId);
//getForObject方法
City city3 = restTemplate.getForObject("http://localhost:8080/city/{cityId}", City.class, varMap);
//getForEntity方法
ResponseEntity<City> responseEntity4 = restTemplate.getForEntity("http://localhost:8080/city/{cityId}", City.class, varMap);
//POST 发送 Map 请求,Map 类型只能是 MultiValueMap
MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<String, String>();
paramMap.add("userName","admin");
restTemplate.postForEntity(url, map, String.class);传实体类参数
传实体类作为请求参数,只能使用 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
41public String postMethod(City city) {
String url = "http://localhost:8080/city";
String uriStr = null;
//自定义请求头
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
HttpEntity<String> formEntity = new HttpEntity<String>(JSON.toJSONString(city), headers);
//rest服务中 Controller 方法使用对象无法接收参数
//URI uri = restTemplate.postForLocation(url, city);
//rest 服务 Controller 方法中 @RequestBody 注解不支持默认的 text/plain;charset=ISO-8859-1
//URI uri = restTemplate.postForLocation(url, JSON.toJSONString(city));
//1.只能发送对象字符串,否则Rest服务接收不到参数
//2.字符串默认数据类型和编码是text/plain;charset=ISO-8859-1,
// 调用的Rest服务的Controller方法中使用 @RequestBody 解析参数封装到对象中,
// 而@RequestBody注解解析的是JSON数据,所以需要设置消息头告诉数据类型和编码
//postForLocation 方法
URI uri = restTemplate.postForLocation(url, formEntity);
if (uri != null) {
uriStr = JSON.toJSONString(uri);
}
//return uriStr;
//postForEntity 方法, 发送的是 JSON 字符串, 接收端使用 @RequestBody 注解解析
ResponseEntity<City> cityResponseEntity = restTemplate.postForEntity(url, city, City.class);
//使用 postForEntity 请求接口
HttpHeaders headers = new HttpHeaders();
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(paramMap, headers);
ResponseEntity<String> respon = template.postForEntity(url, httpEntity, String.class);
//postForObject方法, 发送的是 JSON 字符串, 接收端使用 @RequestBody 注解解析
City city1 = restTemplate.postForObject(url, city, City.class);
}
RestTemplate 请求示例
1 |
|
Spring Boot 2系列(二十一):RestTemplate 远程调用 REST 服务
http://blog.gxitsky.com/2018/06/26/SpringBoot-21-RestTemplate/