【Spring Boot】発生した例外ごとのハンドリングを定義する

# ResponseEntityExceptionHandler について
Spring Boot では `ResponseEntityExceptionHandler`クラスで『どの例外が発生したときになにを返すか』のハンドリングを行っています。
このクラスを継承して例外ごとに定義されているメソッドをオーバーライドすることで、処理を好きなように行うことができます。

すべての例外のハンドリングがこのクラスに記述されているわけではないので、クラスの中身を確認してみてください。

# ハンドリングの例（HttpRequestMethodNotSupportedException）
処理の例を見ていきます。
`POST`メソッドしか定義されていないエンドポイントに対して`GET`でリクエストされたときなどに発生する`HttpRequestMethodNotSupportedException`において、任意のレスポンスボディを定義する方法について記述します。

```CustomControllerAdvice.java
/**
 * 各エンドポイントで共通の例外ハンドリングを定義する.
 */
@RestControllerAdvice
public class CustomControllerAdvice extends ResponseEntityExceptionHandler {

	/**
	 * HttpRequestMethodNotSupportedException(405)のハンドリング.
	 */
	@Override
	protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
			HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
		
		Map<String, Object> res = Map.of("errorMessages", List.of("Method not supported."), "status", "406");
		return handleExceptionInternal(ex, res, headers, status, request);
	}
}
```

まず、`ResponseEntityExceptionHandler`を継承した任意のクラスを作成します。（今回クラス名は`CustomControllerAdvice`としています。）
次にクラスに`@RestControllerAdvice`のアノテーションを付ける必要があります。

そして該当するメソッドをオーバーライドしていきます。
メソッドは`handle〇〇`となっており、`〇〇`の部分は例外の名前から`Exception`を外したものです。

`return`している`handleExceptionInternal`メソッドの引数には以下を入れる形になっています。

- 第1引数：例外
- 第2引数：レスポンスボディ
- 第3引数：レスポンスヘッダ
- 第4引数：HTTPステータス
- 第5引数：`WebRequest`

今回は、`HttpRequestMethodNotSupportedException`が発生したとき、`res`という名前のMapを定義して、それをレスポンスボディに入れるということを行っています。

実際のレスポンスボディの中身は以下のようになります。
```json
{
  "status":"406",
  "errorMessages":["Method not supported."]
}
```
試したい場合は以下のような簡単なコントローラを作成し、アプリケーションを起動後`localhost:8080`に`GET`でアクセスすれば処理を確認できます。

```SampleController.java
@RestController
public class SampleController {
	
	@PostMapping("/")
	public ResponseEntity<Object> sample() {
        return ResponseEntity.ok(null);
	}
}
```

# 自作の例外処理のハンドリング

先程までは既存の例外について見ていきましたが、次は自作した例外が発生したときの処理の書き方についてみていきます。

以下のような、ユーザが見つからなかった際に発生させる例外を定義したとします。

```UserNotFoundException.java
public class UserNotFoundException extends RuntimeException {
	private static final long serialVersionUID = 1L;
}
```

この例外が発生したときの処理を、先程と同じように定義していきます。
こちらにも`@RestControllerAdvice`が付与されている必要があります。

```CustomControllerAdvice.java
@RestControllerAdvice
public class CustomControllerAdvice extends ResponseEntityExceptionHandler {

	/**
	 * UserNotFoundException（自作クラス）のハンドリング.
	 * 404を返す
	 */
	@ExceptionHandler
	protected ResponseEntity<Object> handleUserNotFound(UserNotFoundException ex, WebRequest request) {

		Map<String, Object> res = Map.of("errorMessages", List.of("User not found."), "status", "400");
		return handleExceptionInternal(ex, res, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
	}
}
```

まず、メソッドには`@ExceptionHandler`を付与します。
メソッド名は任意で問題ありませんが、`handle〇〇`のように既存の例外処理のメソッド名と揃えるとわかりやすいと思います。

第1引数には自作した例外クラス、第2引数には`WebRequest`を入れるようにします。
今回も先程と同様、`res`という変数にレスポンスボディの値を定義しています。

定義したレスポンスボディは以下のようになります。

```json
{
  "status":"400",
  "errorMessages":["User not found."]
}
```

こちらも試したい場合は以下のようなコントローラを作成して`GET`で`localhost:8080`にアクセスすると、動作を確認できます。

```SampleController.java
@RestController
public class SampleController {
	
	@GetMapping("/")
	public ResponseEntity<Object> sample() {
		throw new UserNotFoundException();
	}
}
```


# 最後に

多くの箇所で発生しうる例外においては、各コントローラで処理するよりも今回のように共通化してしまったほうが楽な場面もあるかと思います。

よりよい記述方法などありましたら、コメントなどしていただけますと幸いです。