当前位置: 移动技术网 > IT编程>开发语言>Java > RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】

RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】

2019年09月17日  | 移动技术网IT编程  | 我要评论

炽天之翼txt下载,后会无期高清下载,bilbilibi

每篇一句

做事的人和做梦的人最大的区别就是行动力

前言

本文为深入了解spring提供的rest调用客户端resttemplate开山,对它相关的一些组件做讲解。

tips:请注意区分resttemplateredistemplate哦~

clienthttprequestfactory

它是个函数式接口,用于根据urihttpmethod创建出一个clienthttprequest来发送请求~

clienthttprequest它代表请求的客户端,该接口继承自httprequesthttpoutputmessage,只有一个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;
}

它的继承树如下:
在这里插入图片描述
可以直观的看到,我们可以使用apachehttpclientokhttp3netty4都可,但这些都需要额外导包,默认情况下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使用,需注意如下几点:

  1. httpurlconnection对象不能直接构造,需要通过url类中的openconnection()方法来获得
  2. httpurlconnection的connect()函数,实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。http请求实际上直到我们获取服务器响应数据(如调用getinputstream()、getresponsecode()等方法)时才正式发送出去
    1. 配置信息都需要在connect()方法执行之前完成
  3. httpurlconnection是基于http协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。==请务必100%设置==
  4. http正文的内容是通过outputstream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成http正文
  5. 调用getinputstream()方法时,返回一个输入流,用于从中读取服务器对于http请求的返回信息。
  6. 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代替~


关于httpurlconnectionhttpclientokhttpclient的简单比较:
  • 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 httpclientandroid5.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);
    }

}

interceptingclienthttprequestexecute()方法的特点是:若存在拦截器,交给给拦截器去执行发送请求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,手动邀请你入群一起飞 ==

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网