RestTemplate的详细使用方法

在 Eureka 中,使用 RestTemplate 类进行服务调用,相信读者也能感觉到这个类还是比较实用。最重要的是,这个调用只要加上注解 @LoadBalanced,就可以使用 Ribbon 的客户端负载均衡。所以,本节将对这个类进行讲解。

在前面只讲解了简单的一种请求方式,也只有一种 API 的调用。但在实际场景中,我们四种 Restful 都是存在的,因此这里重点讲解 GET、POST、PUT、DELETE 的使用。

RestTemplate功能

首先,我们需要知道 RestTemplate 是由 Spring 提供的一个客户端,主要用于对 Rest 服务进行访问。在这个类中,提高了开发效率,使 HTTP 服务的通信得到简化,简化了提交表单的难度,还自带 Json 自动转换功能。

在默认情况下使用 JDK 的 HTTP 连接工具,但是可以通过属性切换不同的 HTTP 源,例如 Netty、OkHttp 或者 HttpComponents。RestTemplate 的方法如表11.1所示。

image 2024 04 01 09 01 48 887
Figure 1. 表11.1 RestTemplate方法

在这里主要介绍的是四种常用的请求方式,其他的先不进行介绍。为了对底层稍微有些了解,我们看看 GET 的其中一个方法,代码如下所示。

// GET
@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
      RequestCallback requestCallback = acceptHeaderRequestCallback (responseType);
   HttpMessageConverterExtractor<T> responseExtractor =
             new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
   return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

在上面的代码中,在使用时只需要传递 responseType,然后内部方法默认会使用 HttpMessageConverter 实例将 HTTP 转成 pojo 或者将 pojo 转成 HTTP。

在这里顺便了解一下 HttpMessageConverter 的接口功能,代码如下所示。

package org.springframework.http.converter;
/**
  * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
  */
public interface HttpMessageConverter<T> {
      //说明转换器是否可以读取给定的类
      boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
      //说明转换器是否可以写入给定的类
      boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
      //返回
      List<MediaType> getSupportedMediaTypes();
      //读取inputMessage
      T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException;
      //往outputMessage中写Object
      void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage);
}

图11.5是 RestTemplate 的类关系图。

image 2024 04 01 09 03 29 356
Figure 2. 图11.5 RestTemplate的类关系图

从图11.5中可以了解到,RestTemplate 实现了 RestOperations。这个 RestOperations 是一个定义了对 Rest 操作的基本集合,RestTemplate 实现这个接口,是对 Rest 操作的一次封装。在上图中有一个 HttpAccessor,这个类是基础的 HTTP 访问类,用来构建 HttpRequestFactory。

GET请求API

图11.6是主要 GET 的 API。

image 2024 04 01 09 08 02 518
Figure 3. 图11.6 GET的API

通过图11.6,说明 RestTemplate 访问 GET 主要有两种方式。

getForEntity方式

先说明这种方式让人更加容易理解。通过图11.6可以知道,这种方式有三个重载实现,并且在方法中将会返回 ResponseEntity 类型。先看什么是 ResponseEntity,代码如下所示。

public class ResponseEntity<T> extends HttpEntity<T>
{
    public HttpStatus getStatusCode(){}
    public int getStatusCodeValue(){}
    public boolean equals(@Nullable Object other) {}
    public String toString() {}
    public static BodyBuilder status(HttpStatus status) {}
    public static BodyBuilder ok() {}
    public static <T> ResponseEntity<T> ok(T body) {}
    public static BodyBuilder created(URI location) {} ...
}

从上面代码中可以知道,这里包含 HttpStatus 与 BodyBuilder 的信息,说明可以对 response 进行处理。这个对象 ResponseEntity 是 Spring 对于 HTTP 请求响应 response 的封装。

HttpStatus 是一个枚举类,包含了 HTTP 的请求状态;BodyBuilder 封装请求体对象;父类 HttpEntity 包含了请求头信息对象 HttpHeaders。

下面,我们看看提供出来的三个方法,主要是参数不同。

(1)public <T> ResponseEntity<T> getForEntity(String url, Class<T>responseType, Object…​ uriVariables)

在这个方法中,需要三个参数。第一个为 url,表示请求地址;第二个为 responseType,是请求响应体的封装类型;第三个为 uriVariables,表示不定参数。

使用方式主要是使用占位符,然后将参数放在第三个位置上。如果我们返回的是基本类型 String,代码如下所示。

RestTemplate restTemplate=new RestTemplate();
ResponseEntity<String>
responseEntity=restTemplate.getForEntity("http://HELLOSERVER/hello?id={1}",String.class,"100");
String bodyStr=responseEntity.getBody();

如果返回的响应体 Body 是一个对象,例如 User,则代码如下所示。

RestTemplate restTemplate=new RestTemplate();
ResponseEntity<User>
responseEntity=restTemplate.getForEntity("http://HELLOSERVER/hello?id={1}",User.class,"100");
User bodyStr=responseEntity.getBody();

在代码中,将会把占位符和第三个位置的数据进行替换。需要注意的是,如果存在多个参数,需要按照顺序进行书写。

(2)public <T> ResponseEntity<T> getForEntity(String url, Class<T>responseType, Map<String, ?> uriVariables)

将这个重载的方法与上面的方法进行对比,将会发现,第三个位置的参数使用 Map 类型进行封装。但是在使用时,不是简单地修改第三个位置的数据,还需要对 url 进行修改。在 url 中,占位符需要使用 Map 中的 key 作为参数。

使用方法如下,这里只使用 String 类型进行举例说明。

RestTemplate restTemplate=new RestTemplate();
Map<String> map=new HashMap<>();
map.put("id","100");
ResponseEntity<String>
responseEntity=restTemplate.getForEntity("http://HELLOSERVER/hello?id={id}",String.class,map);
String bodyStr=responseEntity.getBody();

(3)public <T> ResponseEntity<T> getForEntity(URI url, Class<T>responseType)

在这个方法中,使用 URI 对 url 与 uriVariavles 进行封装。

package java.net;
public final class URI implements Comparable<URI>, Serializable

URI 是 JDK 原始的封装类,标识一个资源标识符,使用方式如下。

RestTemplate restTemplate=new RestTemplate();
UriComponents uriComponents=UriComponentsBuilder.fromUriString(
      "http://HELLOSERVER/hello?id={id}"
).build()
      .expand("100")
      .encode();
URI uri=uriComponents.toUri();
ResponseEntity
responseEntity=restTemplate.getForEntity(uri,String.class).getBody();

在上面的代码中,我们可以看到,有一个 expand 方法,添加了一个参数。在本实例中,只是简单地添加了一个参数,属于方法一中的不定参数。其实在这里还有一种方式,和方法二一样,在这里可以添加 Map 存放参数。

getForObject方式

在理解了 getForEntity 方式之后,再来理解 getForObject 方式,会比较容易。相比于 getForEntity,这种方式包含了 HTTP 转换成 pojo 的功能,通过上文说过的默认转换器 HttpMessageConverter 进行转换,将响应体 Body 的内容转换成对象,在断点调试时会发现最后看到的是一个 pojo。

这里的使用场景,因为返回的是不包含 HTTP 的其他信息,所以只关注 Body 时,非常适用。实例如下。

RestTemplate restTemplate=new RestTemplate();
User user=restTemplate.getForObject"http://HELLOSERVER/hello?id={1}",User.class,"100");

在这段代码中,我们可以看到,返回时直接获取到了 pojo,不再需要从 HttpEntity 中 getBody 这段代码。这种方式还有多个使用场景如下。

(1)public <T> T getForObject(String url, Class<T> responseType,Object…​ uriVariables)

这种方式对应了 getForEntity 中的方法一,使用方式也相同。这里的函数直接返回 T,即是 responseType 的类型。

(2)public <T> T getForObject(String url, Class<T> responseType,Map<String, ?>uriVariables)

这种方式对应了 getForEntity 中的方法二,使用方式也相同。

(3)public <T> T getForObject(URI url, Class<T> responseType)

这种方式对应了 getForEntity 中的方法三,使用方式也相同。

POST请求API

首先,看看 POST 的 API 有哪些,如图11.7所示。

image 2024 04 01 09 22 50 504
Figure 4. 图11.7 POST的API

根据上图的 API 汇总,可以分为三种方式。

postForLocation方式

在图11.7中,关于 postForLocation 的方法共有三个,而且返回参数都是资源定位 URI,这个 URI 在上文说过,是 JDK 中的类。

为了说明三个方法的不同点,我们先看看里面的代码,代码如下所示。

public URI postForLocation(String url, @Nullable Object request, Object... uriVariables)
      throws RestClientException {
   RequestCallback requestCallback = httpEntityCallback(request);
   HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables);
   return (headers != null ? headers.getLocation() : null);
}
public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)
      throws RestClientException {
   RequestCallback requestCallback = httpEntityCallback(request);
      HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables);
   return (headers != null ? headers.getLocation() : null);
}
public URI postForLocation(URI url, @Nullable Object request) throws RestClientException {
   RequestCallback requestCallback = httpEntityCallback(request);
   HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor());
   return (headers != null ? headers.getLocation() : null);
}

从上面的代码中可以看到,每个方法中的操作几乎相同。首先会根据 request 返回一个 RequestCallback,然后执行 execute,返回 headers,最后从 headers 中取得 location。

其中,RequestCallback 允许操作请求头并写到请求体中。当使用 execute 方法时,不必担心任何资源管理,模板将总是关闭请求并处理任何错误。

这三个方法都是对给定的数据发送 POST 创建资源,返回 HTTP 头,这也就是一个新的资源。看一下 getLocation 的代码,代码如下所示。

@Nullable
public URI getLocation() {
   String value = getFirst(LOCATION);
   return (value != null ? URI.create(value) : null);
}

每个方法上都有如下的一段代码。

The {@code request} parameter can be a {@link HttpEntity} in order to add additional HTTP headers to the request.

这里解释了参数 Object request 的通常用法。如果想要在 HTTP headers 里面加点什么东西,可以在此处传一个 HttpEntity 对象。具体如何使用,将会在具体的使用方法上讲解。

(1)public URI postForLocation(String url, @Nullable Object request,Object…​ uriVariables)

在这个方法中,需要参数访问地址 url 和 uriVariables 变量。

这里有一点需要特别注意,request 可以是一个 HttpEntity,这样就可以被当作一个完整的 HTTP 请求进行处理,因为这里包含了请求头和请求体。但是这里的类型是 Object,就是说 request 也可以是一个普通的对象,然后 RestTemplate 会把请求对象转换成 HttpEntity 进行处理,request 的内容被当成一个消息体进行处理。

RestTemplate restTemplate=new RestTemplate();
HttpHeaders headers = new HttpHeaders();
// 在header里面设置编码方式
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
String body = "tom";
HttpEntity<String> requestEntity = new HttpEntity<String>(body , headers);
URI url=restTemplate.postForLocation(URI.create("http://HELLOSERVER/hello"), requestEntity);

在上面的代码中,直接使用了 HttpEntity 对象。

(2)public URI postForLocation(String url, @Nullable Object request,Map<String, ?>uriVariables)

这里的参数保存方式使用 Map 方式,我们在前面已经介绍过,如果读者还是不清楚,可以看看 postForObject 的使用方式。

(3)public URI postForLocation(URI url, @Nullable Object request)

这个方法可以将变量的参数封装在 URI 中。

postForObject方式

对于 POST 请求方式,请求参数一般都是放在 Body 体中,这样比较安全。但是有时候,一些普通的参数也可以放在 URL 上。但在控制类 Controller 获取参数的时候,情况稍微有些不同,下面也会做一些介绍。

(1)public <T> T postForObject(String url, @Nullable Object request,Class<T>responseType,Object…​ uriVariables)

使用方式如下。

RestTemplate restTemplate=new RestTemplate();
Map<String, Object> object = restTemplate.postForObject("http://HELLOSERVER/hello?name={name}", null, Map.class,"tom");

上面的代码没有写 request,按照前面的说法直接写即可。然后 url 中的参数,没有放在 Body 体中,而是放在 url 上,这样直接写在最后即可。然后,关于在控制类中如何获取参数的问题,在这里做一个实例。

@PostMapping(value = "/hello")
public Map<String, Object> hello(@RequestParam String name){
   Map<String, Object> ret = new HashMap();
   User user= new User ();
   user.setName(name);
   userService.save(user);
   ret.put("user", user);
   return ret;
}

上面的代码比较简单,我们还可以根据 name 与 Body 体做很多事情。在 URL 挂载参数还有另一种方式,如下所示。

RestTemplate restTemplate=new RestTemplate();
Map<String, Object> object = restTemplate.postForObject("http://HELLOSERVER/hello/{name}", null, Map.class,"tom");

控制类的处理方式也稍有不同,主要是 URL 上获取参数使用的注解不同,代码如下所示。

@PostMapping(value = "/hello/{name}")
public Map<String, Object> hello2(@PathVariable String name){
   Map<String, Object> ret = new HashMap();
   User user= new User ();
   user.setName(name);
   userService.save(user);
   ret.put("user", user);
   return ret;
}

(2)public <T> T postForObject(String url, @Nullable Object request,Class<T>responseType,Map<String, ?> uriVariables)

这里的使用方式与上面的方式不同。这里的 URL 的参数写在了 Map 中。同样,在 URL 挂载参数的方式也有两种,这里只讲一种使用方式,代码如下所示。

Map<String,Object> requestMap = new HashMap();
requestMap.put("name", "tom");
RestTemplate restTemplate = new RestTemplate();
Map<String, Object> object = restTemplate.postForObject("http://HELLOSERVER/hello?name={name}", null, Map.class,requestMap);

这个使用方式与 GET 的方式几乎相同,将参数写在 URI 中。

在使用 Body 体时,如何在控制类中获取参数?这个问题在上文没有说明过。其实只需说明一次,其他方法的用法均相同。用法代码如下所示。

HttpHeaders headers = new HttpHeaders(); // HTTP请求头
headers.setContentType(MediaType.APPLICATION_JSON_UTF8); // 请求头设置属性
Map<String,Object> body = new HashMap(); // 请求body
body.put("name", "tom");
HttpEntity<Map<String,Object>> requestEntity = new HttpEntity<Map<String,Object>>(body,headers);
Map<String, Object> object = restTemplate.postForObject("http://HELLOSERVER/hello", requestEntity, Map.class);

然后,写一个控制类,来对应本次请求调用,代码如下所示。

@PostMapping(value = "/hello")
public Map<String, Object> hello(@RequestBody Map<String, Object>request){
   Map<String, Object> ret = new HashMap();
   User user= new User();
   user.setName((String)request.getOrDefault("name", null));
   userService.save(user);
   ret.put("user", user);
   return ret;
}

在上面的代码中,我们获取 Body 体需要使用 @RequestBody 注解。

postForEntity方式

这个方法与 postForObject 的区别在于,postForObject 返回的类型需要自己指定,然后返回结果就是 Body 体转换成的自己指定的类型,而 postForEntity 则包含了 HTTP 响应的 headers,Body 体等信息。这里,不具体讲解,读者可以参考 getForEntity 的使用方式。

HttpEntity

为了让读者更好地理解上面的代码,这里单独对 HttpEntity 做一个小说明。首先,直接看看构造函数,代码如下所示。

protected HttpEntity() {
   this(null, null);
}

public HttpEntity(T body) {
   this(body, null);
}

public HttpEntity(MultiValueMap<String, String> headers) {
   this(null, headers);
}

public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) {
   this.body = body;
   HttpHeaders tempHeaders = new HttpHeaders();
   if (headers != null) {
      tempHeaders.putAll(headers);
   }
   this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders);
}

在上面的代码中,可以传递 headers,也可以传递 body,还可以同时传递 headers 和 body,可以根据自己的需要选择。

PUT请求API

PUT 的三个 API,如图11.8所示。

image 2024 04 01 09 45 45 181
Figure 5. 图11.8 PUT的API

通过 PUT 的请求方式对资源进行创建或者更新,通过上图,我们可以看到 PUT 是不存在返回值的。然后,这个操作还是幂等性的。

使用方式与 postFor 基本相同,不过这里也看一个实例程序,代码如下所示。

RestTemplate restTemplate=new RestTemplate();
int id=100;
User user=new User("tom");
restTemplate.put("http://HELLOSERVER/hello/{id}",user,id);

DELETE请求API

关于 DELETE 的 API,也有三种,如图11.9所示。

image 2024 04 01 09 47 28 691
Figure 6. 图11.9 DELETE的API

在使用 DELETE 时,使用的都是唯一标识进行删除数据,所以,这里只要一个标识加在 URL 中就可以了,不需要 Body 体,使用方式也比较简单。