RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】
每篇一句
做事的人和做梦的人最大的区别就是行动力
前言
本文为深入了解spring
提供的rest调用客户端resttemplate
开山,对它相关的一些组件做讲解。
tips:请注意区分
resttemplate
和redistemplate
哦~
clienthttprequestfactory
它是个函数式接口,用于根据uri
和httpmethod
创建出一个clienthttprequest
来发送请求~
clienthttprequest
它代表请求的客户端,该接口继承自httprequest
、httpoutputmessage
,只有一个clienthttpresponse execute() throws ioexception
方法。其中netty、httpcomponents、okhttp3,httpurlconnection
对它都有实现~
// @since 3.0 resttemplate这个体系都是3.0后才有的 @functionalinterface public interface clienthttprequestfactory { // 返回一个clienthttprequest,这样调用其execute()方法就可以发送rest请求了~ clienthttprequest createrequest(uri uri, httpmethod httpmethod) throws ioexception; }
它的继承树如下:
可以直观的看到,我们可以使用apache
的httpclient
、okhttp3
、netty4
都可,但这些都需要额外导包,默认情况下spring
使用的是java.net.httpurlconnection
。
httpclient最新版本:4.5.10
okhttp最新版本:4.1.1(虽然版本号是4,但是gav还是3哦:com.squareup.okhttp3)
netty最新版本:4.1.39.final(它的5版本可以宣告已死)
spring4.0
是新增了一个对异步支持的asyncclienthttprequestfactory
(spring5.0后标记为已废弃):
// 在spring5.0后被标记为过时了,被org.springframework.http.client.reactive.clienthttpconnector所取代(但还是可用的嘛) @deprecated public interface asyncclienthttprequestfactory { // asyncclienthttprequest#executeasync()返回的是listenablefuture<clienthttpresponse> // 可见它的异步是通过listenablefuture实现的 asyncclienthttprequest createasyncrequest(uri uri, httpmethod httpmethod) throws ioexception; }
使用工厂创建clienthttprequest
,然后我们发请求就不用关心具体httpclient内部的细节了(可插拔使用二方库、三方库)
simpleclienthttprequestfactory
它是spring
内置默认的实现,使用的是jdk内置的java.net.urlconnection
作为client客户端。
public class simpleclienthttprequestfactory implements clienthttprequestfactory, asyncclienthttprequestfactory { private static final int default_chunk_size = 4096; @nullable private proxy proxy; //java.net.proxy private boolean bufferrequestbody = true; // 默认会缓冲body // urlconnection's connect timeout (in milliseconds). // 若值设置为0,表示永不超时 @see urlconnection#setconnecttimeout(int) private int connecttimeout = -1; // urlconnection#setreadtimeout(int) // 超时规则同上 private int readtimeout = -1; //set if the underlying urlconnection can be set to 'output streaming' mode. private boolean outputstreaming = true; // 异步的时候需要 @nullable private asynclistenabletaskexecutor taskexecutor; ... // 省略所有的set方法 @override public clienthttprequest createrequest(uri uri, httpmethod httpmethod) throws ioexception { // 打开一个httpurlconnection httpurlconnection connection = openconnection(uri.tourl(), this.proxy); // 设置超时时间、请求方法等一些参数到connection prepareconnection(connection, httpmethod.name()); //simplebufferingclienthttprequest的excute方法最终使用的是connection.connect(); // 然后从connection中得到响应码、响应体~~~ if (this.bufferrequestbody) { return new simplebufferingclienthttprequest(connection, this.outputstreaming); } else { return new simplestreamingclienthttprequest(connection, this.chunksize, this.outputstreaming); } } // createasyncrequest()方法略,无非就是在线程池里异步完成请求 ... }
需要注意的是:jdk <1.8 doesn't support getoutputstream with http delete
,也就是说如果jdk
的版本低于1.8的话,那么delete请求是不支持body体的。
demo show:
public static void main(string[] args) throws ioexception { simpleclienthttprequestfactory clientfactory = new simpleclienthttprequestfactory(); // connecttimeout只有在网络正常的情况下才有效,因此两个一般都设置 clientfactory.setconnecttimeout(5000); //建立连接的超时时间 5秒 clientfactory.setreadtimeout(5000); // 传递数据的超时时间(在网络抖动的情况下,这个参数很有用) clienthttprequest client = clientfactory.createrequest(uri.create("https://www.baidu.com"), httpmethod.get); // 发送请求 clienthttpresponse response = client.execute(); system.out.println(response.getstatuscode()); //200 ok system.out.println(response.getstatustext()); // ok system.out.println(response.getheaders()); // // 返回内容 是个inputstream byte[] bytes = filecopyutils.copytobytearray(response.getbody()); system.out.println(new string(bytes, standardcharsets.utf_8)); // 百度首页内容的html }
关于httpurlconnection
的api使用,需注意如下几点:
-
httpurlconnection
对象不能直接构造,需要通过url类中的openconnection()
方法来获得 -
httpurlconnection
的connect()函数,实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。http请求实际上直到我们获取服务器响应数据(如调用getinputstream()、getresponsecode()等方法)时才正式发送出去
1. 配置信息都需要在connect()方法执行之前完成 -
httpurlconnection
是基于http协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。==请务必100%设置== - http正文的内容是通过outputstream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成http正文
- 调用getinputstream()方法时,返回一个输入流,用于从中读取服务器对于http请求的返回信息。
-
httpurlconnection.connect()
不是必须的。当我们需要返回值时,比如我们使用httpurlconnection.getinputstream()方法的时候它就会自动发送请求了,所以完全没有必要调用connect()方法了(没必要先建立tcp嘛~)。
使用哪一个底层http库?
我们知道httpurlconnection
它在功能上是有些不足的(简单的提交参数可以满足)。绝大部分情况下web站点的网页可能没这么简单,这些页面并不是通过一个简单的url就可访问的,可能需要用户登录而且具有相应的权限才可访问该页面。在这种情况下,就需要涉及session、cookie的处理了,如果打算使用httpurlconnection来处理这些细节,当然也是可能实现的,只是处理起来难度就大了。
这个时候,apache开源组织提供了一个httpclient
项目,可以用于发送http请求,接收http响应(包含httpget、httppost...等各种发送请求的对象)。
它不会缓存服务器的响应,不能执行html页面中嵌入的javascript代码;也不会对页面内容进行任何解析、处理
因此,下面我就让spring使用httpclient为示例演示使用三方库:
1、导包
<dependency> <groupid>org.apache.httpcomponents</groupid> <artifactid>httpclient</artifactid> <version>4.5.10</version> </dependency>
tips:requires apache httpcomponents
4.3
or higher, as of spring 4.0.
2、案例使用
案例内容仅仅只需
把上例第一句话换成使用httpcomponentsclienthttprequestfactory
它的实例,其余都不用变化即可成功看到效果。可以看看这个类它具体做了什么
// @since 3.1 3.1后出现的。 public class httpcomponentsclienthttprequestfactory implements clienthttprequestfactory, disposablebean { private httpclient httpclient; @nullable private requestconfig requestconfig; // 这个配置就是可以配置超时等等乱七八糟client属性的类 private boolean bufferrequestbody = true; //=========下面是构造函数们========= public httpcomponentsclienthttprequestfactory() { // httpclientbuilder.create().usesystemproperties().build(); // 所有若是这里,配置超时时间可以这么来设置也可: // system.setproperty(”sun.net.client.defaultconnecttimeout”, “5000″); this.httpclient = httpclients.createsystem(); } // 当然可以把你配置好了的client扔进来 public httpcomponentsclienthttprequestfactory(httpclient httpclient) { this.httpclient = httpclient; } ... // 省略设置超时时间。。。等等属性的一些get/set // 超时信息啥的都是保存在`requestconfig`里的 @override public clienthttprequest createrequest(uri uri, httpmethod httpmethod) throws ioexception { httpclient client = gethttpclient(); // 拿到你指定的client)=(或者系统缺省的) // switch语句逻辑:httpmethod == get --> httpget head --> httphead ... httpurirequest httprequest = createhttpurirequest(httpmethod, uri); postprocesshttprequest(httprequest); ... } }
实际使用的是httpclient
完成的请求。另外okhttp3clienthttprequestfactory
使用的是okhttp3.okhttpclient
发送请求;netty4clienthttprequestfactory
使用的是io.netty.channel.eventloopgroup
。此处就不一一例举了
spring5.0以后,
netty4clienthttprequestfactory
过期了,建议使用org.springframework.http.client.reactive.reactorclienthttpconnector
代替~
关于httpurlconnection
、httpclient
、okhttpclient
的简单比较:
-
httpurlconnection
:
- 优点:jdk内置支持,java的标准类
- 缺点:api不够友好,什么都没封装,用起来太原始,不方便(这其实有时候也算优点,原始就证明好控~) -
httpclient
:
- 优点:功能强大,api友好,使用率够高,几乎成为了实际意义上的标准(相当于对httpurlconnection
的封装)
- 缺点:性能稍低(比httpurlconnection
低,但4.3后使用连接池进行了改善),api较臃肿,其实android已经弃用了它~ -
okhttpclient
:新一代的http访问客户端
- 优点:一个专注于性能和易用性的http客户端(节约宽带,android推荐使用),它设计的首要目标就是高效。提供了最新的 http 协议版本 http/2 和 spdy 的支持。如果 http/2 和 spdy 不可用,okhttp 会使用连接池来复用连接以提高效率
- 暂无。
关于apache httpclient
,android
5.0之后已经废弃使用它了(api太多,太重),推荐使用更轻量的httpurlconnection
。(java开发还是推荐用httpclient
)
okhttp
优点较多:支持spdy,可以合并多个到同一个主机的请求;okhttp实现的诸多技术如:连接池,gziping,缓存等;okhttp 处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个ip地址,当第一个ip连接失败的时候,okhttp会自动尝试下一个ip;okhttp是一个java的http+spdy客户端开发包,同时也支持android。默认情况下,okhttp会自动处理常见的网络问题,像二次连接、ssl的握手问题。支持文件上传、下载、cookie、session、https证书等几乎所有功能。支持取消某个请求
综上所述,不管是java还是android,我推荐的自然都是okhttp
(okhttp使用okio进行数据传输。都是square公司自家的,square公司还出了一个retrofit库配合okhttp战斗力翻倍)~~~
池化技术一般用于长连接,那么像http这种适合连接池吗?
httpclient 4.3以后中使用了poolinghttpclientconnectionmanager
连接池来管理持有连接,同一条tcp链路上,连接是可以复用的。httpclient通过连接池的方式进行连接持久化(所以它这个连接池其实是tcp的连接池。它里面有一个很重要的概念:route
的概念,代表一条线路。比如baidu.com是一个route,163.com是一个route...)。
连接池:可能是http请求,也可能是https请求
加入池话技术,就不用每次发起请求都新建一个连接(每次连接握手三次,效率太低)
abstractclienthttprequestfactorywrapper
对其它clienthttprequestfactory
的一个包装抽象类,它有如下两个子类实现
interceptingclienthttprequestfactory
(重要)
interceptor
拦截的概念,还是蛮重要的。它持有的clienthttprequestinterceptor
对于我们若想要拦截发出去的请求非常之重要(比如全链路压测中,可以使用它设置token之类的~)
// @since 3.1 public class interceptingclienthttprequestfactory extends abstractclienthttprequestfactorywrapper { // 持有所有的请求拦截器 private final list<clienthttprequestinterceptor> interceptors; public interceptingclienthttprequestfactory(clienthttprequestfactory requestfactory, @nullable list<clienthttprequestinterceptor> interceptors) { super(requestfactory); // 拦截器只允许通过构造函数设置进来,并且并没有提供get方法方法~ this.interceptors = (interceptors != null ? interceptors : collections.emptylist()); } // 此处返回的是一个interceptingclienthttprequest,显然它肯定是个clienthttprequest嘛~ @override protected clienthttprequest createrequest(uri uri, httpmethod httpmethod, clienthttprequestfactory requestfactory) { return new interceptingclienthttprequest(requestfactory, this.interceptors, uri, httpmethod); } }
interceptingclienthttprequest
的execute()
方法的特点是:若存在拦截器,交给给拦截器去执行发送请求return nextinterceptor.intercept(request, body, this)
,否则就自己上。
clienthttprequestinterceptor
关于请求拦截器,spring mvc内置了两个最基础的实现
==basicauthorizationinterceptor==:
// @since 4.3.1 但在spring5.1.1后推荐使用basicauthenticationinterceptor @deprecated public class basicauthorizationinterceptor implements clienthttprequestinterceptor { private final string username; private final string password; // 注意:username不允许包含:这个字符,但是密码是允许的 public basicauthorizationinterceptor(@nullable string username, @nullable string password) { assert.doesnotcontain(username, ":", "username must not contain a colon"); this.username = (username != null ? username : ""); this.password = (password != null ? password : ""); } @override public clienthttpresponse intercept(httprequest request, byte[] body, clienthttprequestexecution execution) throws ioexception { // 用户名密码连接起来后,用base64对字节码进行编码~ string token = base64utils.encodetostring((this.username + ":" + this.password).getbytes(standardcharsets.utf_8)); // 放进请求头:key为`authorization` 然后执行请求的发送 request.getheaders().add("authorization", "basic " + token); return execution.execute(request, body); } }
这个拦截器木有对body有任何改动,只是把用户名、密码帮你放进了请求头上。
需要注意的是:若你的header里已经存在了
authorization
这个key,这里也不会覆盖的,这会添加哦。但并不建议你有覆盖现象~
==basicauthenticationinterceptor==:
它是用来代替上类的。它使用标准的授权头来处理,参考httpheaders#setbasicauth、httpheaders#authorization
public class basicauthenticationinterceptor implements clienthttprequestinterceptor { private final string username; private final string password; // 编码,一般不用指定 @nullable private final charset charset; ... // 构造函数略 @override public clienthttpresponse intercept(httprequest request, byte[] body, clienthttprequestexecution execution) throws ioexception { httpheaders headers = request.getheaders(); // 只有当请求里不包含`authorization`这个key的时候,此处才会设置授权头哦 if (!headers.containskey(httpheaders.authorization)) { // 这个方法是@since 5.1之后才提供的~~~~~ // 若不包含此key,就设置标准的授权头(根据用户名、密码) 它内部也有这如下三步: // string credentialsstring = username + ":" + password; // byte[] encodedbytes = base64.getencoder().encode(credentialsstring.getbytes(charset)); // string encodedcredentials = new string(encodedbytes, charset); // 注意:它内部最终还是调用set(authorization, "basic " + encodedcredentials);这个方法的 headers.setbasicauth(this.username, this.password, this.charset); } return execution.execute(request, body); } }
说明:这两个请求拦截器虽是spring提供,但默认都是没有被"装配"的,所亲需要,请手动装配~
bufferingclienthttprequestfactory
包装其它clienthttprequestfactory
,使得具有缓存的能力。若开启缓存功能(有开关可控),会使用bufferingclienthttprequestwrapper
包装原来的clienthttprequest
。这样发送请求后得到的是bufferingclienthttpresponsewrapper
响应。
responseerrorhandler
用于确定特定响应是否有错误的策略接口。
// @since 3.0 public interface responseerrorhandler { // response里是否有错 boolean haserror(clienthttpresponse response) throws ioexception; // 只有haserror = true时才会调用此方法 void handleerror(clienthttpresponse response) throws ioexception; // @since 5.0 default void handleerror(uri url, httpmethod method, clienthttpresponse response) throws ioexception { handleerror(response); } }
继承树如下:
defaultresponseerrorhandler
spring
对此策略接口的默认实现,resttemplate
默认使用的错误处理器就是它。
// @since 3.0 public class defaultresponseerrorhandler implements responseerrorhandler { // 是否有错误是根据响应码来的,所以请严格遵守响应码的规范啊 // 简单的说4xx和5xx都会被认为有错,否则是无错的 参考:httpstatus.series @override public boolean haserror(clienthttpresponse response) throws ioexception { int rawstatuscode = response.getrawstatuscode(); httpstatus statuscode = httpstatus.resolve(rawstatuscode); return (statuscode != null ? haserror(statuscode) : haserror(rawstatuscode)); } ... // 处理错误 @override public void handleerror(clienthttpresponse response) throws ioexception { httpstatus statuscode = httpstatus.resolve(response.getrawstatuscode()); if (statuscode == null) { throw new unknownhttpstatuscodeexception(response.getrawstatuscode(), response.getstatustext(), response.getheaders(), getresponsebody(response), getcharset(response)); } handleerror(response, statuscode); } // protected方法,子类对它有复写 protected void handleerror(clienthttpresponse response, httpstatus statuscode) throws ioexception { string statustext = response.getstatustext(); httpheaders headers = response.getheaders(); byte[] body = getresponsebody(response); // 拿到body,把inputstream转换为字节数组 charset charset = getcharset(response); // 注意这里的编码,是从返回的contenttype里拿的~~~ // 分别针对于客户端错误、服务端错误 包装为httpclienterrorexception和httpservererrorexception进行抛出 // 异常内包含有状态码、状态text、头、body、编码等等信息~~~~ switch (statuscode.series()) { case client_error: throw httpclienterrorexception.create(statuscode, statustext, headers, body, charset); case server_error: throw httpservererrorexception.create(statuscode, statustext, headers, body, charset); default: throw new unknownhttpstatuscodeexception(statuscode.value(), statustext, headers, body, charset); } } ... }
到这里就可以给大家解释一下,为何经常能看到客户端错误,然后还有状态码+一串信息了,就是因为这两个异常。
httpclienterrorexception:
public class httpclienterrorexception extends httpstatuscodeexception { ... public static httpclienterrorexception create( httpstatus statuscode, string statustext, httpheaders headers, byte[] body, @nullable charset charset) { switch (statuscode) { case bad_request: return new httpclienterrorexception.badrequest(statustext, headers, body, charset); case unauthorized: return new httpclienterrorexception.unauthorized(statustext, headers, body, charset); case forbidden: return new httpclienterrorexception.forbidden(statustext, headers, body, charset); case not_found: return new httpclienterrorexception.notfound(statustext, headers, body, charset); case method_not_allowed: return new httpclienterrorexception.methodnotallowed(statustext, headers, body, charset); case not_acceptable: return new httpclienterrorexception.notacceptable(statustext, headers, body, charset); case conflict: return new httpclienterrorexception.conflict(statustext, headers, body, charset); case gone: return new httpclienterrorexception.gone(statustext, headers, body, charset); case unsupported_media_type: return new httpclienterrorexception.unsupportedmediatype(statustext, headers, body, charset); case too_many_requests: return new httpclienterrorexception.toomanyrequests(statustext, headers, body, charset); case unprocessable_entity: return new httpclienterrorexception.unprocessableentity(statustext, headers, body, charset); default: return new httpclienterrorexception(statuscode, statustext, headers, body, charset); } } ... }
它针对不同的状态码httpstatus
,创建了不同的类型进行返回,方便使用者控制,这在监控上还是蛮有意义的
badrequest、unauthorized、forbidden...等等都是
httpclienterrorexception
的子类
httpservererrorexception
代码类似,略~
extractingresponseerrorhandler
继承自defaultresponseerrorhandler
。在restful
大行其道的今天,spring5.0
开始提供了此类。它将http错误响应利用httpmessageconverter
转换为对应的restclientexception
// @since 5.0 它出现得还是很晚的。继承自defaultresponseerrorhandler // 若你的resttemplate想使用它,请调用resttemplate#seterrorhandler(responseerrorhandler)设置即可 public class extractingresponseerrorhandler extends defaultresponseerrorhandler { private list<httpmessageconverter<?>> messageconverters = collections.emptylist(); // 对响应码做缓存 private final map<httpstatus, class<? extends restclientexception>> statusmapping = new linkedhashmap<>(); private final map<httpstatus.series, class<? extends restclientexception>> seriesmapping = new linkedhashmap<>(); // 构造函数、set方法给上面两个map赋值。因为我们可以自己控制哪些状态码应该报错,哪些不应该了~ // 以及可以自定义:那个状态码抛我们自定义的异常,哪一系列状态码抛我们自定义的异常,这个十分的便于我们做监控 ... // 省略构造函数和set方法。。。 // 增加缓存功能~~~ 否则在交给父类 @override protected boolean haserror(httpstatus statuscode) { if (this.statusmapping.containskey(statuscode)) { return this.statusmapping.get(statuscode) != null; } else if (this.seriesmapping.containskey(statuscode.series())) { return this.seriesmapping.get(statuscode.series()) != null; } else { return super.haserror(statuscode); } } // 这个它做的事:extract:提取 @override public void handleerror(clienthttpresponse response, httpstatus statuscode) throws ioexception { if (this.statusmapping.containskey(statuscode)) { extract(this.statusmapping.get(statuscode), response); } else if (this.seriesmapping.containskey(statuscode.series())) { extract(this.seriesmapping.get(statuscode.series()), response); } else { super.handleerror(response, statuscode); } } private void extract(@nullable class<? extends restclientexception> exceptionclass, clienthttpresponse response) throws ioexception { if (exceptionclass == null) { return; } // 这里使用到了responseextractor返回值提取器,从返回值里提取内容(本文是提取异常) httpmessageconverterextractor<? extends restclientexception> extractor = new httpmessageconverterextractor<>(exceptionclass, this.messageconverters); restclientexception exception = extractor.extractdata(response); if (exception != null) { // 若提取到了异常信息,抛出即可 throw exception; } } }
若你想定制请求异常的处理逻辑,你也是可以自定义这个接口的实现的,当然还是建议你通过继承defaultresponseerrorhandler
来扩展~
responseextractor
响应提取器:从response
中提取数据。resttemplate
请求完成后,都是通过它来从clienthttpresponse
提取出指定内容(比如请求头、请求body体等)~
它的直接实现似乎只有httpmessageconverterextractor
,当然它也是最为重要的一个实现,和httpmessageconverter
相关。
在解释它之前,先看看这个:messagebodyclienthttpresponsewrapper
,它的特点:它不仅可以通过实际读取输入流来检查响应是否有消息体,还可以检查其长度是否为0(即空)
// @since 4.1.5 它是一个访问权限是default的类,是对其它clienthttpresponse的一个包装 class messagebodyclienthttpresponsewrapper implements clienthttpresponse { private final clienthttpresponse response; // java.io.pushbackinputstream @nullable private pushbackinputstream pushbackinputstream; // 判断相应里是否有body体 // 若响应码是1xx 或者是204;或者getheaders().getcontentlength() == 0 那就返回false 否则返回true public boolean hasmessagebody() throws ioexception { httpstatus status = httpstatus.resolve(getrawstatuscode()); if (status != null && (status.is1xxinformational() || status == httpstatus.no_content || status == httpstatus.not_modified)) { return false; } if (getheaders().getcontentlength() == 0) { return false; } return true; } // 上面是完全格局状态码(contentlength)来判断是否有body体的~~~这里会根据流来判断 // 如果response.getbody() == null,返回true // 若流里有内容,最终就用new pushbackinputstream(body)包装起来~~~ public boolean hasemptymessagebody() throws ioexception { ... } ... // 其余接口方法都委托~ @override public inputstream getbody() throws ioexception { return (this.pushbackinputstream != null ? this.pushbackinputstream : this.response.getbody()); } }
它的作用就是包装后,提供两个方法hasmessagebody、hasemptymessagebody
方便了对body体内容进行判断
// @since 3.0 泛型t:the data type public class httpmessageconverterextractor<t> implements responseextractor<t> { // java.lang.reflect.type private final type responsetype; // 这个泛型也是t,表示数据的class嘛~ // 该calss有可能就是上面的responsetype @nullable private final class<t> responseclass; // 重要:用于消息解析的转换器 private final list<httpmessageconverter<?>> messageconverters; ... // 省略构造函数 // 从clienthttpresponse 里提取值 @override @suppresswarnings({"unchecked", "rawtypes", "resource"}) public t extractdata(clienthttpresponse response) throws ioexception { messagebodyclienthttpresponsewrapper responsewrapper = new messagebodyclienthttpresponsewrapper(response); // 若没有消息体(状态码不对 或者 消息体为空都被认为是木有) if (!responsewrapper.hasmessagebody() || responsewrapper.hasemptymessagebody()) { return null; } // content-type若响应头header里没有指定,那默认是它mediatype.application_octet_stream mediatype contenttype = getcontenttype(responsewrapper); // 遍历所有的messageconverters,根据contenttype 来选则一个消息转换器 // 最终return messageconverter.read((class) this.responseclass, responsewrapper) ... } }
它的处理逻辑理解起来非常简单:利用contenttype
找到一个消息转换器,最终httpmessageconverter.read()
把消息读出来转换成java对象。
它还有两个内部类的实现如下(都是resttemplate
的私有内部类):
resttemplate: // 提取为`responseentity` 最终委托给httpmessageconverterextractor完成的 private class responseentityresponseextractor<t> implements responseextractor<responseentity<t>> { @nullable private final httpmessageconverterextractor<t> delegate; public responseentityresponseextractor(@nullable type responsetype) { // 显然:只有请求的返回值不为null 才有意义~ if (responsetype != null && void.class != responsetype) { this.delegate = new httpmessageconverterextractor<>(responsetype, getmessageconverters(), logger); } else { this.delegate = null; } } // 数据提取。都是交给`delegate.extractdata(response)`做了,然后new一个responseentity出来包装进去 // 若木有返回值(delegate=null),那就是一个`responseentity`实例,body为null @override public responseentity<t> extractdata(clienthttpresponse response) throws ioexception { if (this.delegate != null) { t body = this.delegate.extractdata(response); return responseentity.status(response.getrawstatuscode()).headers(response.getheaders()).body(body); } else { return responseentity.status(response.getrawstatuscode()).headers(response.getheaders()).build(); } } } // 提取请求头 private static class headersextractor implements responseextractor<httpheaders> { @override public httpheaders extractdata(clienthttpresponse response) { return response.getheaders(); } }
uritemplatehandler
这个组件它用于定义用变量扩展uri模板的方法。
// @since 4.2 出现较晚 // @see resttemplate#seturitemplatehandler(uritemplatehandler) public interface uritemplatehandler { uri expand(string uritemplate, map<string, ?> urivariables); uri expand(string uritemplate, object... urivariables); }
关于uri的处理,最终都是委托给uricomponentsbuilder
来完成。若对这块还存在一定疑问的,强烈强烈强烈
推荐阅读
resttemplate的使用和原理你都烂熟于胸了吗?【享学spring mvc】
总结
本文介绍的组件是去理解resttemplate
必备的组件们,属于开山篇。因为resttemplate
使用频繁,并且经常需要调优,因此我寄希望大家也能对它做较为深入的了解,这也是我写本系列的目的,共勉。
== 若对spring、springboot、mybatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞 ==
== 若对spring、springboot、mybatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞 ==