简介
目前 Spring MVC 基本上已经成为了 Java Web 开发的首选框架,而 Web 开发除了要提供接口供客户端调用,我们的服务还经常作为其他服务的客户端。RestTemplate 作为 Spring 内置的 Http 客户端,由于和 Spring 框架整合程度较高,并且设计优秀,成为 Spring 开发首推的 HTTP 客户端。
Java 开发常用的 HTTP 客户端已经有很多了,包括 JDK 自带的 HttpURLConnection、Apache 的 HttpClient 以及 square 提供的 OkHttp,Spring 为什么又设计了一个 RestTemplate ? 这不是重复造轮子吗?
事实上 Spring 框架的设计自始至终未和其他开源框架进行竞争,而是站在巨人的肩膀,对于 RestTemplate 的设计也是如此,RestTempale 提供了访问 HTTP 接口的统一方式,而底层则允许使用不同的实现,包括 HttpURLConnection、HttpClient、OkHttp 以及自定义的实现。看 RestTemplate 的命名似乎使用了设计模式中的模板方法,而从实现来看则更接近于门面模式。
快速入门
先来快速了解一下 RestTemplate。
实例化
使用 RestTemplate 需要先进行实例化,RestTemplate 提供了三个构造方法用于实例化,具体如下。
public class RestTemplate extends InterceptingHttpAccess or implements RestOperations {
public RestTemplate() {
... 省略初始化代码
}
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
validateConverters(messageConverters);
this.messageConverters.addAll(messageConverters);
this.uriTemplateHandler = initUriTemplateHandler();
}
}
无参的构造方法提供了默认的初始化方式,ClientHttpRequestFactory 参数用于修改默认的实现,HttpMessageConverter 用于 Java 对象与请求体/响应体之间的转换,这两个参数稍后细说。
常用方法
HTTP 常见请求方式包括 GET 和 POST,我们先用 GET 请求了解下 RestTemplate,假如我们想访问百度,我们可以写如下的代码:
RestTemplate restTemplate = new RestTemplate();
String responseBody = restTemplate.getForObject("https://baidu.com", String.class);
是的,两行代码就搞定了,是不是很简单。
RestTemplate 提供了很多重载的方法,这些方法定义在父接口 RestOperations 中,具体如下。
HTTP 请求方式 | RestTemplate 方法 |
---|
响应体转指定对象 T | 响应体转 ResponseEntity<T> | 其他 |
---|
GET | getForObject | getForEntity | | HEAD | | | headForHeaders | POST | postForObject | postForEntity | postForLocation | PUT | | | put | PATCH | patchForObject | | | DELETE | | | delete | OPTIONS | | | optionsForAllow | 全部 | | | exchange | execute |
从上面的表格可以看到,RestTemplate 为不同的 HTTP 请求方式定义了不同的方法,方法名中的 For 后面的单词表示要获取的内容。
为了获取响应内容,我们可以调用 *ForObject 或 *ForEntity 的方法,*ForObject 方法可以获取响应体转换成的 Java 对象,*ForEntity 方法则可以获取 ResponseEntity ,ResponseEntity 不仅可以获取 Java 对象表示的响应体,而且还能获取到完整的响应头。
对于 put 、delete 等方法不支持获取响应头或响应体怎么办呢?还有两个万能的方法分别为 exchange 和 execute ,这两个方法允许更自由的定制化操作,这些重载的方法最终都将调用 execute 来完成网络请求。
RestTemplate 提供了多种重载的方法,只是为了用户调用更为方便,看下具体的方法定义,由于方法较多大概看下即可, 无需记忆。
public interface RestOperations {
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;
HttpHeaders headForHeaders(String url, Object... uriVariables) throws RestClientException;
HttpHeaders headForHeaders(String url, Map<String, ?> uriVariables) throws RestClientException;
HttpHeaders headForHeaders(URI url) throws RestClientException;
URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException;
URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables) throws RestClientException;
URI postForLocation(URI url, @Nullable Object request) throws RestClientException;
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException;
void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException;
void put(String url, @Nullable Object request, Map<String, ?> uriVariables) throws RestClientException;
void put(URI url, @Nullable Object request) throws RestClientException;
<T> T patchForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> T patchForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> T patchForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException;
void delete(String url, Object... uriVariables) throws RestClientException;
void delete(String url, Map<String, ?> uriVariables) throws RestClientException;
void delete(URI url) throws RestClientException;
Set<HttpMethod> optionsForAllow(String url, Object... uriVariables) throws RestClientException;
Set<HttpMethod> optionsForAllow(String url, Map<String, ?> uriVariables) throws RestClientException;
Set<HttpMethod> optionsForAllow(URI url) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) throws RestClientException;
<T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException;
<T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException;
<T> T execute(URI url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException;
}
方法虽多,但是有一定的规律,不需要死记硬背,使用时根据 IDE 提示或直接查看方法签名即可。
- 返回值
T :T 是一个泛型类型,是一个表示响应体的 Java 对象,由参数 resposneType 指定具体的类型。 - 返回值
ResponseEntity<T> :返回值 T 只能表示响应体,而 ResponseEntity<T> 不仅可以获取响应体,还可以获取响应头。 - 返回值
HttpHeaders :表示响应头的对象。 - 返回值
URI :URI 形式的重定向地址。 - 返回值
Set<HttpMethod> :接口接收的请求方法,用于 OPTIONS 请求。 - 方法名称:方法名称以 HTTP 请求方式开头,
For 后面的内容表示获取到的响应信息。 - 参数
url :url 表示请求的路径,有两种形式,一种是 String ,另一种是 URI ,url 中可以使用路径变量,格式为 {key} ,key 可以为从 1 开始递增的整数,也可以为一个关键词,如 https://baidu.com?wd={keyword} 。 - 参数
uriVariables :uriVariables 表示路径中的变量,也有两种形式,一种是可变数组,另一种是 Map ,可变数组用于整数指定的路径变量,Map 用于关键词指定的路径变量。 - 参数
method :参数 method 用于 exchange 或 execute 方法,表示 HTTP 请求方式。 - 参数
request :request 表示请求体参数,可以是任意类型,RestTemplate 内部会尝试输出为正确的请求体。 - 参数
requestEntity :request 用于表示请求体,RequestEntity 类型的 requestEntity 不仅可以表示请求体,还可以设置请求头。 - 参数
responseType :responseType 表示 Java 对象形式的响应体,如果对应的类型不包含泛型,可以使用 Class<T> 类型,否则就需要使用 ParameterizedTypeReference<T> 类型的参数。 - 参数
requestCallback :execute 方法使用的参数,请求前的回调,可以在请求前添加一些请求头。 - 参数
responseExtractor :同样是 execute 方法使用的参数,用于将响应体转换为对象。
只看接口方法确实比较枯燥,我们来实战了解 RestTemplate 的使用。假定接口定义如下。
@RestController
public class UserController {
@PostMapping("/register")
public User login(@RequestBody User user) {
return user;
}
}
我们可以使用如下的方式进行请求。
public void test() {
User user = new User().setUsername("hkp").setPassword("123");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<User> httpEntity = new HttpEntity<>(user, headers);
RestTemplate restTemplate = new RestTemplate();
User response = restTemplate.postForObject("http://127.0.0.1:8080/register", httpEntity, User.class);
System.out.println(response);
}
假如把上面的 @PostMapping 改成 @PutMapping ,RestTemplate 没有直接为 PUT 请求获取响应体提供方法,我们可以使用 exchagne 方法。
public void test() {
User user = new User().setUsername("hkp").setPassword("123");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<User> httpEntity = new HttpEntity<>(user, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<User> response = restTemplate.exchange("http://127.0.0.1:8080/register", HttpMethod.PUT, httpEntity, User.class);
User responseBody = response.getBody();
System.out.println(responseBody);
}
进阶
自定义客户端
前面提到 RestTemplate 只是提供访问 HTTP 的统一方法,底层可以使用不同的实现方式,发起请求的客户端实际上由 ClientHttpRequest 表示,由工厂 ClientHttpRequestFactory 创建,在 RestTemplate 构造方法中传入不同 ClientHttpRequestFactory 的实现就可以使用不同的客户端。Spring 内部默认支持 HttpURLConnection、HttpClient、OkHttp,如果不满足还可以定义自己的 HTTP 客户端。 假如我们想将 HTTP 客户端修改为 HttpClient,并设置连接池,在引入相关 jar 包之后,我们可以如下创建 RestTemplate。
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20);
poolingHttpClientConnectionManager.setMaxTotal(100);
HttpClient httpClient = HttpClients.createMinimal(poolingHttpClientConnectionManager);
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
除了在创建 RestTemplate 时指定 ClientHttpRequestFactory,我们还可以在 ClientHttpRequestFactory 创建 ClientHttpRequest 后进一步的定制化,Spring 提供的回调接口是 ClientHttpRequestInitializer。假如我们想设置一个默认请求头,可以如下操作。
ClientHttpRequestInitializer clientHttpRequestInitializer = request -> request.getHeaders().add("source", "user-center");
RestTemplate restTemplate = new RestTemplate();
restTemplate.getClientHttpRequestInitializers().add(clientHttpRequestInitializer);
请求/响应体转换
RestTemplate 还有一个类型为 List<HttpMessageConverter<?>> 的构造方法参数,这里的 HttpMessageConverter 与在 Spring MVC 中的作用类似,用来将对象转换为请求体以及响应体转换为对象。如果需要,我们可以添加自己的 HttpMessageConverter ,例如 fastjson 中提供了一个 HttpMessageConverter 的实现 FastJsonHttpMessageConverter,可以做如下配置添加。
FastJsonHttpMessageConverter httpMessageConverter = new FastJsonHttpMessageConverter();
httpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(0, httpMessageConverter);
请求回调
有时,我们可能需要在请求前添加一些用户鉴权相关的请求头,这个请求头的值可能会发生变化,RestTempate 提供了一个回调接口 RequestCallback,事实上 RestTemplate 内部也用它来设置 Accept 请求头以及使用合适的 HttpMessageConverter 将对象的内容写入请求体。对于用户而言,可以在调用 execute 方法时传入该接口的实现,示例代码如下。
RestTemplate restTemplate = new RestTemplate();
RequestCallback requestCallback = request -> request.getHeaders().add("token", "abc");
restTemplate.execute("http://127.0.0.1:8080/user/list", HttpMethod.GET, requestCallback, null);
响应抽取
在上面的示例中,我们调用了 #execute 方法,最后一个参数我们有意设置成了 null,这个参数的类型为 ResponseExtractor,RestTemplte 内部使用这个接口将响应体转换为用户给定的响应类型,同时用户也可以使用这个参数自定义将响应体抽取为对象的方式。
继续对上面的示例进行完善。
RestTemplate restTemplate = new RestTemplate();
RequestCallback requestCallback = request -> request.getHeaders().add("token", "abc");
ResponseExtractor<List<User>> responseExtractor = response -> {
byte[] bytes = IOUtils.toByteArray(response.getBody());
String body = new String(bytes, response.getHeaders().getContentType().getCharset());
return JSONObject.parseObject(body, new TypeReference<List<User>>() {});
};
List<User> userList = restTemplate.execute("http://127.0.0.1:8080/user/list", HttpMethod.GET, requestCallback, responseExtractor);
响应错误处理
有时,虽然响应已经成功返回,但是我们可能任务这个响应是错误的,我们需要做进一步处理,例如抛出一个业务异常,RestTemplate 为我们提供的接口是 ResponseErrorHandler,这个接口会在请求成功后,响应体解析前执行。示例代码如下。
public void test() {
ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getRawStatusCode() != 200;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
throw new RuntimeException("请求错误");
}
};
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(responseErrorHandler);
List userList = restTemplate.getForObject("http://127.0.0.1:8080/user/list", List.class);
}
拦截器
除了请求回调和响应错误处理,RestTemplate 提供了更为通用的拦截器 ClientHttpRequestInterceptor,其设计与使用方式与 Filter 链很相似。使用示例如下。
public void test() {
ClientHttpRequestInterceptor clientHttpRequestInterceptor = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("token", "abc");
ClientHttpResponse response = execution.execute(request, body);
if (response.getRawStatusCode() != 200) {
throw new RuntimeException("请求错误");
}
return response;
}
};
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(clientHttpRequestInterceptor);
}
快速构建
由于 RestTemplate 需要设置的初始化参数较多,Spring Boot 提供了一个 RestTemplateBuilder 类用于构造 RestTemplate,并且会自动注册为 bean,用户可以定义自己的 RestTemplateBuilder bean 替代 Spring Boot 默认创建的。
将上面零零散散初始化 RestTemplate 的示例替换成 RestTemplateBuilder,代码如下。
public void test() {
RestTemplate restTemplate = new RestTemplateBuilder()
.requestFactory(HttpComponentsClientHttpRequestFactory.class)
.messageConverters(new FastJsonHttpMessageConverter())
.interceptors(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
return execution.execute(request, body);
}
})
.customizers(new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
restTemplate.getClientHttpRequestInitializers().add(new ClientHttpRequestInitializer() {
@Override
public void initialize(ClientHttpRequest request) {
}
});
}
})
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.build();
}
RestTemplateBuilder 还提供了一些其他方法,感兴趣的小伙伴可自行查阅源码。
总结
本篇主要介绍了 RestTemplate 的常用方法以及进阶使用,可以看到设计也确实比较灵活,提供给用户很多定制化的操作,作为 Spring 内置的 HTTP 客户端还是比较推荐大家使用的。如果对你有些许帮忙,欢迎点赞留言。
|