• RestTemplate源码浅析


    近几年来,以信息为中心的表述性状态转移(Representational State Transfer,REST)已经称为替代传统SOAP Web 服务的流行方案。

    REST与RPC几乎没有任何关系。RPC是面向服务的,并关注于行为和动作;而REST是面向资源的,强调描述应用程序的事物和名词。

    RestTemplate定义了36个与REST资源交互的方法,其中的大多数都对应于HTTP的方法。 其实,这里面只有11个独立的方法,其中有十个有三种重载形式,而第十一个则重载了六次,这样一共形成了36个方法。

      • delete() 在特定的URL上对资源执行HTTP DELETE操作

      • exchange() 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的

      • execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象

      • getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象

      • getForObject() 发送一个HTTP GET请求,返回的请求体将映射为一个对象

      • postForEntity() POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得 到的

      • postForObject() POST 数据到一个URL,返回根据响应体匹配形成的对象

      • headForHeaders() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头

      • optionsForAllow() 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息

      • postForLocation() POST 数据到一个URL,返回新创建资源的URL

      • put() PUT 资源到特定的URL

    RestTemplate的核心之一 Http Client
    目前通过RestTemplate 的源码可知,RestTemplate 可支持多种 Http Client的http的访问,如下所示:

    • 基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory,默认。
    • 基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory
    • 基于 OkHttp3的OkHttpClientHttpRequestFactory。
    • 基于 Netty4 的 Netty4ClientHttpRequestFactory。

    其中HttpURLConnection 和 HttpClient 为原生的网络访问类,OkHttp3采用了 OkHttp3的框架,Netty4 采用了Netty框架。

    RestTemplate类的继承结构

    通过IDE的工具可以看到RestTemplate的继承结构很简单:


    由图可以看出继承了一个类,实现了一个接口,我们来看看这些接口和类有什么作用。
    HttpAccessor:官方文档这样描述:Base class for RestTemplate and other HTTP accessing gateway helpers, defining common properties such as the ClientHttpRequestFactory to operate on.可以看出,这个抽象类不仅可以作为RestTemplate的基类,还可以让其他http访问网关助手去继承,然后就可以获得它的功能了,同时呢,这个抽象类是设置一些共有的属性,其中ClientHttpRequestFactory就是在这里设置的。在我看来,这种抽象类是在避免样板代码,把共有的代码放到上层,但是如果要设置其他共有属性呢,只能一层层继承下去了。比如接下来要看的InterceptingHttpAccessor。
    InterceptingHttpAccessor:Base class for RestTemplate and other HTTP accessing gateway helpers, adding interceptor-related properties to HttpAccessor's common properties.就是加了一系列拦截器,这些拦截器会在请求执行之前执行,比如添加一些请求头之类的,http Basic认证就可以通过这种方式来加上去,spring提供这个类BasicAuthorizationInterceptor。至于这个接口为什么给定义的方法ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)三个这样的参数,还得去想想。
    RestOperations:Interface specifying a basic set of RESTful operations. Implemented by RestTemplate. Not often used directly, but a useful option to enhance testability, as it can easily be mocked or stubbed.这个接口是和RESTful api配套的,指定RESTful基本操作集。也就是说这个接口说明了一种能力,一种可以发各种rest请求的能力。

    从上面的分析来看呢,RestTemplate类继承了可以设置共有属性的类,有了基础的http请求功能,然后实现了RESTful基本操作集的接口,有了发rest请求的能力,当然这个能力得RestTemplate它自己去利用这些共有属性去实现了。这么一组合呢,RestTemplate就可以拿来用了。当然,http协议是比较复杂的,各种请求头啊、不同的数据格式啊等等,这些问题的处理逻辑当然不会放在RestTemplate这个类中,不然会写的很长。所以就有了很多委托类,为了让用户有自己的扩展,这些委托类一般都采用了策略模式来设计其实就是多态。接下来我们就来看看这些委托类以及它们的设计思想。
    先大概看看RestTemplate有什么属性,也就是委托类,它的功能肯定要依靠这些属性来实现的。

    1、先来看看messageConverters消息转换器,这是干嘛用的呢?

    • HttpMessageConverter<T>这个接口的描述Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.可以看到这是一个策略接口,策略模式见《行为类模式--策略模式》然后就是说它可以从request和response转换数据,也就是T类型和request支持的mime(多功能网络邮件扩展协议)类型数据互相转换。比如User这个类转换成json格式,也可以把json格式的User转换成User类,当然还有其他的mime类型。
    • 从RestTemplate的构造方法可以看到,你可以使用默认的转换器,如下:
    public RestTemplate() {
            this.messageConverters.add(new ByteArrayHttpMessageConverter());
            this.messageConverters.add(new StringHttpMessageConverter());
            this.messageConverters.add(new ResourceHttpMessageConverter());
            this.messageConverters.add(new SourceHttpMessageConverter<Source>());
            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());
            }
        }

    还可以使用自己准备的消息转换器,有这样的构造方法。可以去查看它的源码。

    • 拿其中一个转换器来说明,AllEncompassingFormHttpMessageConverter这个转换器继承了FormHttpMessageConverter,FormHttpMessageConverter的描述太长了,可以去官方文档查看https://docs.spring.io/spring/docs/4.3.17.BUILD-SNAPSHOT/javadoc-api/,它可以读写常规的表单类型,也就是application/x-www-form-urlencoded,但是只能写multipart类型数据,不能读,那如果要读的话是用哪个转换器呢?还得去看看。有意思的是,描述的最后一句话,Some methods in this class were inspired by org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity.说明spring还是参考了apache在这方面的实现,刚好我最近做的一个api测试工具就是用到apache的MultipartEntityBuilder来构建httpEntity。

      从它的属性和构造方法可知,每个part可以是不同的数据类型,然后有多个partConverter来解析这些数据。同时呢,它所支持的mime类型也初始化了,然后在实现HttpMessageConverter<T>的canRead、canWrite方法时就能用到了。接下来看看read和write方法,它们是怎么实现的,我们可以从实现的角度来看这两个方法的参数设置有什么学问,如果是自己来设计的话,会是什么样呢?

      可以从它的代码看到,首先从HttpInputMessage获得一个流,为什么是这个接口,这个接口有什么用,还有一个HttpOutputMessage,这两个有什么区别,还得思考思考,暂且跳过这个问题,接下来就是得到一个字符串了,然后通过“&”这个符号分割,放到MultiMap里返回出去了。clazz这个参数没有用到,在别的转换器有用的,但是还不知道有什么用。总的看来,这是读常规表单的数据,并没有读multipart类型的,也没有这样类似的read方法了。

      write的话从代码可以看出能写两种格式,常规表单和multipart,具体的逻辑可查看它的源码。


    我们可以看到FormHttpMessageConverter这个类里还有一个私有的静态类,只能内部使用,是用来写一个mime类型的数据。multipart类型有多个不同类型的part,写每个part到OutputStream时都要用到一个转换器,而这个转换器需要一个HttpOutputMessage实现类,并不是一个OutputStream,所以封装一下,然后通过HttpOutputMessage的方法(也是入口)来改变OutputStream。
    这里有个问题,为什么是静态类。私有类还好理解,这个类只在FormHttpMessageConverter里使用,当然弄成私有类是合理的。静态内部类是嵌套类,它不能访问外围类的非静态成员,只能访问静态成员,除此之外,我觉得之所以要弄成静态类,是要说明这个内部类只是一个嵌套类,并不是外围类的一部分,关系不是很亲密。关于具体的区别可以参考这里。

    2、接下来看看ResponseErrorHandler这个接口的功能,有两个方法hasError、handleError,入参都是ClientHttpResponse,spring自己定义的一个接口。

    入参都是一个接口,这一层层的继承,肯定经过深思熟虑过的设计了,先跳过它。RestTemplate使用的是默认的错误处理器,逻辑很简单,如果hasError为真,就调用handleError,处理的逻辑用户自己实现了,在这个默认的处理器中,就是抛出异常HttpClientErrorException,这个异常又是一系列的接口继承:


    要知道,每个接口展现的就是这个层面的功能,这样可以把一个大功能分成一个个独立的小功能,然后在具体的业务逻辑中去配对使用,也就是要什么功能就用哪个接口,同时又对其他的功能不了解,也不关心。这有点像tcp/ip协议的分层。先跳过,有空再来看看。
    每次执行请求得到response后,就会使用这个错误处理器,先看看有没有错误,有的话就处理,没有就跳过。

    看他怎么获得errorHandler的,是通过一个获取方法,在effective java里有提到过这种方式,叫query method,在RestTemplate里除了set、get外,没有别的地方会直接通过this.errorHandler的方式来引用对象。估计在其他类也是一样。
    处理错误后抛出异常就结束了,看来抛出的那个异常很重要啊,spring肯定会捕获到这个异常来展示一些信息。

    3、接下来就是看看UriTemplateHandler这个接口,RestTemplate使用的也是默认的uri模板处理器,作用呢就是组装uri,有不同的组装方式,所以也就有不同的实现类。

    UriTemplateHandler在execute这个比较底层的方法使用,也就是抽象出的步骤比较靠后。DefaultUriTemplateHandler是使用UriComponentsBuilder来扩展uri的。

    3、接下来就是RestTemplate最主要的步骤了:

    例如上面是restTemplate源码中的getForObject和getForObject的 调用

    RestTemplate的主要逻辑就是这三步了,第一和第三步分别交给了RequestCallback、ResponseExtractor,来看看是怎么实现并使用这两个接口的。
    首先是RequestCallback,官方描述Callback interface for code that operates on a ClientHttpRequest. Allows to manipulate the request headers, and write to the request body.
    Used internally by the RestTemplate, but also useful for application code.,它是个回调接口,而且是对ClientHttpRequest操作的,方法就一个doWithRequest, ClientHttpRequest作为它的输出参数,很简单。然后它的实现也很简单,而且实现都在RestTemplate里,还是私有类,这个接口看来是个小众接口,使用范围不是很广。具体的实现可以查看源码。
    然后瞧瞧ResponseExtractor这个接口,官方描述Generic callback interface used by RestTemplate's retrieval methods Implementations of this interface perform the actual work of extracting data from a ClientHttpResponse, but don't need to worry about exception handling or closing resources.Used internally by the RestTemplate, but also useful for application code.也是一个普通的回调接口,而且不用在实现这个方法的时候担心异常处理和资源关闭的问题,因为这个方法是抛出异常的。
    它有三个实现类,一个是从response里抽取headers,一个是抽取httpEntity,另一个就有意思了,

    这个实现类封装了HttpMessageConverterExtractor,相当于做了一个代理,目的是获得ResponseEntity<T>这个类型的数据,如下图。


    4、到这里呢,RestTemplate的实现介绍完了,接下来会介绍在springboot中它是怎么自动注入的,也好了解下springboot的自动配置。

  • 相关阅读:
    常用的排序方法
    mongoose 操作
    formidable使用
    cors跨域(支持cookie跨域) node后台 express
    mongoose Schema写法
    vue生命周期钩子 (mounted 加载数据没有缓存用 activated 配合keep-alive组件)
    vue路由跳转 页面回到顶部
    RESTful风格的路由设计
    router-link 绑定事件不生效
    axios的Content-Type类型导致后台无法解析数据
  • 原文地址:https://www.cnblogs.com/duanxz/p/3517002.html
Copyright © 2020-2023  润新知