一、简述

  调用 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. Python 包文件安装

    https://pypi.tuna.tsinghua.edu.cn/simple/ 清华源 https://pypi.doubanio.com/simple/ 豆瓣源 pip install -i h ...

  2. ant不是内部命令

    解压路径为举例路径:    解压在E盘 新建变量ANT_HOME 路径为解压目录如E:/apache-ant-1.7.1 Path中添加路径为%ANT_HOME%/bin; 错误提示: 'ant' 不 ...

  3. Educational Codeforces Round 41 967 E. Tufurama (CDQ分治 求 二维点数)

    Educational Codeforces Round 41 (Rated for Div. 2) E. Tufurama (CDQ分治 求 二维点数) time limit per test 2 ...

  4. HttpReader

    头文件: #pragma once #include <afxinet.h> class CSF_HttpDataReader { public: CSF_HttpDataReader(v ...

  5. jquery 使用off移除事件 使用one绑定一次事件,on绑定事件后触发多次相同的事件的异常

    <!-- jquery 移除事件,绑定一次事件,搜狗 one --> <!DOCTYPE html> <html lang="en"> < ...

  6. 学到了林海峰,武沛齐讲的Day24-完 对象和实例

    学到这里估计就是坎了...日志都不想写了.. 对象和实例

  7. Linux swap的创建与配置

    在Linux下,swap的作用类似Windows系统下的“虚拟内存”.当物理内存不足时,拿出部分硬盘空间当SWAP分区(虚拟成内存)使用,从而解决内存容量不足的情况.Linux下的swap有两种实现形 ...

  8. 扩展kmp学习笔记

    kmp没写过,扩展kmp没学过可还行. 两个愿望,一次满足 (该博客仅用于防止自己忘记,不保证初学者能看懂我在瞎bb什么qwq) 用途 对于串\(s1,s2\),可以求出\(s2\)与\(s1\)的每 ...

  9. xshel链接linuxl安装nginx

    原文链接:https://blog.csdn.net/Sweet__dream/article/details/78256952?utm_source=blogxgwz9 这个连接更详细:https: ...

  10. dbcp_c3p0连接mysql8.0.13

    背景 学习数据库的使用,上次没有记录,现在都回忆不起来了,所以这次重新学的时候顺便记录下. 配置环境 win10 jdk11 idea mysql8.0.13 DBCP连接使用 用配置文件目前我连接不 ...