Problem Spring Webライブラリのガイド

1. 概要

このチュートリアルでは、* application / problem + json_応答を作成する方法* * https://github.com/zalando/problem-spring-web [Problem Spring Web library]を使用して調査します。*このライブラリは役立ちます。エラー処理に関連する繰り返しのタスクを避けるために。
Problem Spring WebをSpring Bootアプリケーションに統合することにより、プロジェクト内で例外を処理する方法を簡素化し、それに応じて応答を生成できます*。

2. 問題ライブラリ

https://github.com/zalando/problem[Problem]は、JavaベースのREST APIが消費者にエラーを表示する方法を標準化する目的の小さなライブラリです。
A ___問題__は、通知するエラーの抽象化です。 エラーに関する便利な情報が含まれています。 a _Problem_ responseのデフォルト表現を見てみましょう:
{
  "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. 問題のあるSpring Webセットアップ

これはMavenベースのプロジェクトなので、_https://search.maven.org/search?q = g:%22org.zalando%22%20AND%20a:%22problem-spring-web%22 [problem-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.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.12.RELEASE</version>
</dependency>
_https://search.maven.org/search?q = g:org.springframework.boot%20AND%20a:spring-boot-starter-web [spring-boot-starter-web] _と_httpsも必要です。 ://search.maven.org/search?q = g:org.springframework.boot%20AND%20a:spring-boot-starter-security [spring-boot-starter-security] _依存関係。 _problem-spring-web_のバージョン0.23.0からSpring Securityが必要です。

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 extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityProblemSupport problemSupport;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Other security-related configuration
        http.exceptionHandling()
          .authenticationEntryPoint(problemSupport)
          .accessDeniedHandler(problemSupport);
    }
}
最後に、セキュリティ関連の例外の例外ハンドラを作成する必要があります。
@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_ objectsに自動的に変換され、失敗の詳細を含む_application / problem json_応答が生成されます。
それでは、組み込みのアドバイス特性と、カスタム_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. カスタム_問題_の作成

_Problem_のカスタム実装を作成することが可能です。 _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_ responseを使用してエラーの詳細を含む応答を作成する方法を検討しました。 また、Spring Bootアプリケーションでライブラリを構成し、_Problem_ objectのカスタム実装を作成する方法も学びました。
このガイドの実装はhttps://github.com/eugenp/tutorials/tree/master/spring-boot-libraries[the GitHub project]で見つけることができます–これはMavenベースのプロジェクトであるため、インポートが簡単です。そのまま実行します。