13 服务调用:如何正确理解 RestTemplate 远程调用实现原理? 在 12 讲中,我们详细描述了如何使用 RestTemplate 访问 HTTP 端点的使用方法,它涉及 RestTemplate 初始化、发起请求及获取响应结果等核心环节。今天,我们将基于上一课时中的这些环节,从源码出发让你真正理解 RestTemplate 实现远程调用的底层原理。

初始化 RestTemplate 实例

12 讲中我们提到可以通过 RestTemplate 提供的几个构造函数对 RestTemplate 进行初始化。在分析这些构造函数之前,我们有必要先看一下 RestTemplate 类的定义,如下代码所示: public class RestTemplate extends InterceptingHttpAccessor implements RestOperations

从上述代码中,我们可以看到 RestTemplate 扩展了 InterceptingHttpAccessor 抽象类,并实现了 RestOperations 接口。接下来我们围绕 RestTemplate 的方法定义进行设计思路的梳理。

首先,我们来看看 RestOperations 接口的定义,这里截取了部分核心方法,如下代码所示: public interface RestOperations { T getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException; ResponseEntity getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException; T postForObject(String url, @Nullable Object request, Class responseType,Object... uriVariables) throws RestClientException; void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException; void delete(String url, Object... uriVariables) throws RestClientException; ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class responseType, Object... uriVariables) throws RestClientException; … }

显然,RestOperations 接口定义了 12 讲中介绍到的 get/post/put/delete/exhange 等所有远程调用方法组,这些方法都遵循 RESTful 架构风格而设计。RestTemplate 为这些接口提供了实现机制,这是它的一条代码支线。

然后我们再看 InterceptingHttpAccessor,它是一个抽象类,包含的核心变量如下代码所示: public abstract class InterceptingHttpAccessor extends HttpAccessor { private final List interceptors = new ArrayList<>(); private volatile ClientHttpRequestFactory interceptingRequestFactory; … }

通过变量定义,我们明确了 InterceptingHttpAccessor 包含两部分处理功能,一部分负责设置和管理请求拦截器 ClientHttpRequestInterceptor,另一部分负责获取用于创建客户端 HTTP 请求的工厂类 ClientHttpRequestFactory。

同时,我们注意到 InterceptingHttpAccessor 同样存在一个父类 HttpAccessor,这个父类值真正实现了 ClientHttpRequestFactory 的创建及如何通过 ClientHttpRequestFactory 获取代表客户端请求的 ClientHttpRequest 对象。HttpAccessor 的核心变量如下代码所示: public abstract class HttpAccessor { private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); … }

从以上代码我们可以看到,HttpAccessor 中创建了 SimpleClientHttpRequestFactory 作为系统默认的 ClientHttpRequestFactory。关于 ClientHttpRequestFactory,我们会在本课时的后续内容中进行详细的讨论。

最后,针对这部分内容我们再来梳理下 RestTemplate 的类层结构,如下图所示:

图片3.png

RestTemplate 的类层结构

在 RestTemplate 的类层结构中,我们能快速理解它的设计思想。整个类层结构清晰地分成两条支线,左边支线用于完成与 HTTP 请求相关的实现机制,而右边支线提供了基于 RESTful 风格的操作入口,并使用了面向对象中的接口和抽象类完成这两部分功能的聚合。

RestTemplate 核心执行流程

介绍完 RestTemplate 的实例化过程,接下来我们来分析它的核心执行流程。

作为用于远程调用的模板工具类,我们可以从具备多种请求方式的 exchange 方法入手,该方法的定义如下代码所示: @Override public ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class responseType, Object... uriVariables) throws RestClientException { //构建请求回调 RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); //构建响应体抽取器 ResponseExtractor<ResponseEntity> responseExtractor = responseEntityExtractor(responseType); //执行远程调用 return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); }

显然,我们应该进一步关注这里的 execute 方法。事实上,无论我们采用 get/put/post/delete 中的哪种方法发起请求,RestTemplate 负责执行远程调用时,使用的都是 execute 方法,该方法定义如下代码所示:

@Override @Nullable public T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor, Object... uriVariables) throws RestClientException { URI expanded = getUriTemplateHandler().expand(url, uriVariables); return doExecute(expanded, method, requestCallback, responseExtractor); }

从以上代码中,我们发现 execute 方法首先通过 UriTemplateHandler 构建了一个 URI,然后将请求过程委托给 doExecute 方法进行处理,该方法定义如下代码所示:

protected T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor) throws RestClientException { Assert.notNull(url, "URI is required"); Assert.notNull(method, "HttpMethod is required"); ClientHttpResponse response = null; try { //创建请求对象 ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { //执行对请求的回调 requestCallback.doWithRequest(request); } //获取调用结果 response = request.execute(); //处理调用结果 handleResponse(url, method, response); //使用结果提取从结果中提取数据 return (responseExtractor != null ? responseExtractor.extractData(response) : null); } catch (IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex); } finally { if (response != null) { response.close(); } } }

从上述方法中,我们发现使用 RestTemplate 进行远程调用时,主要涉及创建请求对象、执行远程调用及处理响应结果这三大步骤,下面我们分别展开说明下。

创建请求对象

创建请求对象的入口方法如下代码所示: ClientHttpRequest request = createRequest(url, method);

通过跟踪上面的 createRequest 方法,我们发现流程执行到了前面介绍的 HttpAccessor 类,如下代码所示:

public abstract class HttpAccessor { private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); … protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { ClientHttpRequest request = getRequestFactory().createRequest(url, method); if (logger.isDebugEnabled()) { logger.debug(“Created “ + method.name() + “ request for "” + url + “"”); } return request; } }

创建 ClientHttpRequest 的过程是一种典型的工厂模式应用场景,这里我们直接创建了一个实现 ClientHttpRequestFactory 接口的 SimpleClientHttpRequestFactory 对象,然后再通过这个对象的 createRequest 方法创建了客户端请求对象 ClientHttpRequest 并返回给上层组件进行使用。ClientHttpRequestFactory 接口的定义如下代码所示:

public interface ClientHttpRequestFactory { //创建客户端请求对象 ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException; }

在 Spring 中,存在一批 ClientHttpRequestFactory 接口的实现类,而SimpleClientHttpRequestFactory 是它的默认实现,在实现自定义的 ClientHttpRequestFactory 时,开发人员也可以根据需要自行选择。

为简单起见,我们直接跟踪 SimpleClientHttpRequestFactory 的代码,来看它的 createRequest 方法,如下代码所示: private boolean bufferRequestBody = true; @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); prepareConnection(connection, httpMethod.name()); if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); } }

在上述 createRequest 中,首先我们通过传入的 URI 对象构建了一个 HttpURLConnection 对象,然后对该对象进行一些预处理,最后构造并返回一个 ClientHttpRequest 实例。

通过翻阅代码,我们发现上述的 openConnection 方法只是通过 URL 对象的 openConnection 方法返回了一个 UrlConnection,而 prepareConnection 方法也只是完成了对 HttpUrlConnection 超时时间、请求方法等常见属性的设置。

在这里,我们注意到 bufferRequestBody 参数的值为 true,因此通过 createRequest 方法最终返回的结果是一个 SimpleBufferingClientHttpRequest 对象。

执行远程调用

一旦获取了请求对象,我们就可以发起远程调用并获取响应了,RestTemplate 中的入口方法如下代码所示: response = request.execute();

这里的 request 就是前面创建的 SimpleBufferingClientHttpRequest 类,我们可以先来看一下该类的类层结构,如下图所示:

图片5.png

SimpleBufferingClientHttpRequest 类层结构图

在上图的 AbstractClientHttpRequest 中,定义了如下代码所示的 execute 方法。 @Override public final ClientHttpResponse execute() throws IOException { assertNotExecuted(); ClientHttpResponse result = executeInternal(this.headers); this.executed = true; return result; } protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;

AbstractClientHttpRequest 类的作用是防止 HTTP 请求的 Header 和 Body 被多次写入,所以在 execute 方法返回之前,我们设置了一个 executed 标志位。同时,在 execute 方法中,我们最终调用了一个抽象方法 executeInternal,这个方法的实现在 AbstractClientHttpRequest 的子类 AbstractBufferingClientHttpRequest 中,如下代码所示:

@Override protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { byte[] bytes = this.bufferedOutput.toByteArray(); if (headers.getContentLength() < 0) { headers.setContentLength(bytes.length); } ClientHttpResponse result = executeInternal(headers, bytes); this.bufferedOutput = new ByteArrayOutputStream(0); return result; } protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException;

和 AbstractClientHttpRequest 类一样,我们进一步梳理了一个抽象方法 executeInternal,这个抽象方法通过最底层的 SimpleBufferingClientHttpRequest 类实现,如下代码所示:

@Override protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { addHeaders(this.connection, headers); // JDK <1.8 doesn’t support getOutputStream with HTTP DELETE if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) { this.connection.setDoOutput(false); } if (this.connection.getDoOutput() && this.outputStreaming) { this.connection.setFixedLengthStreamingMode(bufferedOutput.length); } this.connection.connect(); if (this.connection.getDoOutput()) { FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream()); } else { // Immediately trigger the request in a no-output scenario as well this.connection.getResponseCode(); } return new SimpleClientHttpResponse(this.connection); }

这里通过 FileCopyUtils.copy 工具方法,我们把结果写入输出流上了,executeInternal 方法最终返回的结果是一个包装了 Connection 对象的 SimpleClientHttpResponse。

处理响应结果

一个 HTTP 请求处理的最后一步是从 ClientHttpResponse 中读取输入流,然后格式化为一个响应体并将其转化为业务对象,入口代码如下所示: //处理调用结果 handleResponse(url, method, response); //使用结果提取从结果中提取数据 return (responseExtractor != null ? responseExtractor.extractData(response) : null);

我们先来看这里的 handleResponse 方法,定义如下代码所示:

protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { ResponseErrorHandler errorHandler = getErrorHandler(); boolean hasError = errorHandler.hasError(response); if (logger.isDebugEnabled()) { try { logger.debug(method.name() + “ request for "” + url + “" resulted in “ + response.getRawStatusCode() + “ (“ + response.getStatusText() + “)” + (hasError ? “; invoking error handler” : “”)); } catch (IOException ex) { // ignore } } if (hasError) { errorHandler.handleError(url, method, response); } }

以上代码中,通过 getErrorHandler 方法我们获取了一个 ResponseErrorHandler,如果响应的状态码错误,我们可以调用 handleError 来处理错误并抛出异常。在这里,我们发现这段代码实际上并没有真正处理返回的数据,而只是执行了错误处理。

而获取响应数据并完成转化的工作是在 ResponseExtractor 中,该接口定义如下代码所示: public interface ResponseExtractor { @Nullable T extractData(ClientHttpResponse response) throws IOException; }

在 RestTemplate 类中,我们定义了一个 ResponseEntityResponseExtractor 内部类实现了ResponseExtractor 接口,如下代码所示:

private class ResponseEntityResponseExtractor implements ResponseExtractor<ResponseEntity> { @Nullable private final HttpMessageConverterExtractor delegate; public ResponseEntityResponseExtractor(@Nullable Type responseType) { if (responseType != null && Void.class != responseType) { this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); } else { this.delegate = null; } } @Override public ResponseEntity extractData(ClientHttpResponse response) throws IOException { if (this.delegate != null) { T body = this.delegate.extractData(response); return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body); } else { return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build(); } } }

在上述代码中,ResponseEntityResponseExtractor 中的 extractData 方法本质上是将数据提取部分的工作委托给了一个代理对象 delegate,而这个 delegate 的类型就是 HttpMessageConverterExtractor。

从命名上看,我们不难看出 HttpMessageConverterExtractor 类的内部使用了 12 讲介绍的 HttpMessageConverter 实现消息的转换,如下代码所示(代码做了裁剪): public class HttpMessageConverterExtractor implements ResponseExtractor { private final List<HttpMessageConverter<?>> messageConverters; @Override @SuppressWarnings({"unchecked", "rawtypes", "resource"}) public T extractData(ClientHttpResponse response) throws IOException { MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { return null; } MediaType contentType = getContentType(responseWrapper); try { for (HttpMessageConverter<?> messageConverter : this.messageConverters) { if (messageConverter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter<?>) messageConverter; if (genericMessageConverter.canRead(this.responseType, null, contentType)) { return (T) genericMessageConverter.read(this.responseType, null, responseWrapper); } } if (this.responseClass != null) { if (messageConverter.canRead(this.responseClass, contentType)) { return (T) messageConverter.read((Class) this.responseClass, responseWrapper); } } } } … }

上述方法看上去有点复杂,但核心逻辑很简单,首先遍历 HttpMessageConveter 列表,然后判断其是否能够读取数据,如果能就调用 read 方法读取数据。

最后,我们讨论下 HttpMessageConveter 中如何实现 read 方法。

先来看 HttpMessageConveter 接口的抽象实现类 AbstractHttpMessageConverter,在它的 read 方法中我们同样定义了一个抽象方法 readInternal,如下代码所示: @Override public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return readInternal(clazz, inputMessage); } protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

在 12 讲中,我们提到 Spring 提供了一系列的 HttpMessageConveter 实现消息的转换,而最简单的实现方式是 StringHttpMessageConverter,该类的 read 方法如下代码所示:

@Override protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException { Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType()); return StreamUtils.copyToString(inputMessage.getBody(), charset); }

StringHttpMessageConverter 的实现过程:首先从输入消息 HttpInputMessage 中通过 getBody 方法获取消息体,也就是一个 ClientHttpResponse 对象,再通过 copyToString 方法从该对象中读取数据,并返回字符串结果。

至此,通过 RestTemplate 发起、执行及响应整个 HTTP 请求的完整流程就介绍完毕了。

从源码解析到日常开发

本节课涉及了大量关于如果处理 HTTP 请求的实现细节,而这些实现细节对开发人员理解 HTTP 协议、掌握 HTTP 协议及远程调用很大帮助,后期,你可以根据实际需要针对某些细节进一步深入分析。

同时,通过对 RestTemplate 本身及围绕它的多个工具类的设计和实现过程进行梳理,也可以加深我们对抽象类与接口的标准设计理念的理解,并将这些设计理念付诸日常开发过程中。

小结与预告

我们要想深入理解和掌握一个 HTTP 请求的处理过程,剖析 RestTemplate 工具类的实现很有必要。

RestTemplate 中提供了创建请求对象、执行远程调用及处理响应结果这三大步骤的完整实现思路。本节课中我们对这些步骤进行了详细说明,并分析了其中包含的设计理念及实现技巧。

参考资料

https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%20Boot%20%e5%ae%9e%e6%88%98%e5%bc%80%e5%8f%91/13%20%20%e6%9c%8d%e5%8a%a1%e8%b0%83%e7%94%a8%ef%bc%9a%e5%a6%82%e4%bd%95%e6%ad%a3%e7%a1%ae%e7%90%86%e8%a7%a3%20RestTemplate%20%e8%bf%9c%e7%a8%8b%e8%b0%83%e7%94%a8%e5%ae%9e%e7%8e%b0%e5%8e%9f%e7%90%86%ef%bc%9f.md