知識庫 / Spring / Spring Boot RSS 訂閱

使用 ProblemDetail 返回錯誤 (Spring Boot)

Spring Boot
HongKong
8
10:58 AM · Dec 06 ,2025

1. 概述

本文將探討在 Spring Boot 應用中使用 <em >ProblemDetail</em> 返回錯誤的方法。無論我們處理 REST API 還是響應式流,它都提供了一種標準化的方式,將錯誤信息傳達給客户端。

讓我們深入瞭解原因。我們將探討在引入它之前錯誤處理的方式,然後,我們還將討論這個強大工具背後的規範。最後,我們將學習如何使用它準備錯誤響應。

2. 我們為什麼應該關注 ProblemDetail ?

利用 ProblemDetail 對錯誤響應進行標準化對於任何 API 來説至關重要。

它有助於客户端理解和處理錯誤,從而提高 API 的可用性和可調試性。 這將帶來更好的開發者體驗和更健壯的應用。

採用它還可以幫助提供更具信息量的錯誤消息,這些錯誤消息對於維護和故障排除我們的服務至關重要。

3. 傳統錯誤處理方法

<em ProblemDetail</em> 之前,我們通常使用自定義異常處理器和響應實體來處理 Spring Boot 中的錯誤。我們創建了自定義錯誤響應結構。這導致了不同 API 之間的一致性問題。

此外,這種方法需要大量的樣板代碼。 此外,它缺乏標準化的錯誤表示方法,使得客户端難以統一解析和理解錯誤消息。

4. ProblemDetail 規範

ProblemDetail 規範是 RFC 7807 標準的一部分。它定義了錯誤響應的一致結構,包括 type, title, status, detailinstance 等字段。這種標準化有助於 API 開發人員和消費者,通過提供錯誤信息的通用格式。

實施 ProblemDetail 確保我們的錯誤響應具有可預測性和易於理解性。 這反過來又提高了 API 與其客户端之間的整體溝通效果。

接下來,我們將探討如何在我們的 Spring Boot 應用程序中實施它,從基本設置和配置開始。

5. 在 Spring Boot 中實現 ProblemDetail

在 Spring Boot 中實現問題詳情有多種方法。

5.1. 通過應用程序屬性啓用 ProblemDetail

首先,我們可以通過添加屬性來啓用它。對於 RESTful 服務,請在 application.properties 中添加以下屬性:

spring.mvc.problemdetails.enabled=true

此屬性允許在基於 MVC(servlet 堆棧)的應用程序中自動使用 ProblemDetail 進行錯誤處理。

對於反應式應用程序,我們將會添加以下屬性:

spring.webflux.problemdetails.enabled=true

一旦啓用後,Spring 使用 ProblemDetail 報告錯誤:

{
    "type": "about:blank",
    "title": "Bad Request",
    "status": 400,
    "detail": "Invalid request content.",
    "instance": "/sales/calculate"
}

該屬性在錯誤處理中會自動提供 ProblemDetail。 此外,如果不需要,也可以關閉它。

5.2. 在異常處理程序中實現 ProblemDetail

全局異常處理程序在 Spring Boot REST 應用程序中實現了集中式錯誤處理。

讓我們考慮一個簡單的 REST 服務,用於計算折扣價格。

它接受請求並返回結果。 此外,它還執行輸入驗證和執行業務規則。

讓我們查看請求的實現:

public record OperationRequest(
    @NotNull(message = "Base price should be greater than zero.")
    @Positive(message = "Base price should be greater than zero.")
        Double basePrice,
    @Nullable @Positive(message = "Discount should be greater than zero when provided.")
        Double discount) {}

以下是結果的實現:

public record OperationResult(
    @Positive(message = "Base price should be greater than zero.") Double basePrice,
    @Nullable @Positive(message = "Discount should be greater than zero when provided.")
        Double discount,
    @Nullable @Positive(message = "Selling price should be greater than zero.")
        Double sellingPrice) {}

以下是無效操作異常的實現:

public class InvalidInputException extends RuntimeException {

    public InvalidInputException(String s) {
        super(s);
    }
}

現在,讓我們來實現 REST 控制器以提供該端點:

@RestController
@RequestMapping("sales")
public class SalesController {

    @PostMapping("/calculate")
    public ResponseEntity<OperationResult> calculate(
        @Validated @RequestBody OperationRequest operationRequest) {
    
        OperationResult operationResult = null;
        Double discount = operationRequest.discount();
        if (discount == null) {
            operationResult =
                new OperationResult(operationRequest.basePrice(), null, operationRequest.basePrice());
        } else {
            if (discount.intValue() >= 100) {
                throw new InvalidInputException("Free sale is not allowed.");
            } else if (discount.intValue() > 30) {
                throw new IllegalArgumentException("Discount greater than 30% not allowed.");
            } else {
                operationResult = new OperationResult(operationRequest.basePrice(),
                    discount,
                    operationRequest.basePrice() * (100 - discount) / 100);
            }
        }
        return ResponseEntity.ok(operationResult);
    }
}

SalesController 類處理 HTTP POST 請求,位於 “/sales/calculate” 終點。

它檢查並驗證 OperationRequest 對象。如果請求有效,它會計算銷售價格,並考慮可選的折扣。如果折扣無效(超過 100% 或超過 30%),則拋出異常。如果折扣有效,它會通過應用折扣計算最終價格,並以包含在 ResponseEntity 中的 OperationResult 形式返回。

現在讓我們看看如何在全局異常處理程序中實現 ProblemDetail

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(InvalidInputException.class)
    public ProblemDetail handleInvalidInputException(InvalidInputException e, WebRequest request) {
        ProblemDetail problemDetail
            = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());
        problemDetail.setInstance(URI.create("discount"));
        return problemDetail;
    }
}

GlobalExceptionHandler 類,帶有 @RestControllerAdvice 註解,擴展了 ResponseEntityExceptionHandler 以在 Spring Boot 應用程序中提供集中式異常處理。

它定義了一個處理 InvalidInputException 異常的方法。當此異常發生時,它會創建一個 ProblemDetail 對象,帶有 BAD_REQUEST 狀態和異常的錯誤消息。此外,它將 instance 設置為 URI (“discount”),以指示錯誤的特定上下文。

這種標準化的錯誤響應為客户端提供了關於錯誤發生情況的清晰詳細信息。

ResponseEntityExceptionHandler 是一個方便在標準化的方式下處理異常的類,適用於應用程序。 這樣,將異常轉換為有意義的 HTTP 響應的過程得到了簡化。 此外,它還使用 ProblemDetail 提供方法來處理常見的 Spring MVC 異常,例如 MissingServletRequestParameterException, MethodArgumentNotValidException 等。

5.3. 測試 ProblemDetail 實現

現在我們來測試我們的功能:

@Test
void givenFreeSale_whenSellingPriceIsCalculated_thenReturnError() throws Exception {

    OperationRequest operationRequest = new OperationRequest(100.0, 140.0);
    mockMvc
      .perform(MockMvcRequestBuilders.post("/sales/calculate")
      .content(toJson(operationRequest))
      .contentType(MediaType.APPLICATION_JSON))
      .andDo(print())
      .andExpectAll(status().isBadRequest(),
        jsonPath("$.title").value(HttpStatus.BAD_REQUEST.getReasonPhrase()),
        jsonPath("$.status").value(HttpStatus.BAD_REQUEST.value()),
        jsonPath("$.detail").value("Free sale is not allowed."),
        jsonPath("$.instance").value("discount"))
      .andReturn();
}

在本SalesControllerUnitTest中,我們已自動注入了MockMvcObjectMapper,用於測試SalesController

測試方法givenFreeSale_whenSellingPriceIsCalculated_thenReturnError()模擬了對“/sales/calculate”端點的POST請求,請求包含一個基本價格為100.0和折扣為140.0OperationRequest。因此,它應該觸發InvalidOperandException異常,在控制器中。

最後,我們驗證了響應類型為BadRequest,並帶有ProblemDetail,指示“免費銷售不允許。”

6. 結論

在本教程中,我們探討了 <em >ProblemDetails</em>,對其規範進行了研究,並在一個 Spring Boot REST 應用程序中實現了它。 此外,我們討論了它相對於傳統錯誤處理的優勢,以及如何在 Servlet 和響應式堆棧中使用它。

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

發佈 評論

Some HTML is okay.