共计 15417 个字符,预计需要花费 39 分钟才能阅读完成。
每篇一句
人圆月圆心圆,人和家和国和 — 中秋节快乐
前言
在阅读本篇之前,建议先阅读开山篇效果更佳。RestTemplate
是 Spring 提供的用于访问 Rest
服务的客户端工具,它提供了多种 便捷 访问远程 Http 服务的方法,能够大大提高客户端的编写效率 。
弱弱呼吁一句:对于那些在 Spring
环境下还在使用HttpClient
(或其它 Client)的同学,今儿看完本文后,建议切换到RestTemplate
(有特殊需求的当然除外喽~)。
RestTemplate
简化了与 http 服务的通信,程序代码可以给它提供 URL,并提取结果。它默认使用的 JDK 的 HttpURLConnection
进行通信,然而我们是可以通过 RestTemplate.setRequestFactory
切换到不同的 HTTP 源:如 Apache HttpComponents
、Netty
、OkHttp
等等。
RestOperations
指定一组基本 restful 操作的接口,定义了基本的 Rest 操作集合,它的唯一实现是RestTemplate
;不直接使用,但这是增强可测试性的一个有用选项,因为它很容易被模拟或存根(后面这句话请好好理解)。
可以对比参照
RedisOperations
,它的实现类也只有RedisTemplate
一个。他俩都采用了设计模式中的模板模式
方法们:
由于此接口里的方法实在太多了(40+ 个),因此我按照 Http 标准进行分类如下表格:
// @since 3.0
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
...
}
HttpMethod | 方法 |
---|---|
GET | |
HEAD | |
POST | |
PUT | |
PATCH | |
DELETE | |
OPTIONS | |
TRACE | 无 |
any(执行任何 Http 方法) |
观察发现,虽然方法众多但有很强的规律可循。每个方法都有三种重载实现: 2 种的 url 参数为字符串,一种 URI 参数,所以掌握规律后再使用,就不用害怕它的多而不知咋使用了。
xxxForObject:返回响应体(也就直接是 body 体力的内容)(T)
xxxForEntity:返回的相应行、响应头、响应码、响应体等等 (ResponseEntity<T>)
xxxForLocation:提交成功之后,返回新资源的 URI。这个只需要服务提供者返回一个 URI 即可,该 URI 表示新资源的位置,可谓非常轻量。(URI)
注意:使用字符串类型的 url 默认会对 url 进行转义,如 http://example.com/hotel list
在执行时会转义为 http://example.com/hotel%20list
,隐式的转义这样是没有问题的。但如果你自己已经转义过了,那就不 ok 了。
若不想要这种隐式的转义,建议使用 URI(URI uri = uriComponents.toUri()
)来构造。
==RestTemplate
中 POST 请求的三种方式 ==
post
请求代表新建 / 创建一个资源,所以它是有返回值的。因为它的使用最为复杂,因此本文以它为例进行讲解。
你如果熟练使用过浏览器的 开发者工具
调试过,你肯定知道 POST
请求它传参是有两种方式的:
- Form Data 方式:我们用 from 表单提交的方式就是它;使用 ajax(注意:这里指的是 jQuery 的 ajax,而不是源生 js 的)默认的提交方式也是它~
- request payload 方式:多部分方式 /json 方式
这两种方式是通过 Content-Type
来区别的:若是 application/x-www-form-urlencoded
那就是 formdata
方式;若是 application/json
或者 multipart/form-data
等方式那就是 request payload
方式
jQuery
在执行 post 请求时,默认会给你设置Content-Type
为application/x-www-form-urlencoded
,所以服务器能够正确解析。
若使用 js 原生的 ajax,如果不显示的
设置 Content-Type,那么默认是 text/plain,这时服务器就不知道怎么解析数据了,所以才只能通过获取原始数据流的方式来进行解析请求数据。(相信没人这么干吧~)
exchange 和 execute 方法:
exchange 方法:更通用的请求方法。它入参必须接受一个 RequestEntity
,从而可以设置请求的路径、头等等信息,最终全都是返回一个ResponseEntity
(可以发送 Get、Post、Put 等所有请求)。
execute 方法: 最最最底层、通用 的请求方法。
RequestCallback:用于操作请求头和 body,在请求发出
前
执行;ResponseExtractor:解析 / 提取 HTTP 响应的数据,而且不需要担心异常和资源的关闭RequestCallback.doWithRequest(ClientHttpRequest)
说白了就是拿到ClientHttpRequest
后对他进行继续处理~RestTemplate
的acceptHeaderRequestCallback、httpEntityCallback
这些方法可以设置它~
HttpAccessor、InterceptingHttpAccessor
这两个抽象类不容忽视,HystrixCommand 和 Ribbon
的逻辑都和它有关系(拦截器)。HttpAccessor
是个抽象基类,它定义要操作 ClientHttpRequestFactory
的公共属性,它一般不直接使用。
// @since 3.0
public abstract class HttpAccessor {
// RestTemplate 默认使用的客户端工厂:基于源生 JDK
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
// 若要切换成三方库的底层组件,设置此方法便可
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {this.requestFactory = requestFactory;}
... // get 方法
// 供给子类非常方便的拿到一个 ClientHttpRequest
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {ClientHttpRequest request = getRequestFactory().createRequest(url, method);
return request;
}
}
它的子类是:InterceptingHttpAccessor
,也还是个抽象实现,主要是管理起了请求的拦截器们:ClientHttpRequestInterceptor
。
InterceptingHttpAccessor
// @since 3.0
// @see InterceptingClientHttpRequestFactory
public abstract class InterceptingHttpAccessor extends HttpAccessor {
// 装载需要作用在 RestTemplate 上的拦截器们~~~
private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
@Nullable
private volatile ClientHttpRequestFactory interceptingRequestFactory;
// 这里语意是 set,所以是完全的替换掉(支持 ordered 排序哦~~~)public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {if (this.interceptors != interceptors) {this.interceptors.clear();
this.interceptors.addAll(interceptors);
AnnotationAwareOrderComparator.sort(this.interceptors);
}
}
// 复写了父类的这个方法很有意思
// 意思为:若你调用者手动 set 进来了,那就以调用者设置的工厂为准 否则使用的是 InterceptingClientHttpRequestFactory
@Override
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {super.setRequestFactory(requestFactory);
this.interceptingRequestFactory = null;
}
// 若配置了拦截器,那么默认就使用 InterceptingClientHttpRequestFactory, 而不再是 SimpleClientHttpRequestFactory 了~~~
@Override
public ClientHttpRequestFactory getRequestFactory() {List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
} else {return super.getRequestFactory();
}
}
}
InterceptingHttpAccessor
最主要的处理逻辑为:若发现调用者设置了请求拦截器,那么它创建的工厂是具有拦截功能的InterceptingClientHttpRequestFactory
,否则就是默认的SimpleClientHttpRequestFactory
。
InterceptingClientHttpRequestFactory
工厂它产生的 ClientHttpRequest
是InterceptingClientHttpRequest
,然而它就会执行拦截器的拦截方法喽:nextInterceptor.intercept(request, body, this)
提问:如有配置有多个请求拦截器,都会执行吗?
解答:这个千万不要犯迷糊和轻易下结论:以为没有迭代它(for 循环)而只是 iterator.next()
就以为若有多个就只会执行一个,那就大错特错了 。这里实际是形成了一个执行链条,只要拦截器的intercept
方法内最终还调用执行器的 intercept()
方法,那么拦截器链就会一直执行下去。其根本缘由是第三个参数传入的是this
,至始至终都是同一个执行器(this=InterceptingRequestExecution
)
==RestTemplate==
RestTemplate
采用 同步 方式执行 HTTP 请求的类,底层默认使用 JDK
原生 HttpURLConnection API
。它实现了接口RestOperations
,提供了非常多的模版方法(重载方法)让开发者能更简单地发送 HTTP 请求。
需要注意的是,RestTemplate
是 Spring 3.0
就有了,但在 Spring5.0 后,Spring 官方是推荐使用 org.springframework.web.reactive.function.client.WebClient
替代它,特别是对于异步的场景。
RestTemplate
因为使用极其广泛,so 即使到了 Spring 5.0,官方只是建议替代,但并没有标注@Deprecated
,因此至少目前你还可以想咋用就咋用吧。
但是AsyncRestTemplate
是明确标注了@Deprecated
,强烈建议使用org.springframework.web.reactive.function.client.WebClient
去代替,所以在 5.0 后不建议再使用它了~。
当然还需要说明一点:若你的项目中没有使用到 WebFlux
的技术栈来处理请求,那么也没必要说为了使用而使用,所以没必要专门为了它而导包(个人建议)~
// @since 3.0
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
// 去 classpath 探测 是否有这些消息转换器相关的 jar~
// 一般情况下我们都会导 jackson2Present~~~
private static boolean romePresent;
private static final boolean jaxb2Present;
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
private static final boolean jackson2SmilePresent;
private static final boolean jackson2CborPresent;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
...
// 下面四个变量很重要:// 消息转换器们(显然对 JSON 格式默认是支持得最好的)private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
// 默认的请求异常处理器,Spring5.0 后其实可以使用它 ExtractingResponseErrorHandler
// 它能够利用消息换换气提取你的错误内容。并且还支持自定义错误码、错误序列等等~
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
// 用于 URL 的构建
private UriTemplateHandler uriTemplateHandler;
// 默认的返回值提取器~~~~
private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
// 空构造,应该是平时使用得最多的了:一切都使用默认的组件配置 Resource 等等
public RestTemplate() {
// 这个几个消息转换器是支持的。字节数组、字符串、this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
this.messageConverters.add(new SourceHttpMessageConverter<>());
// 对 form 表单提交方式的支持
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
// 接下里便是一些列的判断,若类路径上有才会加进来
if (jackson2Present) {this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
...
// new DefaultUriBuilderFactory()
this.uriTemplateHandler = initUriTemplateHandler();}
// 你懂的,若想用 OkHttp,也可以在构造时就指定
public RestTemplate(ClientHttpRequestFactory requestFactory) {this();
setRequestFactory(requestFactory);
}
// 若不想用默认的消息转换器,也可以自己指定(其实一般都不这么去干,而是后面自己再 add 进来)public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
this.messageConverters.addAll(messageConverters);
this.uriTemplateHandler = initUriTemplateHandler();}
... // 省略上面属性的 get/set 犯法们
}
这部分源码我列出来,都是在对构建一个 RestTemplate
实例的准备工作相关方法,包括对各个相关组件的设置。
接下来更重要的便是它实现的接口方法了,我抽出一些关键点进行描述说明:
RestTemplate:@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {//1、new AcceptHeaderRequestCallback(responseType) 它能在发送请求的之前这样一件事:// request.getHeaders().setAccept(allSupportedMediaTypes)
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
// 最终调用的是 execute 方法,此时 URL 是个字符串
// responseExtractor 返回值提取器使用的是消息转换器去读取 body 哒~
// 返回值就是返回的 body 本身(不含有返回的响应头等等信息~)return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
// 它返回的是 ResponseEntity,不会返回 null 的 最终调用的依旧是 execute 方法
// 此时候用的就不是消息转换器的提取器了,而是内部类 `ResponseEntityResponseExtractor`(底层还是依赖消息转换器)// 但是这个提取器,提取出来的可都是 ResponseEntity<T> 实例~
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
}
// HEAD 请求:很简单,使用的提取器就是 headersExtractor,从返回值里把响应 header 拿出来即可
@Override
public HttpHeaders headForHeaders(String url, Object... uriVariables) throws RestClientException {return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor(), uriVariables));
}
// POST 请求
@Override
@Nullable
public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {
// 1、HttpEntityRequestCallback 适配:把 request 适配成一个 HttpEntity
// 然后执行前,通过消息转换器把头信息、body 信息等等都 write 进去
RequestCallback requestCallback = httpEntityCallback(request);
// 因为需要拿到 URI,所以此处使用 headersExtractor 提取器先拿到响应的 header 即可~~~
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables);
return (headers != null ? headers.getLocation() : null);
}
// 除了 httpEntityCallback()不一样,其余和 get 请求一样
@Override
@Nullable
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
// PUT 请求:因为没有返回值,所以不需要返回值提取器。所以,非常的简单~~~
@Override
public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException {RequestCallback requestCallback = httpEntityCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null, uriVariables);
}
// DELETE 请求:也是木有返回值的。// 并且请注意:DELETE 请求这里可都是不能接收 body 的,不能给请求设置请求体的
//(虽然可能底层 httpCLient 支持,但这里不支持,请遵守规范)@Override
public void delete(String url, Object... uriVariables) throws RestClientException {execute(url, HttpMethod.DELETE, null, null, uriVariables);
}
// OPTIONS 请求:和 HEAD 请求的处理逻辑几乎一样
@Override
public Set<HttpMethod> optionsForAllow(String url, Object... uriVariables) throws RestClientException {ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables);
return (headers != null ? headers.getAllow() : Collections.emptySet());
}
所有方法大体执行逻辑一致,都是和 RequestCallback
、responseExtractor
等有关,且最终都是委托给了最为底层的 execute()
方法去执行。
你是否疑问:它提供的 put 方法返回值都是 void,若我 put 请求就有返回值肿么办呢?那么接下来就介绍更为通用的一个方法:exchange()
RestTemplate:@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
// 把请求体适配为 HttpEntity
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
// 消息提取器使用 ResponseEntityResponseExtractor
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
// 从上两个部分就能看到:exchange 方法的入参、出参都是非常通用的~~~
return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}
// ParameterizedTypeReference 参数化类型,用于处理泛型
// 上面的 responseType 就是个 Class。这里是个参数化类型~~~~~
@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {Type type = responseType.getType();
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}
// 这个方法就非常精简了,让调用者自己去构造 RequestEntity,里面是包含了请求的 URL 和方法等信息的
@Override
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType) throws RestClientException {RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(doExecute(requestEntity.getUrl(), requestEntity.getMethod(), requestCallback, responseExtractor));
}
exchange
所有方法使用的都是 HttpEntity
和ResponseEntity
代表请求实体和响应实体,足以见到它设计的通用性。
在 Spring3.2 后提供了
ParameterizedTypeReference
来处理参数化类型 —> 主要是为了处理 List 等的泛型
可以发现即使是 exchange()
方法,最终还是委托给 execute/doExecute
去执行的:
RestTemplate:// 3 个 execute 方法。最终调用的都是 doExecute 方法
// 它做的一件事:使用 UriTemplateHandler 把 URL 的参数填进去~~~
// 底层使用的是我上文介绍的 `UriComponentsBuilder`,还是比较简单的
@Override
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {URI expanded = getUriTemplateHandler().expand(url, uriVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
doExecute 方法:@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
ClientHttpResponse response = null;
ClientHttpRequest request = createRequest(url, method);
// 如果有回调,那就先回调处理一下子请求
if (requestCallback != null) {requestCallback.doWithRequest(request);
}
// 真正意义上的发送请求。// 请注意:如果这里的 request 是 `InterceptingClientHttpRequest`,那就回执行拦截器的 intercept 方法哦~~~
// 至于什么时候是 InterceptingClientHttpRequest 呢?这个上面有讲的
response = request.execute();
// 处理结果(若有错误,那就抛出异常~~~)handleResponse(url, method, response);
// 请求正常。那就使用返回值提取器 responseExtractor 提取出内容即可了~~~
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
...
// 关闭响应(ClientHttpResponse 继承了 Closeable 接口)finally {if (response != null) {response.close();
}
}
}
看完 doExecute()
的模板式的实现步骤,就清楚了 RestTemplate
从发出一个请求到收到一个响应的完整过程。Spring
设计了多个相关组件,提供钩子程序让我们可以干预到流程里面去,最常见的当然就是请求拦截器了,它在 Ribbon 负载均衡和 Hystrix 熔断器里面有很好的应用~
AsyncRestTemplate
它是 @since 4.0
新增的用于解决一些异步 Http 请求的场景,但它寿命比较短,在 Spring5.0
就标记为 @Deprecated
,而被推荐使用WebClient
去代替它。
它的实现基础原理是:RestTemplate
+ SimpleAsyncTaskExecutor
任务池的方式去实现的异步请求,返回值均为 ListenableFuture
。掌握了RestTemplate
后,它使用起来是没有什么障碍的
极简使用 Demo Show
看过了原理的描述,我有理由相信你已经烂熟于胸并对 RestTemplate
能够运用自如了。因此关于使用方面,本文只给如下非常简单的一个 Demo Show 我认为是够了的:
public static void main(String[] args) throws IOException {RestTemplate restTemplate = new RestTemplate();
String pageHtml = restTemplate.getForObject("http://www.baidu.com", String.class);
System.out.println(pageHtml); // 百度首页的 html...
}
解释一点:这里请求得到的是一个 html 网页,所以 HttpMessageConverterExtractor
去提取响应时,使用的是 StringHttpMessageConverter
去处理的,提取代码如下:
StringHttpMessageConverter:@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
// 从响应头的 contentType 里提取(若是 application/json, 那默认也是 urf-8)// 若没有指定编码,就取值 getDefaultCharset。比如本处访问百度,就取值默认值 `ISO-8859-1` 对 body 体进行编码的~
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
return StreamUtils.copyToString(inputMessage.getBody(), charset);
}
小伙伴把此请求案例可以和上面我使用 ClientHttpRequestFactory
发送请求的案例对比(或者和你自己使用 HttpClient
步骤对比),感受感受使用 RestTemplate
是多么的优雅~
推荐阅读
RestTemplate 组件:ClientHttpRequestFactory、ClientHttpRequestInterceptor、ResponseExtractor【享学 Spring MVC】
为何一个 @LoadBalanced 注解就能让 RestTemplate 拥有负载均衡的能力?【享学 Spring Cloud】
总结
微服务作为主流的今天,RestTemplate
可谓是一把利器,每个程序员都应该掌握它。深入理解它对实际应用、调优都有很现实的意义,所以我相信本文能够帮助到你,做到烂熟于胸。
预告一下:下篇文章会原理分析告诉大家为何一个简单的 @LoadBalanced
注解就能让 RestTemplate
拥有负载均衡的能力?
== 若对 Spring、SpringBoot、MyBatis 等源码分析感兴趣,可加我 wx:fsx641385712,手动邀请你入群一起飞 ==
== 若对 Spring、SpringBoot、MyBatis 等源码分析感兴趣,可加我 wx:fsx641385712,手动邀请你入群一起飞 ==