Spring Boot进阶:原理、实战与面试题分析
上QQ阅读APP看书,第一时间看更新

4.1.3 RestTemplate远程调用原理分析

在4.1.2节中,我们详细描述了如何使用RestTemplate访问HTTP端点,涉及RestTemplate初始化、发起请求以及获取响应结果等核心环节。在本节中,我们将基于这些步骤,从源码出发深入理解RestTemplate实现远程调用的底层原理。

1. 远程调用主流程

我们先来看一下RestTemplate类的定义,如代码清单4-15所示。

代码清单4-15 RestTemplate类的定义代码

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations

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

首先,我们来看RestOperations接口的定义,这里截取了部分核心方法,如代码清单4-16所示。

代码清单4-16 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/exchange等远程调用方法组,而这些方法都是遵循RESTful架构风格而设计的。RestTemplate对这些接口都提供了实现,这是它的一条代码支线。

然后,我们再来看InterceptingHttpAccessor,它是一个抽象类,包含的核心变量如代码清单4-17所示。

代码清单4-17 InterceptingHttpAccessor中的核心变量定义代码

public abstract class InterceptingHttpAccessor extends HttpAccessor {
    private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    private volatile ClientHttpRequestFactory interceptingRequestFactory;
    ...
}

通过变量定义,我们明确了InterceptingHttpAccessor应该包含两部分处理功能,一部分是设置和管理请求拦截器ClientHttpRequestInterceptor,另一部分则是获取用于创建客户端HTTP请求的工厂类ClientHttpRequestFactory。这是RestTemplate的另一条代码支线。

同时,我们注意到InterceptingHttpAccessor同样存在一个父类HttpAccessor,这个父类值得展开讨论一下,因为它真正完成了ClientHttpRequestFactory的创建以及通过ClientHttpRequestFactory获取了代表客户端请求的ClientHttpRequest对象。HttpAccessor的核心变量如代码清单4-18所示。

代码清单4-18 HttpAccessor中的核心变量定义代码

public abstract class HttpAccessor {
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpReques-tFactory();
    ...
}

可以看到,HttpAccessor创建了SimpleClientHttpRequestFactory作为系统默认的Client-HttpRequestFactory。关于ClientHttpRequestFactory,本节还会进行详细的讨论。

作为总结,我们来梳理一下RestTemplate的基本类层结构,如图4-1所示。

085-1

图4-1 RestTemplate的类层结构

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

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

代码清单4-19 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 = responseEntityExtra-ctor(responseType);
    //执行远程调用
    return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}

显然,我们应该进一步关注这里的execute()方法。事实上,无论我们采用get/put/post/delete方法组中的哪个方法来发起请求,RestTemplate负责执行远程调用的都是这个execute()方法,该方法定义如代码清单4-20所示。

代码清单4-20 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()方法进行处理,该方法定义如代码清单4-21所示。

代码清单4-21 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) {
        ...
    }
    finally {
        if (response != null) {
            response.close();
        }
    }
}

从上述方法中,我们可以清晰地看到使用RestTemplate进行远程调用所涉及的三大步骤,即创建请求对象、执行远程调用以及处理响应结果。让我们一起来分别看一下。

2. 创建请求对象

创建请求对象的入口方法如代码清单4-22所示。

代码清单4-22 创建请求对象的入口方法代码

ClientHttpRequest request = createRequest(url, method);

分析这里的createRequest()方法,我们发现流程执行到了前面介绍的HttpAccessor类,如代码清单4-23所示。

代码清单4-23 HttpAccessor类实现代码

public abstract class HttpAccessor {
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpReques-tFactory();
    ...
    protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
        ClientHttpRequest request = getRequestFactory().createRequest(url, method);
        ...
        return request;
    }
}

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

代码清单4-24 ClientHttpRequestFactory接口定义代码

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

在Spring Boot中,存在一批ClientHttpRequestFactory接口的实现类,SimpleClient-HttpRequestFactory是它的默认实现,开发人员也可以根据需要实现自定义的ClientHttp-RequestFactory。简单起见,我们直接跟踪SimpleClientHttpRequestFactory的代码,来到它的createRequest()方法,如代码清单4-25所示。

代码清单4-25 SimpleClientHttpRequestFactory的createRequest()方法代码

@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的实例。

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

3. 执行远程调用

一旦获取了请求对象,就可以发起远程调用并获取响应结果了,RestTemplate中的入口方法如代码清单4-26所示。

代码清单4-26 通过RestTemplate获取响应结果代码

response = request.execute();

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

088-1

图4-2 SimpleBufferingClientHttpRequest类层结构

在图4-2的AbstractClientHttpRequest抽象类中,定义了如代码清单4-27所示的execute()方法。

代码清单4-27 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中,如代码清单4-28所示。

代码清单4-28 AbstractBufferingClientHttpRequest的executeInternal ()方法代码

@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类来实现,如代码清单4-29所示。

代码清单4-29 SimpleBufferingClientHttpRequest的executeInternal ()方法代码

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    addHeaders(this.connection, headers);
    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 {
        this.connection.getResponseCode();
    }
    return new SimpleClientHttpResponse(this.connection);
}

这里通过FileCopyUtils.copy()工具方法将响应结果写入到输出流上。而executeInternal()方法最终返回的是一个包装了Connection对象的SimpleClientHttpResponse。

4. 处理响应结果

一个HTTP请求处理的最后一步就是从ClientHttpResponse中读取输入流,格式化成一个响应体并将其转化为业务对象,如代码清单4-30所示。

代码清单4-30 从ClientHttpResponse中提取结果数据代码

//处理调用结果
handleResponse(url, method, response);
//从结果中提取数据
return (responseExtractor != null ? responseExtractor.extractData(response) : null);

我们先来看这里的handleResponse()方法,如代码清单4-31所示。

代码清单4-31 handleResponse()方法代码

protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
    ResponseErrorHandler errorHandler = getErrorHandler();
    boolean hasError = errorHandler.hasError(response);
    if (logger.isDebugEnabled()) {
        ...
    }
    if (hasError) {
        errorHandler.handleError(url, method, response);
    }
}

这段代码实际上并没有真正处理返回的数据,而只是执行了对错误的处理。通过getErrorHandler()方法获取了一个ResponseErrorHandler,如果响应的状态码是错误的,那么就调用handleError处理错误并抛出异常。

那么,获取响应数据并完成转化的工作就应该是在ResponseExtractor中,该接口定义如代码清单4-32所示。

代码清单4-32 ResponseExtractor接口定义代码

public interface ResponseExtractor<T> {
    @Nullable
    T extractData(ClientHttpResponse response) throws IOException;
}

在RestTemplate类中,定义了一个ResponseEntityResponseExtractor内部类来实现Response-Extractor接口,如代码清单4-33所示。

代码清单4-33 ResponseEntityResponseExtractor类代码

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的类型就是HttpMessageConverter-Extractor。从命名上看,我们不难联想,在HttpMessageConverterExtractor类的内部,势必使用了HttpMessageConverter来完成消息的转换。其核心逻辑就是遍历HttpMessageConveter列表,然后判断其是否能够读取数据,如果能就调用read()方法读取数据。

最后,我们来讨论一下HttpMessageConveter中的read()方法是如何实现的。让我们来看HttpMessageConveter接口的抽象实现类AbstractHttpMessageConverter,在它的read()方法中同样定义了一个抽象方法readInternal(),如代码清单4-34所示。

代码清单4-34 AbstractHttpMessageConverter的read()方法代码

@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 Boot内置了一系列的HttpMessageConveter来完成消息的转换,这里面最简单的就是StringHttpMessageConverter,该类的read()方法如代码清单4-35所示。

代码清单4-35 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对象;然后通过copy-ToString()方法从该对象中读取数据,并返回字符串结果。

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