Spring Boot教程(24) – 用RestTemplate访问外部服务

日常开发中难免会向应用外部发起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转化成这个映射类的对象。更多相关信息,你可以查看HttpMessageConvertersAutoConfigurationRestTemplateAutoConfiguration的源码。

我们再来看看RestTemplate所提供的一些方法:

GET请求
POST请求
DELETE请求

除了上面三张图中的GET、POST和DELETE方法,RestTemplate还有一些类似的方法来发起HEAD、OPTIONS、PUT、PATCH等请求。从这些方法的命名和参数可以看出,RestTemplate的风格很RESTful,如果你访问的网络服务的API是这种风格的话,那么使用起来会非常顺手。

底层HTTP库

RestTemplate是比较高阶的接口,它利用了底层的HTTP库,底层的HTTP库有多种选择:

Spring为了方便你在这些这些库之间进行切换,实现了不同的ClientHttpRequestFactory,你选择了不同的库,就需要给RestTemplate设置不同的ClientHttpRequestFactory,上面三种库分别对应下面三个类:

  • HttpComponentsClientHttpRequestFactory
  • SimpleClientHttpRequestFactory
  • OkHttp3ClientHttpRequestFactory

当你需要的时候,可以创建一个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照样能用。

URI模板

在给RestTemplate传递链接的时候,可以给链接里设置占位符,方便之后通过它来动态生成链接。下图中,链接中的”userId”占位符的位置之后会被替换成10。

我刚开始没有研究API的时候,发现getForEntity方法的第三个参数是Map类型,自然而然地以为它是用来传递请求参数的(request parameter或者说query parameter),后来运行的时候才发现不对。如果你想传递请求参数,那么你可能需要使用UriComponentsBuilder来修改链接。

自定义Header

如果你想给请求自定义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一起使用。另外你还可以用RetrofitFeign来通过编写接口方法+注解来定义HTTP请求,使用起来非常简单和容易,我个人很喜欢这种工具,接下来肯定会写一篇文章来总结他们的用法的。

发表评论

电子邮件地址不会被公开。