一、简述

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

  

  此处方法会调用第三步中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());
} }

  此时可以分析一下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);
} }

参看地址:

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

002-02-RestTemplate-初始化调用流程的更多相关文章

  1. Android 12(S) 图像显示系统 - HWC HAL 初始化与调用流程

    必读: Android 12(S) 图像显示系统 - 开篇 接口定义 源码位置:/hardware/interfaces/graphics/composer/ 在源码目录下可以看到4个版本的HIDL ...

  2. android从应用到驱动之—camera(1)---程序调用流程

    一.开篇 写博客还得写开篇介绍,可惜,这个不是我所擅长的.就按我自己的想法写吧. 话说camera模块,从上层到底层一共包含着这么几个部分: 1.apk------java语言 2.camera的ja ...

  3. android从应用到驱动之—camera(1)---程序调用流程[转]

    一.开篇 写博客还得写开篇介绍,可惜,这个不是我所擅长的.就按我自己的想法写吧. 话说camera模块,从上层到底层一共包含着这么几个部分: 1.apk------java语言 2.camera的ja ...

  4. 【转】 Android的NDK开发(1)————Android JNI简介与调用流程

    原文网址:http://blog.csdn.net/conowen/article/details/7521340 ****************************************** ...

  5. InnoDB主要数据结构及调用流程

    InnoDB主要数据结构及调用流程 InnoDB是MySQL中常用的数据引擎.本文将从源码级别对InnoDB重点数据结构和调用流程进行分析. 主要数据结构(buf0buf.h) Buf_pool Bu ...

  6. Samsung_tiny4412(驱动笔记03)----字符设备驱动基本操作及调用流程

    /*********************************************************************************** * * 字符设备驱动基本操作及 ...

  7. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  8. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

  9. (转)android从应用到驱动之—camera(1)---程序调用流程

    一.开篇 写博客还得写开篇介绍,可惜,这个不是我所擅长的.就按我自己的想法写吧. 话说camera模块,从上层到底层一共包含着这么几个部分: 1.apk------java语言 2.camera的ja ...

随机推荐

  1. Song Form

    First of all, song form is an indepentent concept from the boxes, boxes simply describe the way the ...

  2. windows RabbitMQ Server 环境配置中的一些坑

    原文:https://blog.csdn.net/lindonglian/article/details/55805637 RabbitMQ的服务端基于Erlang语言编写,要在机器上安装Rabbit ...

  3. 瞎扯KMP

    瞎扯\(KMP\) 众所周知,\(KMP\)是一种玄学的字符串模式匹配算法. 什么是字符串模式匹配? 通俗的讲,就是统计一个字符串(通常很长)中某个子串(即一段连续的字符)出现的次数或位置.一般来说, ...

  4. m_strcpy

    自己实现strcpy函数 #include <stdio.h> #include <assert.h> //如果它的条件返回错误,则终止程序执行 char *m_strcpy( ...

  5. vue 自定义事件

  6. VS2010 insert Oracle数据库

    背景:批量插入上万条数据到Oracle数据库的一张表里. 工具:VS2010. 因为是访问远程数据库,所以需要先装一个oracle client. 使用oracle客户端的方式访问数据库,需要添加对其 ...

  7. 系统空闲时间 解决 GetLastInputInfo 负数问题

    using System;using System.Collections.Generic;using System.Linq;using System.Runtime.InteropServices ...

  8. bash: sz: command not found

    Linux系统中如果没有安装 lrzsz这个包,就会报rz.sz命令找不到,安装即可解决. 命令: yum install lrzsz 效果图:

  9. spark读文件写mysql(java版)

    package org.langtong.sparkdemo; import com.fasterxml.jackson.databind.ObjectMapper; import org.apach ...

  10. springboot使用rabbitmq-Topic模式,亲自实测能用!!!

    0.项目目录截图 ===================================================================== springboot的版本: <gr ...