知識庫 / Spring RSS 訂閱

使用 Spring RestTemplate 發送 XML POST 請求

Spring,XML
HongKong
6
10:36 AM · Dec 06 ,2025

1. 概述

儘管 JSON 和 REST API 越來越流行,但 XML 仍然深深地嵌入在企業系統中。 許多金融機構、醫療服務提供商、政府機構和遺留平台繼續依賴 SOAP 服務和基於 XML 的協議進行系統之間的通信。 當現代 Spring Boot 應用程序需要與這些成熟的系統集成時,開發人員必須彌合當代開發實踐與基於 XML 的接口之間的差距。

在本文中,我們將演示如何發送 XML POST 請求,將 Java 對象轉換為 XML,反序列化 XML 響應,以及實現 XML 驅動的集成過程中的乾淨服務層。

2. 理解 RestTemplate 和 XML 支持

RestTemplate 實現了 Spring 的同步 HTTP 客户端,採用阻塞式 I/O 模型,線程等待完整請求完成。這種方法適用於需要順序執行的企業事務處理,例如支付授權、合同驗證和文檔驗證工作流。

為了處理 XML 負載,Spring 使用 HTTP 消息轉換器。這些組件在發送請求時將 Java 對象序列化為 XML,並在接收響應時將 XML 轉換為 Java 對象。Spring Boot 在適當的 XML 庫存在時,會自動配置這些轉換器。在本文中,我們使用 Jackson XML 模塊,該模塊提供高效的 XML 支持,並且配置量極少。

3. 項目設置與依賴項

為了在 Spring Boot 中使用 XML,我們包含了 Spring Web starter 及其用於 RestTemplate 以及 Jackson XML 模塊。 Spring Boot 會檢測 XML 模塊並註冊所需的轉換器,用於 XML 序列化和反序列化

讓我們添加以下 Maven 依賴項:spring-boot-starter-webjackson-dataformat-xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

為了使用 RestTemplate 與依賴注入一起使用,我們需要將其定義為 Bean:

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

4. 創建 XML 映射模型

在發送或接收 XML 之前,我們定義 Java 類來表示我們的 XML 負載。 與 JSON 類似,簡單的 POJO 可以自動序列化和反序列化,無需任何特殊註解。

然而,企業級 XML 集成通常依賴於外部系統或 XSD 模式定義的明確的元素名稱和根結構。通過添加 Jackson 的 XML 註解,我們可以使映射具有可預測性並與預期的 XML 模式保持一致,這在銀行、保險和政府服務等行業中是一個常見的需求。

我們不手動構造原始 XML 字符串,而是讓 Jackson 根據這些註解處理轉換。 這有助於保持我們的代碼可維護性、類型安全和與 XML 消息的結構一致。

在此示例中,我們模擬了一個簡單的支付交互:我們的系統發送支付指令並期望收到確認結果的響應。

4.1. XML 請求模型

請求模型包含有關支付交易的信息。每個類字段對應一個 XML 元素。

@JacksonXmlRootElement(localName = "PaymentRequest")
public class PaymentRequest {
    @JacksonXmlProperty(localName = "transactionId")
    private String transactionId;

    @JacksonXmlProperty(localName = "amount")
    private Double amount;

    @JacksonXmlProperty(localName = "currency")
    private String currency;

    @JacksonXmlProperty(localName = "recipient")
    private String recipient;

    public PaymentRequest() {
    }

    public PaymentRequest(String transactionId, Double amount, String currency, String recipient) {
        this.transactionId = transactionId;
        this.amount = amount;
        this.currency = currency;
        this.recipient = recipient;
    }

    // getters and setters omitted for brevity
}

這個類將自動序列化為 XML。 結合以上值,它將生成:

<PaymentRequest>
    <transactionId>TXN12345</transactionId>
    <amount>1500.00</amount>
    <currency>USD</currency>
    <recipient>John Smith</recipient>
</PaymentRequest>

4.2. XML 響應模型

響應模型反映了外部系統返回的結構:

@JacksonXmlRootElement(localName = "PaymentResponse")
public class PaymentResponse {
    @JacksonXmlProperty(localName = "status")
    private String status;

    @JacksonXmlProperty(localName = "message")
    private String message;

    @JacksonXmlProperty(localName = "referenceNumber")
    private String referenceNumber;

    public PaymentResponse() {
    }

    public PaymentResponse(String status, String message, String referenceNumber) {
        this.status = status;
        this.message = message;
        this.referenceNumber = referenceNumber;
    }

    // getters and setters omitted for brevity
}

Spring 將在接收響應時將 XML 解序列化為該模型。

5. 實現 XML 客户端服務

為了保持應用程序結構整潔,我們實現了一個專門的服務,用於處理 XML 轉換和 HTTP 通信。其他組件將僅與 Java 對象交互,而不涉及 XML 或 HTTP 相關的邏輯。

我們將邏輯集中到一個方法中,該方法同時處理髮送請求和處理響應。

@Service
public class PaymentService {
    private final RestTemplate restTemplate;

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

    public PaymentResponse processPayment(PaymentRequest request, String paymentUrl) {
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_XML);
            headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));

            HttpEntity<PaymentRequest> entity = new HttpEntity<>(request, headers);

            ResponseEntity<PaymentResponse> response =
              restTemplate.postForEntity(paymentUrl, entity, PaymentResponse.class);

            return response.getBody();
        } catch (Exception ex) {
            throw new RuntimeException("Payment processing failed: " + ex.getMessage(), ex);
        }
    }
}

這種整合式方法簡化了API,使得調用者只需使用單個方法即可管理整個支付流程。XML轉換在幕後透明地進行,Spring負責序列化和反序列化。設計還實現了錯誤處理的集中化,同時保持了關注點分離,併為未來的增強功能,如日誌記錄、超時或重試邏輯,提供了靈活性。

6. 測試 XML 客户端服務

為了驗證我們的 XML 客户端的行為是否正確,我們使用 Mockito 為 PaymentService 編寫單元測試。每個測試都專注於特定的行為:成功的 XML 處理、服務器端驗證錯誤以及正確的 XML 頭部設置。通過 Mock RestTemplate,我們模擬了外部 API 的行為,而無需調用真實的服務器,從而使我們的測試快速可靠。

第一個測試驗證了“快樂路徑”場景:當我們提交有效的請求時,服務應該收到成功的 XML 響應,將其解析為 PaymentResponse 對象,並將其返回給調用者:

@Test
void givenValidPaymentRequest_whenProcessPayment_thenReturnSuccessfulResponse() {
    PaymentRequest request = new PaymentRequest("TXN001", 100.50, "USD", "Jane Doe");
    PaymentResponse expectedResponse = new PaymentResponse(
      "SUCCESS", "Payment processed successfully", "REF12345"
    );

    ResponseEntity<PaymentResponse> mockResponse =
      new ResponseEntity<>(expectedResponse, HttpStatus.OK);

    when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class)))
      .thenReturn(mockResponse);

    PaymentResponse actualResponse = paymentService.processPayment(request, testUrl);

    assertNotNull(actualResponse);
    assertEquals("SUCCESS", actualResponse.getStatus());
    assertEquals("REF12345", actualResponse.getReferenceNumber());
    assertEquals("Payment processed successfully", actualResponse.getMessage());

    verify(restTemplate).postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class));
}

在本場景中,我們模擬了一個成功的 XML 交換過程。測試確認服務正確地返回了從 XML 負載中填充的響應對象。我們還驗證了 RestTemplate 一次被調用,以確保正確地請求調用。

接下來,我們模擬外部系統拒絕請求的情況——例如,由於無效數據。在這種情況下,RestTemplate 會拋出 HttpClientErrorException,而我們的服務必須將其妥善地包裝起來並提供有意義的錯誤消息:

@Test
void givenRemoteServiceReturnsBadRequest_whenProcessPayment_thenThrowMeaningfulException() {
    PaymentRequest request = new PaymentRequest("TXN002", 200.0, "EUR", "John Smith");

    when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class)))
      .thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Invalid amount"));

    RuntimeException exception = assertThrows(RuntimeException.class,
      () -> paymentService.processPayment(request, testUrl));

    assertTrue(exception.getMessage().contains("Payment processing failed"));
    assertTrue(exception.getMessage().contains("Invalid amount"));
}

在這裏,我們模擬了API返回的400 Bad Request狀態。我們的測試確保服務不會默默失敗或返回空值,而是拋出一個清晰、描述性的異常,其中包含服務器端錯誤消息。

最後,某些XML API需要嚴格的Header值。因此,此測試驗證我們的客户端正確設置Content-Type和Accept Header為application/xml:

@Test
void givenXmlRequest_whenProcessPayment_thenSetCorrectXmlHttpHeaders() {
    PaymentRequest request = new PaymentRequest("TXN004", 300.0, "CAD", "Bob Wilson");
    PaymentResponse expectedResponse = new PaymentResponse("SUCCESS", "OK", "REF67890");

    when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class)))
      .thenReturn(new ResponseEntity<>(expectedResponse, HttpStatus.OK));

    paymentService.processPayment(request, testUrl);

    verify(restTemplate).postForEntity(
        eq(testUrl),
        argThat((HttpEntity<PaymentRequest> entity) -> {
            boolean hasXmlContentType = entity.getHeaders().getContentType()
              .includes(MediaType.APPLICATION_XML);
            boolean acceptsXml = entity.getHeaders().getAccept()
              .contains(MediaType.APPLICATION_XML);
            return hasXmlContentType && acceptsXml;
        }),
        eq(PaymentResponse.class)
    );
}
<p>最終,本次測試通過檢查傳遞給 <em >RestTemplate</em> 的請求頭,驗證了正確的 XML 合約合規性。因此,確保 XML 頭存在可以減少與實際 XML API 的兼容性問題。</p>

7. 結論

XML 在企業系統中仍然至關重要,而 Spring Boot 搭配 RestTemplate 提供了一種簡潔的 XML 集成方法。 通過使用帶有註解的 Java 模型和專門的服務層,現代應用程序可以無縫地與遺留系統進行通信,同時保持代碼清晰度。

如往常一樣,文章的完整源代碼可在 GitHub 上獲取

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

發佈 評論

Some HTML is okay.