RestTemplate源码解析

前言

RestTemplate进行http相关的请求的最底层的实现是利用的java原生的api java.net.URLConnection等实现的,关于如何实现的可以看上一篇文章中的demo。了解了java原生的http请求对解析RestTemplate源码很有帮助。

 

核心doExecute方法

无论用RestTemplate进行get、post、delete、put等http请求他都要走各种execute重载方法,而任何execute方法都会走doExecute这个方法,看下他的源码

    /**
     * Execute the given method on the provided URI.
     * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
     * the response with the {@link ResponseExtractor}.
     * @param url the fully-expanded URL to connect to
     * @param method the HTTP method to execute (GET, POST, etc.)
     * @param requestCallback object that prepares the request (can be {@code null})
     * @param responseExtractor object that extracts the return value from the response (can be {@code null})
     * @return an arbitrary object, as returned by the {@link ResponseExtractor}
     */
    protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
            ResponseExtractor<T> responseExtractor) throws RestClientException {

        Assert.notNull(url, "'url' must not be null");
        Assert.notNull(method, "'method' must not be null");
        ClientHttpResponse response = null;
        try {
            ClientHttpRequest request = createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWithRequest(request);
            }
            response = request.execute();
            handleResponse(url, method, response);
            if (responseExtractor != null) {
                return responseExtractor.extractData(response);
            }
            else {
                return 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();
            }
        }
    }
View Code

分别解释下各个参数的意义:

URI url:请求的http地址,会利用url.openConnection()来链接。

HttpMethod method:这个就比较简单了,代表http请求的方法,GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE。

RequestCallback requestCallback:这个会在进行http请求前处理好http请求的head,如果是post这种带body的请求也会做好body的处理。常见的中文乱码的问题就是在这发生的,当然,涉及到的类不止他一个。

ResponseExtractor<T> responseExtractor:这个是用来处理http的response的,当我们直接通过log.error("http response error,{}",e)打印出来的日志不是我们想要的完整日志,就是这边捣的鬼。

因为doExecute方法实际也没几行,所以我们一行行分析;

(一)ClientHttpRequest request = createRequest(url, method);

    /**
     * Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}.
     * @param url the URL to connect to
     * @param method the HTTP method to execute (GET, POST, etc)
     * @return the created request
     * @throws IOException in case of I/O errors
     * @see #getRequestFactory()
     * @see ClientHttpRequestFactory#createRequest(URI, HttpMethod)
     */
    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;
    }
View Code

他的注释是这样的Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}。这个很好理解,通过ClientHttpRequestFactory获取了一个ClientHttpRequest的实例。

首先是

    public ClientHttpRequestFactory getRequestFactory() {
        return this.requestFactory;
    }

this.requestFactory就是取得全局变量 private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();这边可以看到默认的是SimpleClientHttpRequestFactory,那么如果我想换个ClientHttpRequestFactory呢,答案就是在创建RestTemplate实例时指定ClientHttpRequestFactory,当然这个是在配置时做。

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }
View Code

这边我们以SimpleClientHttpRequestFactory继续往下走。

    @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);
        }
    }
View Code

HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);这一步就是执行了url.openConnection(),

prepareConnection(connection, httpMethod.name());这一步设置了http请求的方法,同时判断请求的方法做一些java原生态api需要做的设置,具体如下:

        if ("GET".equals(httpMethod)) {
            connection.setInstanceFollowRedirects(true);
        }else {
            connection.setInstanceFollowRedirects(false);
        }
        if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
                "PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
            connection.setDoOutput(true);
        }else {
            connection.setDoOutput(false);
        }
View Code

最后是生成并返回了一个ClientHttpRequest实例,

        if (this.bufferRequestBody) {
            return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);//如果是默认的SimpleClientHttpRequestFactory,那么返回的就是这个了
        }
        else {
            return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
        }

 

(二)requestCallback.doWithRequest(request);

RequestCallback的实现类一般用到的就是定义在RestTemplate中的HttpEntityRequestCallback和AcceptHeaderRequestCallback,其中HttpEntityRequestCallback extends AcceptHeaderRequestCallback

 既然HttpEntityRequestCallback继承了AcceptHeaderRequestCallback,那么比AcceptHeaderRequestCallback多做了什么呢?答案就是:HttpEntityRequestCallback多做了关于http请求body的处理。

首先我们先看看AcceptHeaderRequestCallback的doWithRequest方法

        @Override
        public void doWithRequest(ClientHttpRequest request) throws IOException {
            if (this.responseType != null) {
                Class<?> responseClass = null;
                if (this.responseType instanceof Class) {
                    responseClass = (Class<?>) this.responseType;
                }
                List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
                for (HttpMessageConverter<?> converter : getMessageConverters()) {
                    if (responseClass != null) {
                        if (converter.canRead(responseClass, null)) {
                            allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));
                        }
                    }
                    else if (converter instanceof GenericHttpMessageConverter) {
                        GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
                        if (genericConverter.canRead(this.responseType, null, null)) {
                            allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));
                        }
                    }
                }
                if (!allSupportedMediaTypes.isEmpty()) {
                    MediaType.sortBySpecificity(allSupportedMediaTypes);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
                    }
                    request.getHeaders().setAccept(allSupportedMediaTypes);
                }
            }
        }
View Code

这里面有个for循环for (HttpMessageConverter<?> converter : getMessageConverters()),getMessageConverters()是这样子的

    public List<HttpMessageConverter<?>> getMessageConverters() {
        return this.messageConverters;
    }

this.messageConverters是这样子的

private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();

那么messageConverters里面的值是什么呢?答案是,RestTemplate实例化时初始化了messageConverters的值

    public RestTemplate() {
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
        this.messageConverters.add(new ResourceHttpMessageConverter());
        this.messageConverters.add(new SourceHttpMessageConverter<Source>());
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (romePresent) {
            this.messageConverters.add(new AtomFeedHttpMessageConverter());
            this.messageConverters.add(new RssChannelHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
        }
        else if (jaxb2Present) {
            this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            this.messageConverters.add(new MappingJackson2HttpMessageConverter());
        }
        else if (gsonPresent) {
            this.messageConverters.add(new GsonHttpMessageConverter());
        }
    }
View Code

那些if判断其实就是看项目中有没有引入这些jar

    private static boolean romePresent =
            ClassUtils.isPresent("com.rometools.rome.feed.WireFeed",
                    RestTemplate.class.getClassLoader());

    private static final boolean jaxb2Present =
            ClassUtils.isPresent("javax.xml.bind.Binder",
                    RestTemplate.class.getClassLoader());

    private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
                    RestTemplate.class.getClassLoader()) &&
            ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
                    RestTemplate.class.getClassLoader());

    private static final boolean jackson2XmlPresent =
            ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper",
                    RestTemplate.class.getClassLoader());

    private static final boolean gsonPresent =
            ClassUtils.isPresent("com.google.gson.Gson",
                    RestTemplate.class.getClassLoader());
View Code

 下面就是调用converter的canRead方法

canRead方法其实就是判断这个converter是否支持对这种类型进行解析。

然后拿出这些converter支持的MediaType

allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));

最后将这些MediaType设置到head中去

request.getHeaders().setAccept(allSupportedMediaTypes);

以上是适用于没有body的请求,如果有body的请求,会走HttpEntityRequestCallback,他继承自AcceptHeaderRequestCallback,多做了关于body的处理

        public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
            super.doWithRequest(httpRequest);
            if (!this.requestEntity.hasBody()) {
                HttpHeaders httpHeaders = httpRequest.getHeaders();
                HttpHeaders requestHeaders = this.requestEntity.getHeaders();
                if (!requestHeaders.isEmpty()) {
                    for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
                        httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue()));
                    }
                }
                if (httpHeaders.getContentLength() < 0) {
                    httpHeaders.setContentLength(0L);
                }
            }
            else {
                Object requestBody = this.requestEntity.getBody();
                Class<?> requestBodyClass = requestBody.getClass();
                Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
                        ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
                HttpHeaders httpHeaders = httpRequest.getHeaders();
                HttpHeaders requestHeaders = this.requestEntity.getHeaders();
                MediaType requestContentType = requestHeaders.getContentType();
                for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
                    if (messageConverter instanceof GenericHttpMessageConverter) {
                        GenericHttpMessageConverter<Object> genericMessageConverter = (GenericHttpMessageConverter<Object>) messageConverter;
                        if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
                            if (!requestHeaders.isEmpty()) {
                                for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
                                    httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue()));
                                }
                            }
                            if (logger.isDebugEnabled()) {
                                if (requestContentType != null) {
                                    logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
                                            "\" using [" + messageConverter + "]");
                                }
                                else {
                                    logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
                                }

                            }
                            genericMessageConverter.write(
                                    requestBody, requestBodyType, requestContentType, httpRequest);
                            return;
                        }
                    }
                    else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
                        if (!requestHeaders.isEmpty()) {
                            for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
                                httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue()));
                            }
                        }
                        if (logger.isDebugEnabled()) {
                            if (requestContentType != null) {
                                logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
                                        "\" using [" + messageConverter + "]");
                            }
                            else {
                                logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
                            }

                        }
                        ((HttpMessageConverter<Object>) messageConverter).write(
                                requestBody, requestContentType, httpRequest);
                        return;
                    }
                }
                String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
                        requestBodyClass.getName() + "]";
                if (requestContentType != null) {
                    message += " and content type [" + requestContentType + "]";
                }
                throw new RestClientException(message);
            }
        }
View Code

这边也是先循环converter,然后选出能处理的converter,然后写入到body

((HttpMessageConverter<Object>) messageConverter).write(
                                requestBody, requestContentType, httpRequest);

这边以StringHttpMessageConverter举例,调用writeInternal方法

    protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
        if (this.writeAcceptCharset) {
            outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
        }
        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
        StreamUtils.copy(str, charset, outputMessage.getBody());
    }

注意:乱码问题就是在这边产生的

 

Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());

 

首先会看你的请求是否有设置charset,charset的设置在自己编写restTemplate请求代码时做的如下代码就指定了编码格式为UTF-8

        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        HttpEntity request = new HttpEntity<>(jsonObject, headers);
        JSONObject response = restTemplate.postForObject("http://127.0.0.1:8089/getBodyTest", request, JSONObject.class);

如果MediaType没有指定charset,而是这样的

MediaType type = MediaType.parseMediaType("application/json");

那么将使用converter的默认编码,还是以StringHttpMessageConverter为例

    private Charset getContentTypeCharset(MediaType contentType) {
        if (contentType != null && contentType.getCharset() != null) {
            return contentType.getCharset();
        }
        else {
            return getDefaultCharset();
        }
    }

没有设置就会调用getDefaultCharset()

    public Charset getDefaultCharset() {
        return this.defaultCharset;
    }

this.defaultCharset是这样的

private Charset defaultCharset;

这边没有给默认值,说明在其他地方赋值的,那我们看下StringHttpMessageConverter的构造方法

    public StringHttpMessageConverter() {
        this(DEFAULT_CHARSET);
    }

    public StringHttpMessageConverter(Charset defaultCharset) {
        super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
    }

而DEFAULT_CHARSET的值如下

public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");

所以说,如果我们在通过restTemplate发起http请求,如果body是String格式的,那么他的默认编码是"ISO-8859-1",那么就会出现乱码的可能,解决的方法也很简单,就是自己设置下charset

(三)response = request.execute();

这边以SimpleBufferingClientHttpRequest举例

执行的核心代码是这个

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

主要是进行了this.connection.connect();然后将connection包装在了SimpleClientHttpResponse中并返回了

(四)handleResponse(url, method, response);

这一步就是对http的response做处理,这里有个知识点,就是开头提到的直接通过log.error("http response error,{}",e)打印出来的日志不是我们想要的完整日志

    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(response);
        }
    }
View Code

getErrorHandler是这样子的,

    public ResponseErrorHandler getErrorHandler() {
        return this.errorHandler;
    }

而this.errorHandler是这样子的

private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();

所以,如果我们没做任何设置,那么这里的ResponseErrorHandler就是DefaultResponseErrorHandler

对于DefaultResponseErrorHandler,他对hasError(response)的实现就是判断是否是400等4开头或者500等5开头的返回状态吗,如果是则把返回包装下,抛出一条异常

    public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = getHttpStatusCode(response);
        switch (statusCode.series()) {
            case CLIENT_ERROR:
                throw new HttpClientErrorException(statusCode, response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
            case SERVER_ERROR:
                throw new HttpServerErrorException(statusCode, response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
            default:
                throw new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(),
                        response.getHeaders(), getResponseBody(response), getCharset(response));
        }
    }
View Code

由于doExecute已经包了一层tray/catch,所以等我们拿到时就是这样的异常了

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

所以我们的打印日志中无法显示返回的body。

如果想打印出body,这个有个简单的方式,就是自己实现ResponseErrorHandler,如下

public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        return true;
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {

    }
}

然后在resttemplate初始化时将这个handler设置下就行了

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
        return restTemplate;
    }

这样子,返回的原始信息我们都可以拿到了。 

(五)responseExtractor.extractData(response);

这一步就是通过converter将response中的body转换成我们设置的类,大致代码和wirte差不多,只是这边是read

    public T extractData(ClientHttpResponse response) throws IOException {
        MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
        if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
            return null;
        }
        MediaType contentType = getContentType(responseWrapper);

        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
            if (messageConverter instanceof GenericHttpMessageConverter) {
                GenericHttpMessageConverter<?> genericMessageConverter =
                        (GenericHttpMessageConverter<?>) messageConverter;
                if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Reading [" + this.responseType + "] as \"" +
                                contentType + "\" using [" + messageConverter + "]");
                    }
                    return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
                }
            }
            if (this.responseClass != null) {
                if (messageConverter.canRead(this.responseClass, contentType)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Reading [" + this.responseClass.getName() + "] as \"" +
                                contentType + "\" using [" + messageConverter + "]");
                    }
                    return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
                }
            }
        }

        throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
                "for response type [" + this.responseType + "] and content type [" + contentType + "]");
    }
View Code

 

posted on 2020-09-17 22:28  幽人月  阅读(694)  评论(0编辑  收藏  举报