侧边栏壁纸
博主头像
Leokoの小破站博主等级

行动起来,活在当下

  • 累计撰写 14 篇文章
  • 累计创建 10 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

RestTemplate 源码分析

Leoko
2023-08-26 / 0 评论 / 0 点赞 / 157 阅读 / 27844 字

1. 类结构

借助 IDEA 查看 RestTemplate 的类结构:

image-20231020103204730

  • RestOperations:接口,意为 Rest 操作,实际上也是。常用的 getForObjectpostForObject 等方法都是在这个接口里面定义的。

    image-20231020110301636

  • HttpAccessorHttp 访问器。可以设置和获取创建请求的工厂 ClientHttpRequestFactory、创建请求、设置请求的初始化器。

  • InterceptingHttpAccessorHttpAccessor的子类,增加了拦截器,可以在请求发送前做一些拦截操作。

2. 基本使用

SpringBoot 项目为例:

@Configuration
public class MainConfiguation {
​
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

RestTemplate 注入到 IOC 容器后,在需要使用的地方 Autowried 一下 RestTemplate ,然后调用 getForObject 等方法就行了。

3. 源码分析

3.1 构造方法

点进例子中 RestTemplate 的无参构造方法:

public RestTemplate() {
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    ......
    this.uriTemplateHandler = initUriTemplateHandler();    
}

构造方法就干了两件事:

  • 添加一大堆 HttpMessageConverter,负责转换HTTP的请求和响应。

  • 初始化 uri 模板处理器,对 uri 的拼接操作,将路径参数参数拼接到 url 后面,就像下面这种:

    String name = "zhangsan"
    restTemplate.getForObject("http://xxx/{name}", String.class, name);

3.2 getForObject 源码

点进去上面的 getForObject 方法:

// RestTemplate
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
    // 获取请求回调
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    // 初始化消息转换提取器
    HttpMessageConverterExtractor<T> responseExtractor =
        new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    // 执行请求
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
​
public <T> RequestCallback acceptHeaderRequestCallback(Class<T> responseType) {
    return new AcceptHeaderRequestCallback(responseType);
}

AcceptHeaderRequestCallback ,看名字好像是关于请求头中与 Accept-xxx 属性相关的?先不急,接下来会讲。

接着进入 execute 方法:

// RestTemplate
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
            @Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables)
    throws RestClientException {
    // uri 拼接
    URI expanded = getUriTemplateHandler().expand(url, uriVariables);
    // 真正发送请求
    return doExecute(expanded, method, requestCallback, responseExtractor);
}
​
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
            @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
​
    ..........
    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) {
       .......
    }
    ........
}

可以看作是分四步执行:

  • 创建请求 createRequest

  • 回调点执行 requestCallback.doWithRequest

  • 发送请求 request.execute()

  • 处理响应结果 handleResponse

3.2.1 createRequest

// HttpAccessor
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
​
public ClientHttpRequestFactory getRequestFactory() {
    return this.requestFactory;
}
​
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
    ClientHttpRequest request = getRequestFactory().createRequest(url, method);
    initialize(request);
    if (logger.isDebugEnabled()) {
        logger.debug("HTTP " + method.name() + " " + url);
    }
    return request;
}
3.2.1.1 请求工厂的选择
// InterceptingHttpAccessor
public ClientHttpRequestFactory getRequestFactory() {
    // 配置了拦截器,使用 InterceptingClientHttpRequestFactory
    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 {
        // 没有,使用默认的 SimpleClientHttpRequestFactory 
        // 例子中没有配置,走这个逻辑
        return super.getRequestFactory();
    }
}
3.2.1.2 SimpleClientHttpRequestFactory

默认创建请求的工厂是 SimpleClientHttpRequestFactory。工厂中可以设置请求的相关参数:

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
​
    private static final int DEFAULT_CHUNK_SIZE = 4096;
    private boolean bufferRequestBody = true;
    private int chunkSize = DEFAULT_CHUNK_SIZE;
    private int connectTimeout = -1;
    private int readTimeout = -1;
    private boolean outputStreaming = true;
    .......
}

可以看到有连接超时时间、读取超时时间等,默认是 -1,也就是不超时。如果想要设置 RestTemplate 请求的超时时间等,需要改一下 RestTemplate 的初始化注入,声明创建请求的工厂并设置参数。

@Configuration
public class MainConfiguation {
​
    @Bean
    public RestTemplate restTemplate(){
         RestTemplate restTemplate = new RestTemplate();
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        // 禁用请求体缓存
        requestFactory.setBufferRequestBody(false);
        // 设置连接超时时间为 5 秒
        requestFactory.setConnectTimeout(5000); 
        // 设置读取超时时间为 5 秒
        requestFactory.setReadTimeout(5000);
        restTemplate.setRequestFactory(requestFactory);
        return restTemplate;
    }
}
3.2.1.2 SimpleClientHttpRequestFactory # createRequest

回到 getRequestFactory().createRequest(url, method),进入 createRequest 方法

// SimpleClientHttpRequestFactory
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);
    }
}
1. openConnection & prepareConnection
protected HttpURLConnection openConnection(URL url, @Nullable Proxy proxy) throws IOException {
    URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
    if (!(urlConnection instanceof HttpURLConnection)) {
        throw new IllegalStateException(
            "HttpURLConnection required for [" + url + "] but got: " + urlConnection);
    }
    return (HttpURLConnection) urlConnection;
}
​
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
    if (this.connectTimeout >= 0) {
        connection.setConnectTimeout(this.connectTimeout);
    }
    if (this.readTimeout >= 0) {
        connection.setReadTimeout(this.readTimeout);
    }
​
    boolean mayWrite =
        ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
         "PATCH".equals(httpMethod) || "DELETE".equals(httpMethod));
​
    connection.setDoInput(true);
    connection.setInstanceFollowRedirects("GET".equals(httpMethod));
    connection.setDoOutput(mayWrite);
    connection.setRequestMethod(httpMethod);
}
​

这两个方法里面的代码就跟普通使用 URLConnection 进行 http 请求一样,RestTemplate 做了封装而已,这里不过多讲解,不熟悉的可以先学习下怎么使用 URLConnection 发送 http 请求。

3.2.1.3 initialize(request)

初始化请求。

// RestTemplate
private void initialize(ClientHttpRequest request) {
    this.clientHttpRequestInitializers.forEach(initializer -> initializer.initialize(request));
}

遍历 clientHttpRequestInitializers 集合,执行每个 ClientHttpRequestInitializerinitialize 方法。而 clientHttpRequestInitializers 是在 RestTemplate 的爷爷里面,默认为空。

public abstract class HttpAccessor {
    private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList<>();
    
    public void setClientHttpRequestInitializers(
            List<ClientHttpRequestInitializer> clientHttpRequestInitializers) {
​
        if (this.clientHttpRequestInitializers != clientHttpRequestInitializers) {
            this.clientHttpRequestInitializers.clear();
            this.clientHttpRequestInitializers.addAll(clientHttpRequestInitializers);
            AnnotationAwareOrderComparator.sort(this.clientHttpRequestInitializers);
        }
    }
    
    public List<ClientHttpRequestInitializer> getClientHttpRequestInitializers() {
        return this.clientHttpRequestInitializers;
    }
}

ClientHttpRequestInitializer 是一个接口。一般情况下,可以使用它来设置请求的头部信息、认证凭据等。

3.2.2 回调点执行 requestCallback.doWithRequest

之前贴的代码片段知道了 requestCallback 的类型是 AcceptHeaderRequestCallback,所以直接进入这个类看它的 doWithRequest 方法,而它其实是 RestTemplate 的内部类。

对于 getForObject 来说,类型是AcceptHeaderRequestCallback ,用来设置请求头;

对于 postForObject 来说,类型是 HttpEntityRequestCallback ,用来设置 httpEntity

// AcceptHeaderRequestCallback
public void doWithRequest(ClientHttpRequest request) throws IOException {
    if (this.responseType != null) {
        List<MediaType> allSupportedMediaTypes = getMessageConverters().stream()
            .filter(converter -> canReadResponse(this.responseType, converter))
            .flatMap((HttpMessageConverter<?> converter) -> getSupportedMediaTypes(this.responseType, converter))
            .distinct()
            .sorted(MediaType.SPECIFICITY_COMPARATOR)
            .collect(Collectors.toList());
        if (logger.isDebugEnabled()) {
            logger.debug("Accept=" + allSupportedMediaTypes);
        }
        request.getHeaders().setAccept(allSupportedMediaTypes);
    }
}

发送 HTTP 请求时设置请求头部中的 Accept 字段。Accept 字段表示客户端能够接受的响应内容类型。

image-20231021142753055

3.2.3 request.execute()

由于之前 SimpleClientHttpRequestFactory # createRequest 创建请求时,bufferRequestBody 默认为 true,所以当前 request 的类型是 SimpleBufferingClientHttpRequest

先看下 SimpleBufferingClientHttpRequest 的类结构。

image-20231022164446828

execute() 方法是定义在 ClientHttpRequest 接口里面的,由抽象类 AbstractClientHttpRequest 实现。

// AbstractClientHttpRequest
public final ClientHttpResponse execute() throws IOException {
    assertNotExecuted();
    ClientHttpResponse result = executeInternal(this.headers);
    this.executed = true;
    return result;
}
​
// 由子类 AbstractBufferingClientHttpRequest 实现
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;
// AbstractBufferingClientHttpRequest
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
    .......
    // 轮到 SimpleBufferingClientHttpRequest
    ClientHttpResponse result = executeInternal(headers, bytes);
    .......
}
​
// 这里才由子类 SimpleBufferingClientHttpRequest 实现
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput)
            throws IOException;

父类 AbstractBufferingClientHttpRequest 中的 executeInternal() 方法才是由 SimpleBufferingClientHttpRequest 直接实现的。

// SimpleBufferingClientHttpRequest
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    addHeaders(this.connection, headers);
    .......
    this.connection.connect();
    ......
    return new SimpleClientHttpResponse(this.connection);
}

与服务端建立连接,并返回 SimpleClientHttpResponse

4. 进阶使用:拦截器

前面讲过 createRequest 是有创建工厂的选择,而这个选择跟 RestTemplate 是否配置了拦截器相关。

// RestTemplate 的父类
public abstract class InterceptingHttpAccessor extends HttpAccessor {
    
    private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    
    public List<ClientHttpRequestInterceptor> getInterceptors() {
            return this.interceptors;
    }
    
    public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
        Assert.noNullElements(interceptors, "'interceptors' must not contain null elements");
        // Take getInterceptors() List as-is when passed in here
        if (this.interceptors != interceptors) {
            this.interceptors.clear();
            this.interceptors.addAll(interceptors);
            AnnotationAwareOrderComparator.sort(this.interceptors);
        }
    }
}

ClientHttpRequestInterceptorSpring 框架提供的一个接口,它允许开发人员在发送 HTTP 请求之前或之后对请求进行拦截和处理,我们可以根据需要对请求进行修改、添加头信息、记录日志等操作。

4.1 使用示例

public class MyHttpRequestInterceptor implements ClientHttpRequestInterceptor {
​
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
            ClientHttpRequestExecution execution) throws IOException {
        // 对请求进行拦截和处理
        System.out.println("Intercepting request: " + request.getURI());
​
        // 可以修改请求头信息
        HttpHeaders headers = request.getHeaders();
        headers.add("X-Custom-Header", "custom-value");
​
        // 可以修改请求体数据
        byte[] modifiedBody = modifyRequestBody(body);
​
        // 继续执行请求
        ClientHttpResponse response = execution.execute(request, modifiedBody);
​
        // 可以对响应进行处理
        System.out.println("Received response with status code: " + response.getStatusCode());
        
        return response;
    }
    
    private byte[] modifyRequestBody(byte[] body) {
        // 对请求体进行修改
        // ...
        return modifiedBody;
    }
}
​
@Configuration
public class MainConfiguation {
​
    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add(new MyHttpRequestInterceptor());
        return restTemplate;
    }
}

4.2 流程分析

配置了拦截器之后,使用的请求工厂就是 InterceptingClientHttpRequestFactory了。

public ClientHttpRequestFactory getRequestFactory() {
    List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
    if (!CollectionUtils.isEmpty(interceptors)) {
        ClientHttpRequestFactory factory = this.interceptingRequestFactory;
        if (factory == null) {
            // 注意这里,super.getRequestFactory() 默认还是 SimpleClientHttpRequestFactory
            factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
            this.interceptingRequestFactory = factory;
        }
        return factory;
    }
    ......
}

SimpleClientHttpRequestFactorycreateRequest不同,InterceptingClientHttpRequestFactorycreateRequest 仅仅只是创建了一个请求对象,类型是 InterceptingClientHttpRequest,并把设置的拦截器列表传递过去。

为什么它的 createRequest 不用 openConnection & prepareConnection 呢? 先别急,后面有答案。

// InterceptingClientHttpRequestFactory
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
    return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
}
4.2.1 InterceptingClientHttpRequest # executeInternal

请求类型为 InterceptingClientHttpRequest 时,request.execute() 方法的执行逻辑和前面是类似的,不过最终是执行 InterceptingClientHttpRequestexecuteInternal 方法。

// InterceptingClientHttpRequest 
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
    InterceptingClientHttpRequest.InterceptingRequestExecution requestExecution = new InterceptingClientHttpRequest.InterceptingRequestExecution();
    return requestExecution.execute(this, bufferedOutput);
}
private class InterceptingRequestExecution implements ClientHttpRequestExecution {
    private final Iterator<ClientHttpRequestInterceptor> iterator;
​
    public InterceptingRequestExecution() {
        this.iterator = InterceptingClientHttpRequest.this.interceptors.iterator();
    }
​
    public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
        // 遍历拦截器列表并执行拦截器的 intercept 方法
        if (this.iterator.hasNext()) {
            ClientHttpRequestInterceptor nextInterceptor = (ClientHttpRequestInterceptor)this.iterator.next();
            return nextInterceptor.intercept(request, body, this);
        } else {
            HttpMethod method = request.getMethod();
            Assert.state(method != null, "No standard HTTP method");
            ClientHttpRequest delegate = InterceptingClientHttpRequest.this.requestFactory.createRequest(request.getURI(), method);
            request.getHeaders().forEach((key, value) -> {
                delegate.getHeaders().addAll(key, value);
            });
            if (body.length > 0) {
                if (delegate instanceof StreamingHttpOutputMessage) {
                    StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage)delegate;
                    streamingOutputMessage.setBody((outputStream) -> {
                        StreamUtils.copy(body, outputStream);
                    });
                } else {
                    StreamUtils.copy(body, delegate.getBody());
                }
            }
​
            return delegate.execute();
        }
    }
}

拦截器都执行完毕后,有一行代码:ClientHttpRequest delegate = InterceptingClientHttpRequest.this.requestFactory.createRequest(request.getURI(), method);

可以看到最终创建请求的是 InterceptingClientHttpRequest.this.requestFactory,而这个构造函数那里说了,默认还是 SimpleClientHttpRequestFactory,又回到最初的分析了。看到这是不是就解答了之前的疑问呢,InterceptingClientHttpRequestFactory 创建请求不用开启连接、准备连接,因为它交给代理来做了。不得不说,这个设计真的妙!

5. ClientHttpRequestInitializer 和 ClientHttpRequestInterceptor

前面提到过 ClientHttpRequestInitializer 也能对请求做一下初始化操作,那和 ClientHttpRequestInterceptor 的区别是什么呢? 进入 ClientHttpRequestInitializer 看类注释:

image-20231022180256219

有一句话:不同于 ClientHttpRequestInterceptorClientHttpRequestInitializer 不需要将整个请求体读入内存就能实现自定义操作。

image-20231022180522798

image-20231022180513734

再来比较一下接口,拦截器的 intercept 方法除了用来遍历拦截器列表并创建请求的 ClientHttpRequestExecution外,比 ClientHttpRequestInitializerinitialize 方法多了一个 body 即请求体。所以注释中说的区别就是这个了。

0

评论区