简介
目前 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 中,具体如下。
从上面的表格可以看到,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 只能表示响应体,而 ResponseEntity 不仅可以获取响应体,还可以获取响应头。
返回值 HttpHeaders:表示响应头的对象。
返回值 URI:URI 形式的重定向地址。
返回值 Set:接口接收的请求方法,用于 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 类型,否则就需要使用 ParameterizedTypeReference 类型的参数。
参数 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 客户端。
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 与在 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 客户端还是比较推荐大家使用的。如果对你有些许帮忙,欢迎点赞留言。