1. 概要

このチュートリアルでは、探索しますアプリケーション/問題+json応答を生成する方法問題SpringWebライブラリを使用します。 このライブラリは、エラー処理に関連する反復的なタスクを回避するのに役立ちます。

Problem SpringWebをSpringBootアプリケーションに統合することで、プロジェクト内の例外の処理方法を簡素化し、それに応じて応答を生成することができます

2. 問題ライブラリ

Problem は、JavaベースのRestAPIがコンシューマーにエラーを表現する方法を標準化することを目的とした小さなライブラリです。

問題は、通知したいエラーを抽象化したものです。 エラーに関する便利な情報が含まれています。 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プロジェクトのプロブレムライブラリの実装に焦点を当てます。

3. 問題のあるSpringWebセットアップ

これはMavenベースのプロジェクトなので、問題-spring-web依存関係をpom.xmlに追加しましょう。

<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>2.4.0</version> 
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.4.0</version>  
</dependency>

また、spring-boot-starter-webおよびspring-boot-starter-securityの依存関係も必要です。 problem-spring-webのバージョン0.23.0からSpringSecurityが必要です。

4. 基本構成

最初のステップとして、ホワイトラベルのエラーページを無効にして、代わりにカスタムエラー表現を表示できるようにする必要があります。

@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)

それでは、必要なコンポーネントのいくつかを ObjectMapperbeanに登録しましょう。

@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. 高度な構成

基本構成に加えて、セキュリティ関連の問題を処理するようにプロジェクトを構成することもできます。 最初のステップは、SpringSecurityとのライブラリ統合を有効にする構成クラスを作成することです。

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

    @Autowired
    private SecurityProblemSupport problemSupport;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // Other security-related configuration
        http.exceptionHandling()
          .authenticationEntryPoint(problemSupport)
          .accessDeniedHandler(problemSupport);
        return http.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応答が生成されます。

それでは、組み込みのアドバイス特性と、カスタムProblem実装を作成する方法について説明しましょう。

7. 組み込みのアドバイス特性

アドバイストレイトは、例外をキャッチして適切な問題オブジェクトを返す小さな例外ハンドラーです。

一般的な例外には、組み込みのアドバイス特性があります。 したがって、例外をスローするだけでそれらを使用できます。

throw new UnsupportedOperationException();

その結果、次のような応答が返されます。

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

Springセキュリティとの統合も構成したため、セキュリティ関連の例外をスローできます。

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オブジェクトのカスタム実装を作成する方法も学びました。

このガイドの実装は、 GitHubプロジェクトにあります。これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。