zoukankan      html  css  js  c++  java
  • 002-02-RestTemplate-初始化调用流程

    一、简述

      调用 RestTemplate 的默认构造函数,RestTemplate 对象在底层通过使用 java.net 包下的实现创建 HTTP 请求,可以通过使用 ClientHttpRequestFactory 指定不同的HTTP请求方式。

      使用的是spring5.0.1

      默认使用 SimpleClientHttpRequestFactory,是 ClientHttpRequestFactory 实现类。如下流程:

    1)使用默认构造方法new一个实例

      RestTemplate template = new RestTemplate();

    2)RestTemplate 内部通过调用 doExecute 方法,首先就是获取 ClientHttpRequest

    doExcute核心处理

    //通过ClientHttpRequestFactory工厂生产一个ClientHttpRequest 
    ClientHttpRequest request = this.createRequest(url, method);
    if(requestCallback != null) {
        //封装了请求头和请求体,使用了HttpMessageConverter
        requestCallback.doWithRequest(request);
    }
    //ClientHttpRequest&execute 执行请求
    response = request.execute();
    //response error处理
    this.handleResponse(url, method, response);
    if(responseExtractor == null) {
        resource = null;
        return resource;
    }
    
    var14 = responseExtractor.extractData(response);

    源代码

        @Nullable
        protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
            Assert.notNull(url, "'url' must not be null");
            Assert.notNull(method, "'method' must not be null");
            ClientHttpResponse response = null;
    
            Object var14;
            try {
                String resource;
                try {
                    ClientHttpRequest request = this.createRequest(url, method);
                    if(requestCallback != null) {
                        requestCallback.doWithRequest(request);
                    }
    
                    response = request.execute();
                    this.handleResponse(url, method, response);
                    if(responseExtractor == null) {
                        resource = null;
                        return resource;
                    }
    
                    var14 = responseExtractor.extractData(response);
                } catch (IOException var12) {
                    resource = url.toString();
                    String query = url.getRawQuery();
                    resource = query != null?resource.substring(0, resource.indexOf(63)):resource;
                    throw new ResourceAccessException("I/O error on " + method.name() + " request for "" + resource + "": " + var12.getMessage(), var12);
                }
            } finally {
                if(response != null) {
                    response.close();
                }
    
            }
    
            return var14;
        }
    View Code

      

      此处方法会调用第三步中HttpAccessor中的ClientHttpRequest createRequest方法。

    3)RestTemplate 实现了抽象类 HttpAccessor ,所以可以调用父类的 createRequest

    在HttpAccessor中

       private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        public ClientHttpRequestFactory getRequestFactory() {
            return this.requestFactory;
        }

      这里也提供了自定义的ClientHttpRequestFactory 请求工厂的方法,后续会使用到

        public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
            Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
            this.requestFactory = requestFactory;
        }
        protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
            ClientHttpRequest request = this.getRequestFactory().createRequest(url, method);
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Created " + method.name() + " request for "" + url + """);
            }
    
            return request;
        }

    4)SimpleClientHttpRequestFactory 实现了 ClientHttpRequest,同时实现方法

      注意 bufferRequestBody 是可以在 RestTemplate 设置,是标志是否使用缓存流的形式,默认是 true,缺点是当发送大量数据时,比如put/post的保存和修改,那么可能内存消耗严重。所以这时候可以设置 RestTemplate.setBufferRequestBody(false);

      即使用 SimpleStreamingClientHttpRequest 来实现。

        public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
            HttpURLConnection connection = this.openConnection(uri.toURL(), this.proxy);
            this.prepareConnection(connection, httpMethod.name());
            return (ClientHttpRequest)(this.bufferRequestBody?new SimpleBufferingClientHttpRequest(connection, this.outputStreaming):new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming));
        }

      第一行:创建java.net.HttpURLConnection 

      第二行:创建connection属性,同时设置了setDoInput

    5)openConnection 即打开连接,而是 prepareConnection 各种连接准备,针对请求者

        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);
            }
    
            connection.setDoInput(true);
            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(false);
            } else {
                connection.setDoOutput(true);
            }
    
            connection.setRequestMethod(httpMethod);
        }

    5.1、设置连接超时时间:connectTimeout

    5.2、设置读超时时间:readTimeout

    5.3、设置URL允许输入:connection.setDoInput(true);//默认true

      URL连接可用于输入和/或输出。 如果您打算使用URL连接进行输入,请将DoInput标志设置为true;否则,设置为false。 默认值是true。

      httpUrlConnection.setDoInput(true);以后就可以使用conn.getInputStream().read();

    5.4、setDoOutput

      URL连接可用于输入和/或输出。 如果您打算将URL连接用于输出,请将DoOutput标志设置为true,如果不是,则为false 默认值是false。

      如果是get请求

        get请求用不到conn.getOutputStream(),因为参数直接追加在地址后面,因此默认是false。 

      如果是post、put、patch、delete

        以上几种请求会将setDoOutput设置为true。

        相当于可以使用:httpUrlConnection.setDoOutput(true);以后就可以使用conn.getOutputStream().write()

    6、执行requestCallback.doWithRequest(request);

      RequestCallback 封装了请求体和请求头对象,也就是说在该对象里面可以拿到我们需要的请求参数。

      在执行 doWithRequest 时,有一个非常重要的步骤,他和前面Connection发送请求体有着密切关系,我们知道请求头就是 SimpleBufferingClientHttpRequest.addHeaders 方法,那么请求体 bufferedOutput 是如何赋值的呢?就是在 doWithRequest 里面,如下 StringHttpMessageConverter (其他 MessageConvert 也一样,这里也是经常乱码的原因)

      由上文8.1、可知 RequestCallback 用于操作请求头和body,在请求发出前执行。以下是RequestCallback的 两个实现类

    AcceptHeaderRequestCallback 只处理请求头,用于getXXX()方法。
    HttpEntityRequestCallback 继承于AcceptHeaderRequestCallback可以处理请求头和body,用于putXXX()、postXXX()和exchange()方法。

      其中HttpEntityRequestCallback有继承:HttpEntityRequestCallback extends RestTemplate.AcceptHeaderRequestCallback

      故先查看下AcceptHeaderRequestCallback代码

            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();
                    Iterator var4 = RestTemplate.this.getMessageConverters().iterator();//第一步
    
                    while(var4.hasNext()) {
                        HttpMessageConverter<?> converter = (HttpMessageConverter)var4.next();
                        if(responseClass != null) {
                            if(converter.canRead(responseClass, (MediaType)null)) {
                                allSupportedMediaTypes.addAll(this.getSupportedMediaTypes(converter));
                            }
                        } else if(converter instanceof GenericHttpMessageConverter) {
                            GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter)converter;
                            if(genericConverter.canRead(this.responseType, (Class)null, (MediaType)null)) {
                                allSupportedMediaTypes.addAll(this.getSupportedMediaTypes(converter));
                            }
                        }
                    }
    
                    if(!allSupportedMediaTypes.isEmpty()) {
                        MediaType.sortBySpecificity(allSupportedMediaTypes);
                        if(RestTemplate.this.logger.isDebugEnabled()) {
                            RestTemplate.this.logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
                        }
                        request.getHeaders().setAccept(allSupportedMediaTypes);
                    }
                }
    
            }

      注释第一步:获取RestTemplate所有绑定的MessageConverters。

        有上文  RestTemplate初始化时绑定了基本的五种,以及其他更多,初始化代码

        public RestTemplate() {
            this.messageConverters = new ArrayList();
            this.errorHandler = new DefaultResponseErrorHandler();
            this.uriTemplateHandler = new DefaultUriBuilderFactory();
            this.headersExtractor = new RestTemplate.HeadersExtractor();
            this.messageConverters.add(new ByteArrayHttpMessageConverter());
            this.messageConverters.add(new StringHttpMessageConverter());
            this.messageConverters.add(new ResourceHttpMessageConverter(false));
            this.messageConverters.add(new SourceHttpMessageConverter());
            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());
            } else if(jsonbPresent) {
                this.messageConverters.add(new JsonbHttpMessageConverter());
            }
    
            if(jackson2SmilePresent) {
                this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
            }
    
            if(jackson2CborPresent) {
                this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
            }
    
        }
    View Code

      此时可以分析一下StringHttpMessageConverter 代码    

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

      其中 str 就是请求体;HttpOutputMessage 对象就是我们准备的 ClientHttpRequest 对象,也就是上面的 SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest

      这样,先调用父类的流方法,把内容写入流中,然后调用父类的 executeInternal方法在调用自身的该方法 executeInternal ,如下一步

      注意点1:charset 设置字符集,可以来自客户端;

        public static final Charset DEFAULT_CHARSET;//静态代码块,即设置为ISO_8859_1,但是网络传输一般是UTF-8,故大部分情况需要设置编码

        static {
            DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
        }

      注意点2:StreamUtils是spring中用于处理流的类,是java.io包中inputStream和outputStream,不是java8中Steam。使用时仅依赖spring-core

        copy方法带三个参数:被拷贝的字符串,写文件时指定的字符集,指定目的地(outputStream)。

        更多参看:https://blog.csdn.net/neweastsun/article/details/79432763

    7)接着执行 response = request.execute();

    调用接口ClientHttpRequest 

    public interface ClientHttpRequest extends HttpRequest, HttpOutputMessage {
        ClientHttpResponse execute() throws IOException;
    }

    然后使用实例 SimpleBufferingClientHttpRequest 封装请求体和请求头

    查看executeInternal

        protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
            addHeaders(this.connection, headers);
            if(HttpMethod.DELETE == this.getMethod() && bufferedOutput.length == 0) {
                this.connection.setDoOutput(false);
            }
    
            if(this.connection.getDoOutput() && this.outputStreaming) {
                this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
            }
    
            this.connection.connect();
            if(this.connection.getDoOutput()) {
                FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
            } else {
                this.connection.getResponseCode();
            }
    
            return new SimpleClientHttpResponse(this.connection);
        }

    delete 时通过请求方式和是否有请求体对象来判断是否需要发送请求体

    如果是delete请求,首先设置 DoOutput = true,然后根据是否有请求体数据,然后封装请求体

    FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());

    8)解析response

    接着就是 response 的解析了,主要还是 Error 的解析。

      this.handleResponse(url, method, response);

    内部处理多是解析error处理

        protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
            ResponseErrorHandler errorHandler = this.getErrorHandler();
            boolean hasError = errorHandler.hasError(response);
            if(this.logger.isDebugEnabled()) {
                try {
                    this.logger.debug(method.name() + " request for "" + url + "" resulted in " + response.getRawStatusCode() + " (" + response.getStatusText() + ")" + (hasError?"; invoking error handler":""));
                } catch (IOException var7) {
                    ;
                }
            }
    
            if(hasError) {
                errorHandler.handleError(url, method, response);
            }
    
        }
    View Code

    参看地址:

    https://my.oschina.net/heweipo/blog/801653

  • 相关阅读:
    常用JVM配置参数
    JVM运行机制
    go 奇技淫巧
    如何实现LRU(最近最少使用)缓存淘汰算法?
    数组下标为什么是0而不是1?
    ServiceMesh 演化进程
    CAP定理详解
    vscode 调试配置信息
    Ubuntu 断网问题解决
    ubuntu 关闭指定占用端口
  • 原文地址:https://www.cnblogs.com/bjlhx/p/8652763.html
Copyright © 2011-2022 走看看