所有方法大体执行逻辑一致,都是和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拥有负载均衡的能力?