知識庫 / Spring RSS 訂閱

Spring Web 庫使用指南

Spring
HongKong
7
01:28 PM · Dec 06 ,2025

1. 概述

本教程將探討如何使用 application/problem+json 響應的生成方法。我們將使用 Problem Spring Web 庫。該庫可以幫助我們避免與錯誤處理相關的重複性任務。

通過將 Problem Spring Web 集成到我們的 Spring Boot 應用程序中,我們可以簡化項目內異常的處理方式,並生成相應的響應。

2. 問題庫

Problem 是一個小型庫,旨在標準化 Java 風格的 Rest API 將錯誤信息告知消費者的方式。

一個 Problem 是對我們想要告知的任何錯誤的一種抽象。它包含有關錯誤的一些有用的信息。下面是默認的 Problem 響應表示形式:

{
  "title": "Not Found",
  "status": 404
}

在這種情況下,狀態碼和標題已經足夠描述錯誤。但是,我們也可以添加對該錯誤的詳細描述:

{
  "title": "Service Unavailable",
  "status": 503,
  "detail": "Database not reachable"
}

我們還可以創建自定義 Problem 對象,以適應我們的需求:

Problem.builder()
  .withType(URI.create("https://example.org/out-of-stock"))
  .withTitle("Out of Stock")
  .withStatus(BAD_REQUEST)
  .withDetail("Item B00027Y5QG is no longer available")
  .with("product", "B00027Y5QG")
  .build();

在本教程中,我們將重點介紹 Spring Boot 項目中的 Problem 庫實現。

3. Spring Web 問題配置

由於這是一個基於 Maven 的項目,請在 <pom.xml> 文件中添加 <em><a href="https://mvnrepository.com/artifact/org.zalando/problem-spring-web">problem-spring-web</a></em> 依賴:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web</artifactId>
    <version>0.23.0</version>
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.2</version> 
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-security</artifactId>
    <version>3.3.2</version>  
</dependency>

我們還需要 spring-boot-starter-webspring-boot-starter-security依賴。Spring Security 需要從 problem-spring-web 的 0.23.0 版本及以上。

4. 基本配置

首先,我們需要禁用白標錯誤頁面,以便我們能夠看到自定義的錯誤表示形式:

@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)

現在,讓我們在 ObjectMapper Bean 中註冊一些必需的組件:

@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper().registerModules(
      new ProblemModule(),
      new ConstraintViolationProblemModule());
}

之後,我們需要將以下屬性添加到 application.properties文件中:

spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.http.encoding.force=true

最後,我們需要實現 ProblemHandling 接口:

@ControllerAdvice
public class ExceptionHandler implements ProblemHandling {}

5. 高級配置

除了基本配置之外,我們還可以配置項目以處理與安全相關的難題。第一步是創建配置類,以便啓用庫與 Spring Security 的集成:

@Configuration
@EnableWebSecurity
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration {

    @Autowired
    private SecurityProblemSupport problemSupport;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      return http.csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/"))
          .permitAll())
        .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(problemSupport)
          .accessDeniedHandler(problemSupport))
        .build();
    }
}

最後,我們需要為與安全相關的異常創建異常處理程序:

@ControllerAdvice
public class SecurityExceptionHandler implements SecurityAdviceTrait {}

6. REST 控制器

在配置好我們的應用程序後,我們準備創建一個 RESTful 控制器:

@RestController
@RequestMapping("/tasks")
public class ProblemDemoController {

    private static final Map<Long, Task> MY_TASKS;

    static {
        MY_TASKS = new HashMap<>();
        MY_TASKS.put(1L, new Task(1L, "My first task"));
        MY_TASKS.put(2L, new Task(2L, "My second task"));
    }

    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Task> getTasks() {
        return new ArrayList<>(MY_TASKS.values());
    }

    @GetMapping(value = "/{id}",
      produces = MediaType.APPLICATION_JSON_VALUE)
    public Task getTasks(@PathVariable("id") Long taskId) {
        if (MY_TASKS.containsKey(taskId)) {
            return MY_TASKS.get(taskId);
        } else {
            throw new TaskNotFoundProblem(taskId);
        }
    }

    @PutMapping("/{id}")
    public void updateTask(@PathVariable("id") Long id) {
        throw new UnsupportedOperationException();
    }

    @DeleteMapping("/{id}")
    public void deleteTask(@PathVariable("id") Long id) {
        throw new AccessDeniedException("You can't delete this task");
    }

}

在本項目中,我們故意拋出一些異常。這些異常會被自動轉換為 Problem 對象,從而產生包含故障詳細信息的 application/problem+json 響應。

現在,我們來討論一下內置的 Advice 特性和如何創建自定義 Problem 實現。

7. 內置建議特性

一個建議特性是一個小型的異常處理程序,用於捕獲異常並返回適當的問題對象。

有內置的建議特性用於常見的異常。因此,我們可以通過簡單地拋出異常來使用它們:

throw new UnsupportedOperationException();

作為結果,我們將會得到以下響應:

{
    "title": "Not Implemented",
    "status": 501
}

由於我們還配置了與 Spring Security 的集成,因此我們能夠拋出與安全相關的異常:

throw new AccessDeniedException("You can't delete this task");

獲取正確的響應:

{
    "title": "Forbidden",
    "status": 403,
    "detail": "You can't delete this task"
}

8. 創建自定義問題

可以創建自定義問題的實現。我們只需要擴展 AbstractThrowableProblem 類:

public class TaskNotFoundProblem extends AbstractThrowableProblem {

    private static final URI TYPE
      = URI.create("https://example.org/not-found");

    public TaskNotFoundProblem(Long taskId) {
        super(
          TYPE,
          "Not found",
          Status.NOT_FOUND,
          String.format("Task '%s' not found", taskId));
    }

}

我們也可以以以下方式拋出自定義問題:

if (MY_TASKS.containsKey(taskId)) {
    return MY_TASKS.get(taskId);
} else {
    throw new TaskNotFoundProblem(taskId);
}

由於拋出 TaskNotFoundProblem 異常,我們將獲得:

{
    "type": "https://example.org/not-found",
    "title": "Not found",
    "status": 404,
    "detail": "Task '3' not found"
}

9. 處理堆棧跟蹤

如果希望在響應中包含堆棧跟蹤,則需要相應地配置我們的 ProblemModule

ObjectMapper mapper = new ObjectMapper()
  .registerModule(new ProblemModule().withStackTraces());

默認情況下,因果鏈已禁用,但我們可以通過覆蓋行為輕鬆啓用它:

@ControllerAdvice
class ExceptionHandling implements ProblemHandling {

    @Override
    public boolean isCausalChainsEnabled() {
        return true;
    }

}

在啓用這兩個功能後,我們將會收到類似下面的響應:

{
  "title": "Internal Server Error",
  "status": 500,
  "detail": "Illegal State",
  "stacktrace": [
    "org.example.ExampleRestController
      .newIllegalState(ExampleRestController.java:96)",
    "org.example.ExampleRestController
      .nestedThrowable(ExampleRestController.java:91)"
  ],
  "cause": {
    "title": "Internal Server Error",
    "status": 500,
    "detail": "Illegal Argument",
    "stacktrace": [
      "org.example.ExampleRestController
        .newIllegalArgument(ExampleRestController.java:100)",
      "org.example.ExampleRestController
        .nestedThrowable(ExampleRestController.java:88)"
    ],
    "cause": {
      // ....
    }
  }
}

10. 結論

本文介紹瞭如何使用 Problem Spring Web 庫創建包含錯誤詳細信息的響應,通過使用 application/problem+json 響應來實現。 此外,我們還學習瞭如何在 Spring Boot 應用程序中配置該庫,並創建自定義 Problem 對象的實現。

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

發佈 評論

Some HTML is okay.