• 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
  • 相关阅读:
    python中os.listdir( )函数读取文件夹
    斐讯k2p 月光银 硬件版本A2-软件版本22.7.8.5 刷官改系统
    Ubuntu 16.04 换国内源
    pip和apt-get换源
    Python-OpenCV中的filter2D()函数
    Python-OpenCV中图像合并显示
    Python-OpenCV——Image Blurring(Image Smoothing)
    Python-OpenCV中的resize()函数
    删除Chrome地址栏记录中自动补全的网址
    Python中Numpy mat的使用
  • 原文地址:https://www.cnblogs.com/vincentren/p/13688100.html
Copyright © 2020-2023  润新知