Spring HTTP 客户端神器 RestTemplate 详解

简介

目前 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>其他
GETgetForObjectgetForEntity
HEADheadForHeaders
POSTpostForObjectpostForEntitypostForLocation
PUTput
PATCHpatchForObject
DELETEdelete
OPTIONSoptionsForAllow
全部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 客户端。
在这里插入图片描述假如我们想将 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 客户端还是比较推荐大家使用的。如果对你有些许帮忙,欢迎点赞留言。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值