日常开发中难免会向应用外部发起HTTP请求,比如访问云存储平台的API、调用微信或支付宝的API、抓取网页等等。Spring框架提供了 RestTemplate
来完成这一需求。 RestTemplate
提供了很多方法,方便你发起GET和POST等请求。下图是个简单的调用,去获取网页的HTML代码:
getForEntity方法会发起GET请求,然后返回一个 ResponseEntity
对象,它把HTTP请求的响应抽象成一个对象。通过 ResponseEntity
,你可以获取到此次响应的状态码、首部(Header)和主体(Body)。有的时候你仅仅需要Body部分,那就可以使用getForObject方法。
Spring Boot对 RestTemplate
也做了自动配置,但是他并没有提供给一个全局的 RestTemplate
对象,而是给了个全局的 RestTemplateBuilder
对象,你可以在需要的时候,build一个 RestTemplate
出来,像下面一样:
Spring Boot在创建了 RestTemplateBuilder
之后,还给他配置了很多 HttpMessageConverter
,它的作用是把HTTP请求中的Body转化为Java中的对象,或者把对象转化为Body。我们看看上面的getForObject方法,第二个参数是个Class对象,告诉 RestTemplate
你想把Body转化成什么类型的对象,如果你传递了String.class,那么 StringHttpMessageConverter
就会将Body转换成String对象。如果你传递了byte[].class,那么 ByteArrayHttpMessageConverter
会把Body转化成byte[]对象,如果你传递了某个JSON映射类, MappingJackson2HttpMessageConverter
会把Body转化成这个映射类的对象。更多相关信息,你可以查看 HttpMessageConvertersAutoConfiguration
和 RestTemplateAutoConfiguration
的源码。
我们再来看看RestTemplate所提供的一些方法:
除了上面三张图中的GET、POST和DELETE方法, RestTemplate
还有一些类似的方法来发起HEAD、OPTIONS、PUT、PATCH等请求。从这些方法的命名和参数可以看出, RestTemplate
的风格很RESTful,如果你访问的网络服务的API是这种风格的话,那么使用起来会非常顺手。
RestTemplate
是比较高阶的接口,它利用了底层的HTTP库,底层的HTTP库有多种选择:
Spring为了方便你在这些这些库之间进行切换,实现了不同的 ClientHttpRequestFactory
,你选择了不同的库,就需要给 RestTemplate
设置不同的 ClientHttpRequestFactory
,上面三种库分别对应下面三个类:
当你需要的时候,可以创建一个 ClientHttpRequestFactory
对象,传给 RestTemplate
。当然,一般情况下,你不必像上图这么做。Spring Boot会看看你的类路径下有没有HttpComponents,有的话就用,没有的话看看有没有OkHttp,最后才是保底的HttpUrlConnection。如果你想使用OkHttp,可以直接在build.gradle里加上依赖,其他啥都不用做。
值得一提的是,OkHttp对应的是 OkHttp3ClientHttpRequestFactory
,但是目前OkHttp的版本已经到4.x了,是不是应该再找一个 OkHttp4ClientHttpRequestFactory
呢?其实4.x版本只是用Kotlin把OkHttp重新写了一遍,接口并没有改变,还是兼容3.x的。另外你可能要问,我项目用的是Java,OkHttp用的是Kotlin,是不是我就不能在项目里用了呀?其实不是,虽然你在Intellij IDEA里跳入OkHttp源码时看到的是Kotlin代码,但是实际项目编译的时候,你引入的是Kotlin编译过之后的class文件,所以说加入4.x版本的OkHttp照样能用。
在给 RestTemplate
传递链接的时候,可以给链接里设置占位符,方便之后通过它来动态生成链接。下图中,链接中的”userId”占位符的位置之后会被替换成10。
我刚开始没有研究API的时候,发现getForEntity方法的第三个参数是Map类型,自然而然地以为它是用来传递请求参数的(request parameter或者说query parameter),后来运行的时候才发现不对。如果你想传递请求参数,那么你可能需要使用 UriComponentsBuilder
来修改链接。
如果你想给请求自定义Header,直接用getForObject或者postForObject这种方法可能做不到。这个时候,就需要使用更加通用的exchange方法:
一个HTTP请求,说白了也就这4个东西需要设定:HTTP方法、URL、Header和Body。exchange的这么多种重载方法就是变着花样方便你去传递这四个东西。你在上面这些方法中可能没直接看到设置Header的地方,因为Header被包含在 HttpEntity
类中, HttpEntity
= HttpHeaders
+ Body 。另外,你还可以使用 RequestEntity
去构造一个请求。 RequestEntity
继承于 HttpEntity
,相当于 RequestEntity
= HttpEntity
+ url + HTTP方法 。
如果你想给每个请求都加上特定的Header,可以通过拦截器实现:
我在写上面的代码的时候发现了一个巨坑。 RestTemplateBuilder
每次设定过之后,会返回一个新的 RestTemplateBuilder
,也就是说,在上图中,builder和newBuilder不是同一个对象,这好像跟我们以前所接触过的各种Builder不太一样,以前的Builder每次设置了之后都会返回this,而 RestTemplateBuilder
会去new一个新的对象,刚开始我感觉不太合理,没必要这么做。后来想想,因为Spring Boot会默认提供一个全局的 RestTemplateBuilder
,如果一个类对它进行了修改,其他类获取 RestTemplateBuilder
的时候就是修改过的了,可能会产生副作用。
上面我们说了给 单个 RestTemplate
的所有请求都添加Header的方法,下面说说给 所有 RestTemplate
添加Header的方法。 RestTemplateCustomizer
是用来对 RestTemplate
进行修改的类,我们在容器中扔这样一个 RestTemplateCustomizer
对象,那么Spring Boot在创建 RestTemplateBuilder
的时候,会自动把它提供给 RestTemplateBuilder
,这样每个 RestTemplate
都可以自定义了:
虽然上面我们的例子写的是给请求添加Header,但是你可以做的更多,总的来说,就是框架给了你一种能力:可以对单个请求、单个 RestTemplate
、以及所有 RestTemplate
进行自定义。我们可以看出, RestTemplate
的设计还是非常灵活方便的。
默认情况下,遇到4xx错误,或者5xx错误的时候,会抛出异常,不同的状态码有不同的异常,可能的异常如下图:
捕获异常之后,你还可以从异常对象中获取状态码、Header和Body,以便根据不同的错误信息做不同的处理。如果你对默认的异常处理机制不满意,可以自定义一个 ResponseErrorHandler
传递给 RestTemplate
,不过我觉得默认的就挺好,没必要再搞一套。
本文介绍了日常开发可能用到的接口和场景,还有一些比较细节的东西还可以挖掘,比如通过Multipart请求上传文件等等。除了 RestTemplate
,你还可以选择 WebClient
这种非阻塞、响应式的请求工具,它通常和WebFlux一起使用。另外你还可以用 Retrofit 和 Feign 来通过编写接口方法+注解来定义HTTP请求,使用起来非常简单和容易,我个人很喜欢这种工具,接下来肯定会写一篇文章来总结他们的用法的。