從 RESTTemplate 消費頁面實體響應

REST
Remote
1
04:25 AM · Dec 01 ,2025

1. 概述

在本教程中,我們將研究 RestTemplate 以調用 RESTful 端點並讀取類型為 Page<Entity> 的響應。 我們將快速瞭解 Jackson 如何反序列化 RestTemplate 接收到的 JSON 響應。 我們將使用員工數據設置一個簡單的 RESTful 端點。

稍後,我們將設置一個客户端類,該類將使用 RestTemplate 從端點消耗數據,首先導致異常。 然後,我們將採取必要的步驟來使 RestTemplate 客户端能夠成功讀取 JSON 響應。 最後,我們將編寫一個集成測試以驗證正確行為。

2. RestTemplate 和 Jackson 序列化解

RestTemplate 是一個廣泛使用的客户端 HTTP 通信庫,它簡化了發出 HTTP 請求和處理響應的過程。當我們使用 RestTemplate 向服務器發出 HTTP 調用時,服務器的響應通常是 JSON 格式。Jackson 負責將此 JSON 響應反序列化為 Java 對象。

當 Jackson 遇到 JSON 對象並需要創建相應的 Java 類實例時,它會查找合適的構造函數或工廠方法來調用。 默認情況下,Jackson 使用默認構造函數進行實例化。 但是,在某些情況下,默認構造函數可能不可用或可能無法充分初始化對象。

為了解決此類情況,可以使用 @JsonCreator 註解來標記 Jackson 應該用於實例化,構造函數或工廠方法。 這允許我們定義在反序列化過程中自定義對象創建邏輯。

此外,當我們需要使用 Jackson 反序列化 JSON 並捕獲泛型類型時,我們可以提供 ParameterizedTypeReference 實例。 此類的目的是啓用捕獲和傳遞泛型類型。

為了捕獲泛型類型並在運行時保留它,我們需要創建一個子類,通常使用 inline 的方式,即 new ParameterizedTypeReference<List<String>>() {} 結果實例可用於獲取 Type 實例,該實例在運行時攜帶捕獲的參數化類型信息。

接下來,讓我們設置一個包含 RESTful 端點和調用端點的客户端類的員工數據示例。

3. 定義 REST 控制器

讓我們創建一個簡單的員工數據示例。 我們將創建一個 GET /employee/data 端點,它將以分頁響應返回 EmployeeDto 數據:

@GetMapping("/data")
public ResponseEntity<Page<EmployeeDto>> getData(@RequestParam(defaultValue = "0") int page, 
  @RequestParam(defaultValue = "10") int size) {
    List<EmployeeDto> empList = listImplementation();

    int totalSize = empList.size();
    int startIndex = page * size;
    int endIndex = Math.min(startIndex + size, totalSize);

    List<EmployeeDto> pageContent = empList.subList(startIndex, endIndex);

    Page<EmployeeDto> employeeDtos = new PageImpl<>(pageContent, PageRequest.of(page, size), totalSize);

    return ResponseEntity.ok().body(employeeDtos);
}

顯然,getData() 方法返回 Page<EmplyeeDto> 作為響應,內容為 List<EmployeeDto>

4. 定義客户端,使用 RestTemplate

讓我們考慮一個典型的場景,即從另一個外部服務通過 HTTP 調用 GET /organization/data 端點。 我們將使用 RestTemplate 定義用於調用端點的客户端。 它將嘗試將 JSON 序列化為 Page<EmployeeDto>

為了讓 Jackson 將 JSON 數據序列化為 Page<EmployeeDto>我們將提供具體的實現類 PageImpl,它是抽象類 Page 接口的:

@Component
public class EmployeeClient {
    private final RestTemplate restTemplate;

    public EmployeeClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public Page<EmployeeDto> getEmployeeDataFromExternalAPI(Pageable pageable) {
        String url = "http://localhost:8080/employee";

        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(url)
          .queryParam("page", pageable.getPageNumber())
          .queryParam("size", pageable.getPageSize());

        ResponseEntity<PageImpl<EmployeeDto>> responseEntity = restTemplate.exchange(uriBuilder.toUriString(),
          HttpMethod.GET, null, new ParameterizedTypeReference<PageImpl<EmployeeDto>>() {
          });

        return responseEntity.getBody();
    }
}

但是,嘗試向 Jackson 提供 ParameterizedType<Page<EmployeeDto>>ParameterizedType<PageImpl<EmployeeDto>> 將導致錯誤:

org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.data.domain.Pageable]; 
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Pageable` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 160] (through reference chain: org.springframework.data.domain.PageImpl["pageable"])

 5. 如何解決 HttpMessageConversionException

我們知道當 RestTemplate 調用返回 Page<EmployeeDto> 的端點時,響應無法成功讀取到 PageImpl<EmployeeDto>。 這是因為   PageImpl 類沒有默認構造函數。 此外,在現有的構造函數中沒有任何 @JsonCreator 註解。

為了解決反序列化問題,讓我們定義一個繼承 PageImpl<T>並具有默認構造函數以及 @JsonCreator 註解的自定義類:

public class CustomPageImpl<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public CustomPageImpl(@JsonProperty("content") List<T> content, @JsonProperty("number") int number,
      @JsonProperty("size") int size, @JsonProperty("totalElements") Long totalElements,
      @JsonProperty("pageable") JsonNode pageable, @JsonProperty("last") boolean last,
      @JsonProperty("totalPages") int totalPages, @JsonProperty("sort") JsonNode sort,
      @JsonProperty("numberOfElements") int numberOfElements) {
        super(content, PageRequest.of(number, 1), 10);
    }

    public CustomPageImpl(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public CustomPageImpl(List<T> content) {
        super(content);
    }

    public CustomPageImpl() {
        super(new ArrayList<>());
    }
}

本質上,CustomPageImpl 類提供用於將 JSON 響應反序列化為類實例的自定義構造函數。 它擴展了 PageImpl 類,該類通常用於表示分頁數據。 此外,我們添加了 @JsonCreator(JsonCreator.Mode.PROPERTIES) 註解,以指定隨後的構造函數應用於反序列化。

接下來,讓我們重構客户端,以便 restTemplate.exchange() 將 JSON 響應轉換為 CustomPageImpl

ResponseEntity<CustomPageImpl<EmployeeDto>> responseEntity = restTemplate.exchange(
  uriBuilder.toUriString(),
  HttpMethod.GET,
  null,
  new ParameterizedTypeReference<CustomPageImpl<EmployeeDto>>() {}
);

在這裏,調用 restTemplate.exchange() 方法以發送 HTTP GET 請求。 它期望接收類型為 ResponseEntity<CustomPageImpl<EmployeeDto>> 的響應。

ParameterizedTypeReference<CustomPageImpl<EmployeeDto>> 處理響應類型,允許將響應主體反序列化為包含 EmployeeDto 對象的 CustomPageImpl這在 Java 的類型擦除由於運行時丟失泛型類型信息而必須這樣做。

 6. 集成測試

最後,我們使用 CustomPageImpl測試客户端是否按預期工作:

@Test
void givenGetData_whenRestTemplateExchange_thenReturnsPageOfEmployee() {
    ResponseEntity<CustomPageImpl<EmployeeDto>> responseEntity = restTemplate.exchange(
      "http://localhost:" + port + "/organization/data",
      HttpMethod.GET,
      null,
      new ParameterizedTypeReference<CustomPageImpl<EmployeeDto>>() {}
    );

    assertEquals(200, responseEntity.getStatusCodeValue());
    PageImpl<EmployeeDto> restPage = responseEntity.getBody();
    assertNotNull(restPage);

    assertEquals(10, restPage.getTotalElements());

    List<EmployeeDto> content = restPage.getContent();
    assertNotNull(content);
}

在此,測試通過 restTemplate.exchange調用端點返回成功的響應。它包含類型為 PageImpl<EmployeeDto>的體,內容類型為 List<EmployeeDto>,以及分頁信息。

7. 結論

在本教程中,我們探討了使用 RestTemplate 進行 HTTP 請求和處理響應的問題。我們特別關注了將響應反序列化到 Page<Entity> 過程中涉及的問題。最後,我們演示了使用 CustomPageImpl 類與 ParameterizedTypeReference 成功地將 JSON 讀取到 Page<EmployeeDto>

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.