Spring Boot で REST API のエラー時のレスポンスをカスタマイズする（後編）

# 概要
Spring Boot でエラー時のレスポンスをカスタマイズしてみたので備忘録としてここに記載します。
[前回の記事](https://qiita.com/Keisuke1119/items/d13302582fbf933b5fa4) では、独自例外を定義し、その例外をハンドルするようにしましたが、今回は Spring が独自に定義している例外のハンドルについて記載したいと思います。

# 経緯
前回の記事で定義した独自の例外を補足できたのですが、しかし、 ` MethodArgumentNotValidException ` 例外初め Spring が定義している例外が発生した際、そのエラー内容がボディ部が null で返ってきました。今回は、その原因と対応方針をまとめてみます。

# 前提条件
## 環境
- SpringBoot 2.1.3
- Java 1.8.0_181

# 原因
「Spring 徹底入門 Spring Framework による Java アプリケーション開発」には下記のように記載があります。
以下、引用をさせて頂きます。

> ResponseEntityExceptionHandler には、Spring MVC のフレームワーク処理で発生する例外をハンドリングする @ExceptionHandler メソッドが実装されています。つまり、上記のようなクラスを作成するだけで、フレームワーク処理で発生する例外を全てハンドリングすることができます。ResponseEntityExceptionHandler クラスを継承したクラスを作成しただけだと、レスポンスボディはからの状態でエラー応答されます。
ResponseEntityExceptionHandler を継承したクラスを作成しただけだと、レスポンスボディは空の状態で応答されます。レスポンスボディにエラー情報を出力する場合は、 handleExceptionInternal メソッドをオーバーライドします。

直接的な原因はこれで分かりました。
[前回の記事](https://qiita.com/Keisuke1119/items/d13302582fbf933b5fa4) では独自例外に対するハンドル処理は実装していたのですが、 Spring が定義している例外については一切ハンドル処理を定義していなかったため、例外が発生したとしてもボディ部がブランク表示されてしまったのだと思います。
（ ＝ ` ResponseEntityExceptionHandler ` を継承したハンドルクラスを定義した段階で、独自例外だけではなく、他の例外が発生した時もそのハンドル方法を定義しないといけないらしいです。この辺り、あまり詳しくないのですので、詳しい方教えてください。。。）

今度は、それをコードベースで確認をしてみました。

親クラスである `ResponseEntityExceptionHadler` クラスの ` MethodArgumentNotValidException ` をハンドルする ` handleMethodArgumentNotValid ` メソッドを確認すると、ボディ部に明示的に ` null ` が渡されているし、デバックをするとこの処理を通っています。

```ResponseEntityExceptionHadler.java
protected ResponseEntity<Object> handleMethodArgumentNotValid(
			MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

	return handleExceptionInternal(ex, null, headers, status, request);
}
```

`handleExceptionInternal` の引数の内容や順番に関しては、 [前回の記事](https://qiita.com/Keisuke1119/items/d13302582fbf933b5fa4) を参照ください。

# 対応方針
上記から分かるようにメソッドの引数のボディ部に明示的に ` null ` を渡しているので、このメソッドをオーバーライドして、ボディ部に実際に表示させたいエラー内容を渡してあげれば表示されるはずです。
では、実際に動かしてみたいと思います。

# コントローラの定義

```HelloSpringExceptionController.java
package com.example.demo.springExceptionErrorResponse;

import com.example.demo.errorResponse.ErrorDetail;
import com.example.demo.errorResponse.MyException;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/springexceptionhandle")
public class HelloSpringExceptionController {

    @RequestMapping("/test")
    public void get(@RequestBody @Validated Person person, Model model) {
    }
}
```

# モデルクラスの定義

```Person.java
package com.example.demo.springExceptionErrorResponse;

import lombok.Data;

import javax.validation.constraints.NotNull;

@Data
public class Person {
    @NotNull
    String firstName;
    @NotNull
    String lastName;
}
```

# 例外ハンドラクラスの定義
今回は簡単に ` "エラーだよ。" ` という文字列を渡してみました。
ここに関してはエラー時のボディ用のクラス定義を行なうなどして、よしなに定義してみてください。

```SpringExceptionHandler.java
package com.example.demo.springExceptionErrorResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
public class SpringExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(
            Exception ex,
            Object object,
            HttpHeaders headers,
            HttpStatus status,
            WebRequest request) {
        return super.handleExceptionInternal(ex, "エラーだよ。", headers, status, request);
    }
}
```

# 実行
下記、 JSON を `http://localhost:8080/springexceptionhandle/test` に対して  `POST` でリクエストしてみます。
`Person.java` には `firstName` に対して `@NotNull` 制約が適応されているので、このリクエストはエラーになるはずです。

```input.json
{
  "firstName": null,
  "lastName": "名前"
}
```

# 結果
```
エラーだよ。
```
上記はレスポンスボディとしては寂しいですが、エラー発生時時刻、ステータスコードなどを表示させたい場合には、[前回の記事](https://qiita.com/Keisuke1119/items/d13302582fbf933b5fa4) のようにレスポンスボディ専用のクラスを定義して ` super.handleExceptionInternal ` メソッドの2つ目の引数にそのクラスのインスタンスを渡してあげれば表示されるはずです。

# 関連記事
[Spring Boot で REST API のエラー時のレスポンスをカスタマイズする（前編）](https://qiita.com/Keisuke1119/items/d13302582fbf933b5fa4)

# 参考記事
今回、記事を記載する上で下記の記事を参考にさせて頂きました。

[Spring 徹底入門 Spring Framework による Java アプリケーション開発](https://www.amazon.co.jp/Spring%E5%BE%B9%E5%BA%95%E5%85%A5%E9%96%80-Spring-Framework%E3%81%AB%E3%82%88%E3%82%8BJava%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA-%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BENTT%E3%83%87%E3%83%BC%E3%82%BF/dp/4798142476)
[Spring Bootで作成したREST APIのエラーレスポンスをカスタムする](https://qiita.com/YutaKase6/items/434d36901e3a3360e9b0)
[SpringBootでエラーハンドリング](https://qiita.com/kws9/items/55982154a26a65426503)
[SpringBootの@RestControllerで例外処理をする](https://qiita.com/shotana/items/f3627e45feb912946c7c)