如何正确理解RestTemplate远程调用的实现原理?

2024-05-08 13:28

本文主要是介绍如何正确理解RestTemplate远程调用的实现原理?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文从源码出发理解RestTemplate实现远程调用的底层原理。

初始化RestTemplate实例

我们可以通过RestTemplate所提供的几个构造函数来对其进行初始化。在分析这些构造函数之前,有必要先看一下RestTemplate类的定义,如下所示:

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations

可以看到,RestTemplate扩展了InterceptingHttpAccessor抽象类,并实现了RestOperations接口。我们围绕RestTemplate的方法定义来梳理它在设计上的思想。

首先,我们来到RestOperations接口的定义,这里截取了部分核心方法,如下所示:

public interface RestOperations {

<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;

<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;

<T> T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables) throws RestClientException;

void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException;

void delete(String url, Object... uriVariables) throws RestClientException;

<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,

Class<T> responseType, Object... uriVariables) throws RestClientException;

}

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

然后,我们再来看InterceptingHttpAccessor,它是一个抽象类,包含的核心变量如下所示:

public abstract class InterceptingHttpAccessor extends HttpAccessor {

private final List<ClientHttpRequestInterceptor> 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的类层结构,如下所示:


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

介绍完RestTemplate的实例化过程,接下来我们来分析它的核心执行流程。作为用于远程调用的模板工具类,我们可以从具备多种请求方式的exchange方法入手,该方法如下所示:

@Override

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,

@Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)

throws RestClientException {

//构建请求回调

RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);

//构建响应体抽取器

ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);

//执行远程调用

return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));

}

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

@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);

}

execute方法首先通过UriTemplateHandler构建了一个URI,然后将请求过程委托给了doExecute方法进行处理,该方法定义如下:

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,

@Nullable ResponseExtractor<T> 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类,我们可以先来看一下该类的类层结构,如下图所示:


在上图的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<T> {

@Nullable

T extractData(ClientHttpResponse response) throws IOException;

}

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

private class ResponseEntityResponseExtractor <T> implements ResponseExtractor<ResponseEntity<T>> {

@Nullable

private final HttpMessageConverterExtractor<T> 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<T> 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类的内部,肯定是使用了上一课时所介绍的HttpMessageConverter来完成消息的转换,如下所示(代码做了裁剪):

public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {

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;

在上一课时中,我们已经提到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请求的完整流程就介绍完毕了。

这篇关于如何正确理解RestTemplate远程调用的实现原理?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/970446

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形