RestTemplate related components: ClientHttpRequestInterceptor enjoy learning Spring MVC []

Each one

The biggest difference between doing things and the people are dreaming of mobility

Foreword

To better understand this article Springprovided Rest invoking client RestTemplatemountains, some of its related components explaining to do.

Tips: Please note the distinction RestTemplateand RedisTemplateoh ~

ClientHttpRequestFactory

It is a function interface, according to URIand HttpMethodcreate a ClientHttpRequestrequest is sent ~

ClientHttpRequestIt represents the client request, the interface inherits from HttpRequest, HttpOutputMessagethere is only one ClientHttpResponse execute() throws IOExceptionmethod. Wherein Netty、HttpComponents、OkHttp3,HttpUrlConnectionit has implemented ~

// @since 3.0  RestTemplate这个体系都是3.0后才有的
@FunctionalInterface
public interface ClientHttpRequestFactory { 

    // 返回一个ClientHttpRequest,这样调用其execute()方法就可以发送rest请求了~
    ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;
}

Its inheritance tree as follows:
Here Insert Picture Description
you can visually see, we can use Apachethe HttpClient, OkHttp3, Netty4are available, but these require additional lead pack, by default Springusing java.net.HttpURLConnection.

HttpClient latest version: 4.5.10
OkHttp latest version: 4.1.1 (although the version number is 4, but it GAV or 3 oh: com.squareup.okhttp3)
Netty latest version: 4.1.39.Final (version 5 it may have been declared dead)

Spring4.0It is a new asynchronous support AsyncClientHttpRequestFactory(after Spring5.0 marked as obsolete):

// 在Spring5.0后被标记为过时了,被org.springframework.http.client.reactive.ClientHttpConnector所取代(但还是可用的嘛)
@Deprecated
public interface AsyncClientHttpRequestFactory {

    // AsyncClientHttpRequest#executeAsync()返回的是ListenableFuture<ClientHttpResponse>
    // 可见它的异步是通过ListenableFuture实现的
    AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException;
}

Use the factory to create ClientHttpRequest, and then we send the request would not care about the internal details of the specific httpClient (pluggable library using a two-way, three libraries)

SimpleClientHttpRequestFactory

It is Springbuilt default implementation, using JDK built java.net.URLConnectionas a client client.

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {

    private static final int DEFAULT_CHUNK_SIZE = 4096;
    @Nullable
    private Proxy proxy; //java.net.Proxy
    private boolean bufferRequestBody = true; // 默认会缓冲body
    
    // URLConnection's connect timeout (in milliseconds).
    // 若值设置为0,表示永不超时 @see URLConnection#setConnectTimeout(int)
    private int connectTimeout = -1;
    // URLConnection#setReadTimeout(int) 
    // 超时规则同上
    private int readTimeout = -1;
    
    //Set if the underlying URLConnection can be set to 'output streaming' mode.
    private boolean outputStreaming = true;

    // 异步的时候需要
    @Nullable
    private AsyncListenableTaskExecutor taskExecutor;
    ... // 省略所有的set方法
    
    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        
        // 打开一个HttpURLConnection
        HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
        // 设置超时时间、请求方法等一些参数到connection
        prepareConnection(connection, httpMethod.name());

        //SimpleBufferingClientHttpRequest的excute方法最终使用的是connection.connect();
        // 然后从connection中得到响应码、响应体~~~
        if (this.bufferRequestBody) {
            return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
        } else {
            return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
        }
    }

    // createAsyncRequest()方法略,无非就是在线程池里异步完成请求
    ...
}

Note that: JDK <1.8 doesn't support getOutputStream with HTTP DELETEthat is if the JDKversion is less than 1.8, then Delete request is not supported body body.

Demo Show:

public static void main(String[] args) throws IOException {
    SimpleClientHttpRequestFactory clientFactory  = new SimpleClientHttpRequestFactory();
    
    // ConnectTimeout只有在网络正常的情况下才有效,因此两个一般都设置
    clientFactory.setConnectTimeout(5000); //建立连接的超时时间  5秒
    clientFactory.setReadTimeout(5000); // 传递数据的超时时间(在网络抖动的情况下,这个参数很有用)

    ClientHttpRequest client = clientFactory.createRequest(URI.create("https://www.baidu.com"), HttpMethod.GET);
    // 发送请求
    ClientHttpResponse response = client.execute();
    System.out.println(response.getStatusCode()); //200 OK
    System.out.println(response.getStatusText()); // OK
    System.out.println(response.getHeaders()); //

    // 返回内容 是个InputStream
    byte[] bytes = FileCopyUtils.copyToByteArray(response.getBody());
    System.out.println(new String(bytes, StandardCharsets.UTF_8)); // 百度首页内容的html
}

About HttpURLConnectionAPI use, should pay attention to the following points:

  1. HttpURLConnectionObjects can not be directly constructed URL class need to openConnection()be a method of obtaining
  2. HttpURLConnectionThe connect () function, but actually set up a TCP connection with the server, and sends an HTTP request is not practical. HTTP requests until we actually get the server response data (such as call getInputStream (), getResponseCode () method, etc.) when sending out formal
    Configuration information needs to be completed before the connect () method to perform
  3. HttpURLConnectionHTTP-based protocol, which is implemented by the underlying socket communication. If you do not set a timeout (timeout), under abnormal network conditions, it may cause the program not to continue down the dead. Be sure to set up 100% == ==
  4. Text contents by HTTP OutputStream stream writing, the data written to the stream does not immediately sent to the network, but in the buffer memory, when current shutdown to be generated based on the content written HTTP body
  5. When calling the getInputStream () method that returns an input stream for the HTTP server to return the requested information read from.
  6. HttpURLConnection.connect()It is not necessary. When we need to return a value, for example, we use HttpURLConnection.getInputStream () method when it will automatically send a request, so there is no need to call connect () method of (no need to first establish Tcp Well ~).

Which one underlying http library?

We know that HttpURLConnectionit is somewhat insufficient (simple parameters to meet submit) in function. In most cases a Web site may not be so simple, through these pages is not a simple URL can access and may require the user to login and have the appropriate privileges to access this page. In this case, we need to involve Session, Cookie's deal, if you plan to use HttpURLConnection to deal with these details, of course, also possible to achieve, but the difficulty of dealing with them is big.

This time, Apache provides an open source HttpClientprogram, may be used to send an HTTP request, receives the HTTP response (containing the objects HttpGet, HttpPost ... other transmission request).

It does not respond to the cache server can not perform HTML page embedded Javascript code; nor will any page content parsing, processing,

So, here I let Spring to use HttpClient example demonstrates the use of the tripartite library:
1, guide package

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.10</version>
</dependency>

Tips:Requires Apache HttpComponents 4.3 or higher, as of Spring 4.0.

2, case
case content 仅仅只需to use the example of the first sentence replaced with HttpComponentsClientHttpRequestFactoryinstances of it, and the rest do not have to change to successfully see the effect. This class can look at it specifically what to do

// @since 3.1 3.1后出现的。
public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {

    private HttpClient httpClient;
    @Nullable
    private RequestConfig requestConfig; // 这个配置就是可以配置超时等等乱七八糟client属性的类
    private boolean bufferRequestBody = true;

    //=========下面是构造函数们=========
    public HttpComponentsClientHttpRequestFactory() {
        // HttpClientBuilder.create().useSystemProperties().build();
        // 所有若是这里,配置超时时间可以这么来设置也可:
        // System.setProperty(”sun.net.client.defaultConnectTimeout”, “5000″);
        this.httpClient = HttpClients.createSystem();
    }
    // 当然可以把你配置好了的Client扔进来
    public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) {
        this.httpClient = httpClient;
    }
    ... // 省略设置超时时间。。。等等属性的一些get/set
    // 超时信息啥的都是保存在`RequestConfig`里的


    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        HttpClient client = getHttpClient(); // 拿到你指定的client)=(或者系统缺省的)
        // switch语句逻辑:HttpMethod == GET --> HttpGet HEAD --> HttpHead ...
        HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
        postProcessHttpRequest(httpRequest);
        ...
    }
}

The actual use of the HttpClientcompleted request. Further OkHttp3ClientHttpRequestFactoryuse of the okhttp3.OkHttpClienttransmission request; Netty4ClientHttpRequestFactoryusing io.netty.channel.EventLoopGroup. Here not enumerate

After Spring5.0, Netty4ClientHttpRequestFactoryexpired, it is recommended to use org.springframework.http.client.reactive.ReactorClientHttpConnectorinstead of ~


About HttpURLConnection, HttpClient, OkHttpClientsimple comparison:
  • HttpURLConnection:
    - Advantages: JDK built-in support, java standard class
    - disadvantages: API is not friendly enough, nothing package, use up too original, not convenient (which is actually sometimes also considered the advantages of the original as evidenced by good control ~)
  • HttpClient:
    - Advantages: powerful, the API friendly usage is high enough, almost become standard (equivalent to the actual meaning of HttpURLConnectionthe package)
    - disadvantages: lower performance (ratio of HttpURLConnectionlower, but using 4.3 been improved connection pool) , API than the bloated, in fact, Android has abandoned it ~
  • OkHttpClient: Http access to a new generation of clients
    - advantages: a focus on performance and ease of use of the HTTP client (save broadband, Android is recommended), the primary objective of its design is efficient. It provides support for the latest version of the HTTP protocol HTTP / 2 and SPDY is. If HTTP / 2 and SPDY unavailable, OkHttp connection pool to use multiple connections to improve the efficiency
    - No.

Here Insert Picture Description
On Apache HttpClient, Androidafter 5.0 has abandoned the use of it (API too much, too), it is recommended to use the lighter HttpUrlConnection. (Java development or recommended HttpClient)

OkHttpMore advantages: support SPDY, may be incorporated into a plurality of requests for the same host; OkHttp achieve many techniques such as: connection pool, gziping, caching; OkHttp network processing many incurable diseases: YES from many common problems in connection Automatic recovery. If your server is configured with multiple IP addresses, when the first IP connection failed, OkHttp will automatically try the next IP; OkHttp is a Java HTTP + SPDY client development package, which also supports Android. By default, OKHttp automatically handles common network problems like secondary connection, the SSL handshake issues . Supports file upload, download almost all the features, cookie, session, https certificate. Support a request to cancel

In summary, whether Java or Android, I recommend are natural OkHttp(OkHttp Okio use for data transmission. Is the company's own Square, Square also a library with a Retrofit OkHttp combat double) ~~~

Pooling techniques are generally used for long connection, such as Http connection pool for it?
HttpClient 4.3 after using the PoolingHttpClientConnectionManagerconnection pool to manage connections hold the same link on a TCP connection can be multiplexed. HttpClient persistent connection (so it is actually the connection pool tcp connection pool connection pool by way of it there is a very important concept: Routethe concept, representing a line such as baidu.com is a route, 163.com. It is a route ...).

Connection Pooling: http request may be, it may be https request
to join the pool, then technology, you do not always initiate requests to create a new connection (handshake each time they connect three times, efficiency is too low)


AbstractClientHttpRequestFactoryWrapper

Other ClientHttpRequestFactoryin a packaging abstract class with two subclasses implemented as follows

InterceptingClientHttpRequestFactory(important)

InterceptorInterception of the concept is still pretty important. It holds ClientHttpRequestInterceptora request if we want to block sent out is very important (such as full link voltage measurement, you can use it to set token like ~)

// @since 3.1
public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
    // 持有所有的请求拦截器
    private final List<ClientHttpRequestInterceptor> interceptors;

    public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, @Nullable List<ClientHttpRequestInterceptor> interceptors) {
        super(requestFactory);
        // 拦截器只允许通过构造函数设置进来,并且并没有提供get方法方法~
        this.interceptors = (interceptors != null ? interceptors : Collections.emptyList());
    }

    // 此处返回的是一个InterceptingClientHttpRequest,显然它肯定是个ClientHttpRequest嘛~
    @Override
    protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
        return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
    }

}

InterceptingClientHttpRequestThe execute()feature of the process is: If there is an interceptor, the interceptor to to send a request to perform return nextInterceptor.intercept(request, body, this), otherwise on their own.


ClientHttpRequestInterceptor

On request interceptors, Spring MVC built the two most basic realization
Here Insert Picture Description
== BasicAuthorizationInterceptor ==:

// @since 4.3.1  但在Spring5.1.1后推荐使用BasicAuthenticationInterceptor
@Deprecated
public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor {
    
    private final String username;
    private final String password;
    
    // 注意:username不允许包含:这个字符,但是密码是允许的
    public BasicAuthorizationInterceptor(@Nullable String username, @Nullable String password) {
        Assert.doesNotContain(username, ":", "Username must not contain a colon");
        this.username = (username != null ? username : "");
        this.password = (password != null ? password : "");
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // 用户名密码连接起来后,用Base64对字节码进行编码~
        String token = Base64Utils.encodeToString((this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8));
    
        // 放进请求头:key为`Authorization`  然后执行请求的发送
        request.getHeaders().add("Authorization", "Basic " + token);
        return execution.execute(request, body);
    }
}

The interceptor wood on the body there are any changes, just the user names, passwords, help you put a request to the head.

Note that: If your header already exists Authorizationin this key, there will not be covered, which will add Oh. But it does not suggest you have covered the phenomenon ~

== BasicAuthenticationInterceptor ==:
It is used instead of the class. It uses the standard authorization processing head, with reference toHttpHeaders#setBasicAuth、HttpHeaders#AUTHORIZATION

public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor {
    private final String username;
    private final String password;
    // 编码,一般不用指定
    @Nullable
    private final Charset charset;
    ... // 构造函数略

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {

        HttpHeaders headers = request.getHeaders();
        // 只有当请求里不包含`Authorization`这个key的时候,此处才会设置授权头哦
        if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
            
            // 这个方法是@since 5.1之后才提供的~~~~~
            // 若不包含此key,就设置标准的授权头(根据用户名、密码) 它内部也有这如下三步:
            
            // String credentialsString = username + ":" + password;
            // byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
            // String encodedCredentials = new String(encodedBytes, charset);
            
            // 注意:它内部最终还是调用set(AUTHORIZATION, "Basic " + encodedCredentials);这个方法的
            headers.setBasicAuth(this.username, this.password, this.charset);
        }
        return execution.execute(request, body);
    }
}

Description: Although these two requests interceptors provided by Spring, but are not default "fitting" of the pro necessary, manual assembly -

BufferingClientHttpRequestFactory

Other packaging ClientHttpRequestFactory, such ability to cache. If the caching feature is turned on (a switch controlled), it will use the BufferingClientHttpRequestWrapperpackaging of the original ClientHttpRequest. After such transmission request is obtained BufferingClientHttpResponseWrapperin response.


ResponseErrorHandler

Used to determine whether a particular response has the wrong strategy interfaces.

// @since 3.0
public interface ResponseErrorHandler {

    // response里是否有错
    boolean hasError(ClientHttpResponse response) throws IOException;
    // 只有hasError = true时才会调用此方法
    void handleError(ClientHttpResponse response) throws IOException;
     // @since 5.0
    default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
        handleError(response);
    }
}

Inheritance tree as follows:
Here Insert Picture Description

DefaultResponseErrorHandler

SpringImplementation of this policy the default interface, RestTemplateerror handler is used by default it.

// @since 3.0
public class DefaultResponseErrorHandler implements ResponseErrorHandler {

    // 是否有错误是根据响应码来的,所以请严格遵守响应码的规范啊
    // 简单的说4xx和5xx都会被认为有错,否则是无错的  参考:HttpStatus.Series
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        int rawStatusCode = response.getRawStatusCode();
        HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
        return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode));
    }
    ...
    // 处理错误
    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
        if (statusCode == null) {
            throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
        }
        handleError(response, statusCode);
    }
    
    // protected方法,子类对它有复写
    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        String statusText = response.getStatusText();
        HttpHeaders headers = response.getHeaders();
        byte[] body = getResponseBody(response); // 拿到body,把InputStream转换为字节数组
        Charset charset = getCharset(response); // 注意这里的编码,是从返回的contentType里拿的~~~
        
        // 分别针对于客户端错误、服务端错误 包装为HttpClientErrorException和HttpServerErrorException进行抛出
        // 异常内包含有状态码、状态text、头、body、编码等等信息~~~~
        switch (statusCode.series()) {
            case CLIENT_ERROR:
                throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);
            case SERVER_ERROR:
                throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);
            default:
                throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);
        }
    }
    ...
}

Here you can explain to you why we often see a client error, then there's a bunch of information about the status codes +, because these two exceptions.


HttpClientErrorException:

public class HttpClientErrorException extends HttpStatusCodeException {
    ...
    public static HttpClientErrorException create(
            HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) {

        switch (statusCode) {
            case BAD_REQUEST:
                return new HttpClientErrorException.BadRequest(statusText, headers, body, charset);
            case UNAUTHORIZED:
                return new HttpClientErrorException.Unauthorized(statusText, headers, body, charset);
            case FORBIDDEN:
                return new HttpClientErrorException.Forbidden(statusText, headers, body, charset);
            case NOT_FOUND:
                return new HttpClientErrorException.NotFound(statusText, headers, body, charset);
            case METHOD_NOT_ALLOWED:
                return new HttpClientErrorException.MethodNotAllowed(statusText, headers, body, charset);
            case NOT_ACCEPTABLE:
                return new HttpClientErrorException.NotAcceptable(statusText, headers, body, charset);
            case CONFLICT:
                return new HttpClientErrorException.Conflict(statusText, headers, body, charset);
            case GONE:
                return new HttpClientErrorException.Gone(statusText, headers, body, charset);
            case UNSUPPORTED_MEDIA_TYPE:
                return new HttpClientErrorException.UnsupportedMediaType(statusText, headers, body, charset);
            case TOO_MANY_REQUESTS:
                return new HttpClientErrorException.TooManyRequests(statusText, headers, body, charset);
            case UNPROCESSABLE_ENTITY:
                return new HttpClientErrorException.UnprocessableEntity(statusText, headers, body, charset);
            default:
                return new HttpClientErrorException(statusCode, statusText, headers, body, charset);
        }
    }
    ...
}

It is aimed at a different status code HttpStatus, create a different return types, user-friendly controls, it is still quite significance in monitoring

BadRequest, Unauthorized, Forbidden ... and so are HttpClientErrorExceptionsubclasses

HttpServerErrorExceptionSimilarly the code slightly ~


ExtractingResponseErrorHandler

Inherited from DefaultResponseErrorHandler. In RESTfulpopular today, Spring5.0it began offering a class. Http error response using it HttpMessageConverteris converted to the correspondingRestClientException

// @since 5.0 它出现得还是很晚的。继承自DefaultResponseErrorHandler 
// 若你的RestTemplate想使用它,请调用RestTemplate#setErrorHandler(ResponseErrorHandler)设置即可
public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler {
    private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
    
    // 对响应码做缓存
    private final Map<HttpStatus, Class<? extends RestClientException>> statusMapping = new LinkedHashMap<>();
    private final Map<HttpStatus.Series, Class<? extends RestClientException>> seriesMapping = new LinkedHashMap<>();

    // 构造函数、set方法给上面两个Map赋值。因为我们可以自己控制哪些状态码应该报错,哪些不应该了~
    // 以及可以自定义:那个状态码抛我们自定义的异常,哪一系列状态码抛我们自定义的异常,这个十分的便于我们做监控
    ... // 省略构造函数和set方法。。。


    // 增加缓存功能~~~  否则在交给父类
    @Override
    protected boolean hasError(HttpStatus statusCode) {
        if (this.statusMapping.containsKey(statusCode)) {
            return this.statusMapping.get(statusCode) != null;
        } else if (this.seriesMapping.containsKey(statusCode.series())) {
            return this.seriesMapping.get(statusCode.series()) != null;
        } else {
            return super.hasError(statusCode);
        }
    }

    // 这个它做的事:extract:提取
    @Override
    public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        if (this.statusMapping.containsKey(statusCode)) {
            extract(this.statusMapping.get(statusCode), response);
        } else if (this.seriesMapping.containsKey(statusCode.series())) {
            extract(this.seriesMapping.get(statusCode.series()), response);
        } else {
            super.handleError(response, statusCode);
        }
    }


    private void extract(@Nullable Class<? extends RestClientException> exceptionClass, ClientHttpResponse response) throws IOException {
        if (exceptionClass == null) {
            return;
        }

        // 这里使用到了ResponseExtractor返回值提取器,从返回值里提取内容(本文是提取异常)
        HttpMessageConverterExtractor<? extends RestClientException> extractor =
                new HttpMessageConverterExtractor<>(exceptionClass, this.messageConverters);
        RestClientException exception = extractor.extractData(response);
        if (exception != null) { // 若提取到了异常信息,抛出即可
            throw exception;
        }
    }
}

If you want to customize request processing logic unusual, you can also customize the implementation of this interface, of course, it is recommended that you go through inheritance DefaultResponseErrorHandlerto extend ~


ResponseExtractor

Response extractor: from the Responseextraction data. RestTemplateAfter the completion of the request, which is through from ClientHttpResponse~ (such as request header, the request Body the like) to extract the contents of the specified
Here Insert Picture Description
it seems that only the direct implementation HttpMessageConverterExtractor, of course, it is most important to realize, and HttpMessageConverterrelated.
Before explaining it, to look at this: MessageBodyClientHttpResponseWrapperit features: It can not only by actually reading the input stream to check whether a response message body, can also check the length is zero (i.e., empty)

// @since 4.1.5  它是一个访问权限是default的类,是对其它ClientHttpResponse的一个包装
class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
    private final ClientHttpResponse response;
    // java.io.PushbackInputStream
    @Nullable
    private PushbackInputStream pushbackInputStream;
    
    // 判断相应里是否有body体
    // 若响应码是1xx 或者是204;或者getHeaders().getContentLength() == 0 那就返回false  否则返回true
    public boolean hasMessageBody() throws IOException {
        HttpStatus status = HttpStatus.resolve(getRawStatusCode());
        if (status != null && (status.is1xxInformational() || status == HttpStatus.NO_CONTENT || status == HttpStatus.NOT_MODIFIED)) {
            return false;
        }
        if (getHeaders().getContentLength() == 0) {
            return false;
        }
        return true;
    }

    // 上面是完全格局状态码(ContentLength)来判断是否有body体的~~~这里会根据流来判断
    // 如果response.getBody() == null,返回true
    // 若流里有内容,最终就用new PushbackInputStream(body)包装起来~~~
    public boolean hasEmptyMessageBody() throws IOException {
        ...
    }
    
    ...  // 其余接口方法都委托~
    @Override
    public InputStream getBody() throws IOException {
        return (this.pushbackInputStream != null ? this.pushbackInputStream : this.response.getBody());
    }
}

Its function is packaged, two methods hasMessageBody、hasEmptyMessageBodyfacilitate the determination of the content body member

// @since 3.0 泛型T:the data type
public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
    // java.lang.reflect.Type
    private final Type responseType;
    // 这个泛型也是T,表示数据的Class嘛~
    // 该calss有可能就是上面的responseType
    @Nullable
    private final Class<T> responseClass;
    // 重要:用于消息解析的转换器
    private final List<HttpMessageConverter<?>> messageConverters;
    ... // 省略构造函数


    // 从ClientHttpResponse 里提取值
    @Override
    @SuppressWarnings({"unchecked", "rawtypes", "resource"})
    public T extractData(ClientHttpResponse response) throws IOException {
        MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
        // 若没有消息体(状态码不对 或者 消息体为空都被认为是木有)
        if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
            return null;
        }
    
        // content-type若响应头header里没有指定,那默认是它MediaType.APPLICATION_OCTET_STREAM
        MediaType contentType = getContentType(responseWrapper);
        
        // 遍历所有的messageConverters,根据contentType 来选则一个消息转换器
        // 最终return messageConverter.read((Class) this.responseClass, responseWrapper)
        ...
    }
}

It is appreciated that the processing logic is very simple: by using contentTypefind a message converter, eventually HttpMessageConverter.read()the message read out is converted into a Java object.

It also implements two inner classes are as follows (all RestTemplateprivate inner class):

RestTemplate:

    // 提取为`ResponseEntity`  最终委托给HttpMessageConverterExtractor完成的
    private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {

        @Nullable
        private final HttpMessageConverterExtractor<T> delegate;

        public ResponseEntityResponseExtractor(@Nullable Type responseType) {
            // 显然:只有请求的返回值不为null 才有意义~
            if (responseType != null && Void.class != responseType) {
                this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
            } else {
                this.delegate = null;
            }
        }

        // 数据提取。都是交给`delegate.extractData(response)`做了,然后new一个ResponseEntity出来包装进去
        // 若木有返回值(delegate=null),那就是一个`ResponseEntity`实例,body为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();
            }
        }
    }

    // 提取请求头
    private static class HeadersExtractor implements ResponseExtractor<HttpHeaders> {
        @Override
        public HttpHeaders extractData(ClientHttpResponse response) {
            return response.getHeaders();
        }
    }

UriTemplateHandler

It is for this component method definition with variable expansion uri template .

// @since 4.2 出现较晚  
// @see RestTemplate#setUriTemplateHandler(UriTemplateHandler)
public interface UriTemplateHandler {
    URI expand(String uriTemplate, Map<String, ?> uriVariables);
    URI expand(String uriTemplate, Object... uriVariables);
}

On handling of URI, ultimately entrusted to UriComponentsBuilderbe done. If there is still a doubt for this piece of strong strong strong reference here

Recommended Reading

RestTemplate use and principles you are overripe in the chest yet? [Enjoy learning Spring MVC]

to sum up

This article describes the components is to understand the RestTemplateessential components who belongs to the mountains papers. Because RestTemplatethe use of frequent and often require tuning, so I hope that we can send it to do a more in-depth understanding, and this is my purpose in writing this series, encourage each other.

== If for Spring, SpringBoot, MyBatis source code analysis and other interested can add me wx: fsx641385712, manually invite you to take off into a group ==
== If for Spring, SpringBoot, MyBatis source code analysis and other interested can add me wx : fsx641385712, manually invite you into the group took off ==

Guess you like

Origin www.cnblogs.com/fangshixiang/p/11532777.html