简介

目前 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 方法则可以获取 ResponseEntityResponseEntity 不仅可以获取 Java 对象表示的响应体,而且还能获取到完整的响应头。

对于 putdelete 等方法不支持获取响应头或响应体怎么办呢?还有两个万能的方法分别为 exchangeexecute,这两个方法允许更自由的定制化操作,这些重载的方法最终都将调用 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 提示或直接查看方法签名即可。

  • 返回值 TT 是一个泛型类型,是一个表示响应体的 Java 对象,由参数 resposneType 指定具体的类型。
  • 返回值 ResponseEntity<T>:返回值 T 只能表示响应体,而 ResponseEntity<T> 不仅可以获取响应体,还可以获取响应头。
  • 返回值 HttpHeaders:表示响应头的对象。
  • 返回值 URIURI 形式的重定向地址。
  • 返回值 Set<HttpMethod>:接口接收的请求方法,用于 OPTIONS 请求。
  • 方法名称:方法名称以 HTTP 请求方式开头,For 后面的内容表示获取到的响应信息。
  • 参数 urlurl 表示请求的路径,有两种形式,一种是 String,另一种是 URIurl 中可以使用路径变量,格式为 {key}key 可以为从 1 开始递增的整数,也可以为一个关键词,如 https://baidu.com?wd={keyword}
  • 参数 uriVariablesuriVariables 表示路径中的变量,也有两种形式,一种是可变数组,另一种是 Map,可变数组用于整数指定的路径变量,Map 用于关键词指定的路径变量。
  • 参数 method:参数 method 用于 exchangeexecute 方法,表示 HTTP 请求方式。
  • 参数 requestrequest 表示请求体参数,可以是任意类型,RestTemplate 内部会尝试输出为正确的请求体。
  • 参数 requestEntityrequest 用于表示请求体,RequestEntity 类型的 requestEntity 不仅可以表示请求体,还可以设置请求头。
  • 参数 responseTyperesponseType 表示 Java 对象形式的响应体,如果对应的类型不包含泛型,可以使用 Class<T> 类型,否则就需要使用 ParameterizedTypeReference<T> 类型的参数。
  • 参数 requestCallbackexecute 方法使用的参数,请求前的回调,可以在请求前添加一些请求头。
  • 参数 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 客户端。

spring authorization server整合客户端 spring http客户端_mvc

假如我们想将 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 客户端还是比较推荐大家使用的。如果对你有些许帮忙,欢迎点赞留言。