一、简述

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



源代码




java resttemplate 例子 resttemplate教程_请求头

java resttemplate 例子 resttemplate教程_java_02

@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

  

java resttemplate 例子 resttemplate教程_HTTP_03

  此处方法会调用第三步中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。

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




java resttemplate 例子 resttemplate教程_请求头

java resttemplate 例子 resttemplate教程_java_02

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

    更多参看:

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处理




java resttemplate 例子 resttemplate教程_请求头

java resttemplate 例子 resttemplate教程_java_02

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