知識庫 / REST RSS 訂閱

從 RESTTemplate 消費 Page Entity 響應

REST
HongKong
11
03:37 AM · Dec 06 ,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 使用默認構造函數進行實例化。但是,在某些情況下,默認構造函數可能不可用或可能不足以正確初始化對象。

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

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

為了捕獲通用類型並在運行時保留它,我們需要創建一個子類,通常使用 inline 的方式,即 >。 這樣生成的實例可以用來獲取 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>我們將提供抽象 Page 接口的具體實現類 PageImpl :

@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 並具有默認構造函數以及 @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. 結論

在本教程中,我們探討了使用 <em >RestTemplate</em> 進行 HTTP 請求以及處理響應的方法。我們特別關注了將響應反序列化為 <em >Page&lt;Entity&gt;</em> 時所涉及的問題。最後,我們演示瞭如何結合使用 <em >CustomPageImpl</em> 類以及 <em >ParameterizedTypeReference</em>,從而成功地將 JSON 解析為 <em >Page&lt;EmployeeDto&gt;</em>

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

發佈 評論

Some HTML is okay.