强大又优雅!Spring Boot 中 RestTemplate 的最佳实践详解

开发 前端
我们将详细演示如何使用 RestTemplate​ 进行 POST 请求,包括如何设置请求头和请求体、如何构建和传递复杂数据,以及如何处理返回的响应。

在现代开发中,API 的设计和调用变得尤为重要,尤其是基于 REST 架构的服务调用。RestTemplate 是 Spring 提供的用于同步调用 RESTful 服务的强大工具,它支持各种 HTTP 方法,如 GET、POST、PUT、DELETE 等。作为开发者,理解并掌握如何高效使用 RestTemplate 是优化服务交互性能的重要一步。本文旨在深入探讨 RestTemplate 的 POST 请求方法以及 exchange() 和 execute() 等常用方法,帮助你在实际开发中灵活使用这些工具应对复杂的业务场景。

通过示例代码,我们将详细演示如何使用 RestTemplate 进行 POST 请求,包括如何设置请求头和请求体、如何构建和传递复杂数据,以及如何处理返回的响应。同时,我们还将探索如何使用 exchange() 指定 HTTP 方法,实现灵活的请求处理。无论是初学者还是有经验的开发者,这篇文章都将为你提供有价值的参考。

RestTemplate 是 Spring 提供的用于访问 REST 服务的客户端。

它提供了多种便捷的方法来访问远程 HTTP 服务,大大提高了客户端代码开发的效率。

之前,我使用 Apache 的 HttpClient 开发 HTTP 请求。代码非常复杂,我不得不管理资源清理等问题,代码繁琐且包含大量冗余部分。

以下是我封装的一个 post 请求工具的截图:

图片图片

本教程将指导你如何在 Spring 生态系统中使用 RestTemplate 进行 GET 和 POST 请求,并通过 exchange 方法来指定请求类型。同时,还会分析 RestTemplate 的核心方法。

阅读完本教程后,你将能够以优雅的方式进行 HTTP 请求。

RestTemplate 简介

*RestTemplate* 是 *Spring* 中用于同步客户端通信的核心类。它简化了与 HTTP 服务的通信,并遵循 RestFul 原则。代码只需提供一个 URL 并提取结果。

默认情况下,RestTemplate 依赖于 JDK 的 HTTP 连接工具。但是,你可以通过 setRequestFactory 属性切换到其他 HTTP 工具源,例如 Apache HttpComponents、Netty 和 OkHttp。

RestTemplate 大大简化了表单数据的提交,并包含对 JSON 数据的自动转换。

然而,要真正掌握它的使用,必须理解 HttpEntity 的结构(包括 headers 和 body)以及它与 uriVariables 之间的区别。

这一点在 POST 请求中尤为明显,稍后我们将详细讨论。

此类的主要入口点基于六种 HTTP 方法:

图片图片

此外,exchange 和 execute 可以与上述方法互换使用。

在内部,RestTemplate 默认使用 HttpMessageConverter 实例,将 HTTP 消息转换为 POJO或将 POJO 转换为 HTTP 消息。默认情况下,它会为主要的 MIME 类型注册转换器,但你也可以通过 setMessageConverters 注册其他转换器。

(在实际使用中,这一点并不十分明显;许多方法都有一个 responseType 参数,你可以传递一个与响应体映射的对象,底层的 HttpMessageConverter 会进行映射。)

HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);

HttpMessageConverter.java 源代码:

public interface HttpMessageConverter<T> {
        // 判断该转换器是否可以读取给定的类。
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

        // 判断该转换器是否可以写入给定的类。
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

        // 返回一个 List<MediaType>
    List<MediaType> getSupportedMediaTypes();

        // 读取输入消息
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

        // 将对象写入输出消息
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;
}

在内部,RestTemplate 默认使用 SimpleClientHttpRequestFactory 和 DefaultResponseErrorHandler 来分别处理 HTTP 请求的创建和错误处理。

然而,你可以通过 setRequestFactory 和 setErrorHandler 来覆盖这些默认行为。

GET 请求

getForObject() 方法

public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables){}
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
public <T> T getForObject(URI url, Class<T> responseType)

getForObject() 方法可以将 HTTP 响应直接转换为 POJO,而不像 getForEntity() 需要额外处理响应。

getForObject 会直接返回 POJO,省略了大量响应信息。

POJO 示例:

public class Notice {
    private int status;
    private Object msg;
    private List<DataBean> data;
}
public class DataBean {
  private int noticeId;
  private String noticeTitle;
  private Object noticeImg;
  private long noticeCreateTime;
  private long noticeUpdateTime;
  private String noticeContent;
}

无参数的 GET 请求

/**
 * 无参数的 GET 请求
 */
@Test
public void restTemplateGetTest(){
    RestTemplate restTemplate = new RestTemplate();
    Notice notice = restTemplate.getForObject("http://icoderoad.com/notice/list/1/5", Notice.class);
    System.out.println(notice);
}

控制台输出:

INFO 19076 --- [           main] c.w.s.c.w.c.HelloControllerTest          
: Started HelloControllerTest in 5.532 seconds (JVM running for 7.233)

Notice{status=200, msg=null, data=[DataBean{noticeId=21, noticeTitle='aaa', noticeImg=null, 
noticeCreateTime=1525292723000, noticeUpdateTime=1525292723000, noticeContent='<p>aaa</p>'}, 
DataBean{noticeId=20, noticeTitle='ahaha', noticeImg=null, noticeCreateTime=1525291492000, 
noticeUpdateTime=1525291492000, noticeContent='<p>ah.......'

带参数的 GET 请求 1

Notice notice = restTemplate.getForObject("http://icoderoad.com/notice/list/{1}/{2}", Notice.class, 1, 5);

可以看到,使用了占位符 {1} 和 {2}。

带参数的 GET 请求 2

Map<String, String> map = new HashMap<>();
map.put("start", "1");
map.put("page", "5");
Notice notice = restTemplate.getForObject("http://icoderoad.com/notice/list/", Notice.class, map);

在这种情况下,使用 Map 加载参数,默认情况下会以 PathVariable 格式解析 URL。

getForEntity() 方法

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){}
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){}
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}

与 getForObject() 不同,此方法返回 ResponseEntity 对象。

如果需要将其转换为 POJO,则必须使用 JSON 工具类,可根据个人偏好选择。

对于不熟悉解析 JSON 的人,可以查阅工具如 FastJson 或 Jackson。接下来我们来探讨 ResponseEntity 的相关方法。

ResponseEntity、HttpStatus 和 BodyBuilder 的结构

//ResponseEntity.java

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.java

public enum HttpStatus {
public boolean is1xxInformational() {}
public boolean is2xxSuccessful() {}
public boolean is3xxRedirection() {}
public boolean is4xxClientError() {}
public boolean is5xxServerError() {}
public boolean isError() {}
}
//BodyBuilder.java

public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
    // 通过 Content-Length 头设置响应实体的内容长度
    BodyBuilder contentLength(long contentLength);
    // 设置响应实体的 MediaType
    BodyBuilder contentType(MediaType contentType);
    // 设置响应实体的内容并返回
    <T> ResponseEntity<T> body(@Nullable T body);
}

如上所示,ResponseEntity 包含了来自 HttpStatus 和 BodyBuilder 的信息,这使得处理原始响应变得更加简单。

示例:

@Test
public void rtGetEntity(){
    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<Notice> entity = restTemplate.getForEntity("http://icoderoad.com/notice/list/1/5", Notice.class);

    HttpStatus statusCode = entity.getStatusCode();
    System.out.println("statusCode.is2xxSuccessful()" + statusCode.is2xxSuccessful());

    Notice body = entity.getBody();
    System.out.println("entity.getBody()" + body);

    ResponseEntity.BodyBuilder status = ResponseEntity.status(statusCode);
    status.contentLength(100);
    status.body("在此处添加一个声明");
    ResponseEntity<Class<Notice>> body1 = status.body(Notice.class);
    Class<Notice> body2 = body1.getBody();
    System.out.println("body1.toString()" + body1.toString());
}

输出:

statusCode.is2xxSuccessful() true
entity.getBody() Notice{status=200, msg=null, data=[DataBean{noticeId=21, noticeTitle='aaa', ...
body1.toString() <200 OK, class com.waylau.spring.cloud.weather.pojo.Notice, {Content-Length=[100]}>

当然,像 getHeaders() 这样的方法也是可用的,但在此不作示例。

POST 请求

类似于 GET 请求,POST 请求也有 postForObject 和 postForEntity 方法。

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)
         throws RestClientException {}
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)
         throws RestClientException {}
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}

示例

这里我使用一个邮箱验证接口进行测试。

@Test
public void rtPostObject(){
    RestTemplate restTemplate = new RestTemplate();
    String url = "http://47.xxx.xxx.96/register/checkEmail";
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("email", "844072586@qq.com");

    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
    ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
    System.out.println(response.getBody());
}

执行结果:

{"status":500,"msg":"该邮箱已经注册","data":null}

为什么使用 MultiValueMap?

MultiValueMap 是 Map 的子类,它允许每个键存储多个值。这里简单介绍该接口:

public interface MultiValueMap<K, V> extends Map<K, List<V>> {...}

使用 MultiValueMap 的原因是 HttpEntity 接受的请求类型为 MultiValueMap:

public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers){}

在这个构造函数中,我们传入的 map 是请求体,headers 是请求头。

我们使用 HttpEntity 是因为,尽管 restTemplate.postForEntity 方法似乎接受 @Nullable Object request 类型,但如果深入追溯,会发现这个 request 是通过 HttpEntity 解析的。核心代码如下:

if (requestBody instanceof HttpEntity) {
    this.requestEntity = (HttpEntity<?>) requestBody;
} else if (requestBody != null) {
    this.requestEntity = new HttpEntity<>(requestBody);
} else {
    this.requestEntity = HttpEntity.EMPTY;
}

我曾尝试使用 map 传递参数,虽然编译时没有报错,但请求无效,最终出现 400 错误。

这种请求方式已经满足 POST 请求的需求,同时,cookie 作为请求头的一部分,也可以根据需要进行设置。

其他方法与此类似。

使用 exchange 指定 HTTP 方法

exchange() 方法与 getForObject()、getForEntity()、postForObject() 和 postForEntity() 不同之处在于它允许你指定 HTTP 方法。

你会注意到,所有的 exchange 方法似乎都有 @Nullable HttpEntity<?> requestEntity 参数,这意味着我们需要使用 HttpEntity 来传递请求体。正如前面提到的,使用 HttpEntity性能更好。

示例

@Test
public void rtExchangeTest() throws JSONException {
    RestTemplate restTemplate = new RestTemplate();
    String url = "http://icoderoad.com/notice/list";
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    JSONObject jsonObj = new JSONObject();
    jsonObj.put("start", 1);
    jsonObj.put("page", 5);

    HttpEntity<String> entity = new HttpEntity<>(jsonObj.toString(), headers);
    ResponseEntity<JSONObject> exchange = restTemplate.exchange(url, HttpMethod.GET, entity, JSONObject.class);
    System.out.println(exchange.getBody());
}

这次我使用了 JSONObject 来传递和返回数据。其他 HttpMethod 方法的使用类似。

使用 execute 指定 HTTP 方法

execute() 方法类似于 exchange(),它允许你指定不同的 HttpMethod 类型。但不同之处在于它返回的响应体是一个对象 <T>,而不是 ResponseEntity<T>。

需要强调的是,execute() 方法是上述所有方法的底层实现。以下是一个示例:

@Override
@Nullable
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)
        throws RestClientException {

    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

结语

通过对 RestTemplate 不同方法的深入讲解,特别是 POST 请求的使用以及 exchange()、execute() 的灵活运用,本文展示了在开发过程中如何使用这些方法简化与外部服务的交互,并提升代码的可读性和维护性。在面对复杂的业务需求时,掌握这些技术将帮助开发者在请求数据、处理响应以及提升 API 性能方面取得更好的平衡。

对于高效的 HTTP 请求处理,RestTemplate 提供了丰富的功能,灵活支持多种请求方式和参数配置,极大地简化了开发流程。随着项目复杂度的增加,理解和掌握这些工具的使用技巧,能够大大提升开发效率,同时减少潜在的错误。通过深入研究 RestTemplate,我们可以构建出更加健壮、高效的服务交互机制,满足不断变化的业务需求。希望本文能为你提供深入的理解,助力你的开发之旅。

责任编辑:武晓燕 来源: 路条编程
相关推荐

2024-10-11 11:46:40

2024-11-21 14:42:31

2024-04-18 08:28:06

2024-05-13 13:13:13

APISpring程序

2024-11-28 09:43:04

2024-03-08 10:50:44

Spring技术应用程序

2020-08-14 10:40:35

RestTemplatRetrofitJava

2024-08-13 08:41:18

2024-09-27 12:27:31

2024-10-15 10:38:32

2023-09-22 10:12:57

2023-09-14 08:16:51

2017-01-15 14:50:34

Spring Batc实践

2023-12-06 07:13:16

RESTAPI客户端

2024-08-02 09:15:22

Spring捕捉格式

2022-05-25 09:00:00

令牌JWT安全

2024-11-11 11:30:34

2023-04-17 23:49:09

开发代码Java

2022-10-26 07:14:25

Spring 6Spring业务

2009-06-10 09:08:13

WCF变更处理契约
点赞
收藏

51CTO技术栈公众号