SpringBoot で共通エラーハンドリング 【@RestControllerAdvise】

## はじめに
ついつい、個人で開発していると正常系の処理ばかり書いてエラーハンドリングの知識が身につかないと感じたので
今回はSpringBootでエラーハンドリングの実装に挑戦してみた。

今回はモノシリックなアプリではなく、RestApi形式で例外が発生した際にはエラー情報を格納したレスポンスを返す。

## 環境
言語: Java SDK 1.8
FW: Springboot 2.5
IDE: Intellij Idea

## 実装

### 通常の処理を行うコントローラを実装

下記実装では `/{bloodType}` にリクエストを投げた際に `bloodType` を
メソッド `isBloodType` で血液型なのか判定しています。
もし血液型[ A, B, AB, O ]に当てはまらない場合、独自に実装したエラークラス `BadBloodTypeException` を呼ぶ。

```main.java

package com.example.demo.controller;

import com.example.demo.common.BadBloodTypeException;
import com.example.demo.model.Person;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RestController
public class SampleController {
    @GetMapping(value = "/{bloodType}")
    public ResponseEntity<Object> hello(@PathVariable String bloodType) {
        Person p = new Person();
        p.setFirstName("first");
        p.setMiddleName("middle");
        p.setLastName("last");
        p.setAge(24);
        boolean isBoodType = isBloodType(bloodType);

        if (isBoodType) {
            p.setBloodType(bloodType);
        }
        return ResponseEntity.status(HttpStatus.OK).body(p);
    }

    private boolean isBloodType(String bloodType) throws BadBloodTypeException{
        List<String> ageList = new ArrayList<String>(Arrays.asList("A","B","AB","O"));
        if (!ageList.contains(bloodType)) {
            throw new BadBloodTypeException("BadBloodTypeExceptionが発生しました");
        }
        return true;
    }
}

```

### BadBloodTypeException 実装部分
単純に非検査例外を拡張して引数に渡されたメッセージをセットするだけです。

```BadBloodTypeException.java
package com.example.demo.common;

public class BadBloodTypeException extends RuntimeException {
    public BadBloodTypeException(String message) {
        super(message);
    }
}
```

### RestControllerAdvice 実装部分

このクラスを実装することにより処理中に発生したエラークラスに応じて共通の処理を格納しておくことができます。
下記実装部分では先ほど紹介した独自のエラークラス `BadBloodTypeException` が処理中で発生した際に自動で呼び出される。

上記コントローラの処理を振り返るとURLパラメータで渡された血液型が[ A, B, AB, O ]に当てはまらない場合、独自に実装したエラークラス `BadBloodTypeException` を呼び出します。その際、自動的に下記メソッドが呼び出されます。

※バッドリクエスト ステータスとbodyにメッセージ「不正なリクエストです。」を返します。

```SampleControllerAdvise.java
package com.example.demo.controller;

import com.example.demo.common.BadBloodTypeException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class SampleControllerAdvise {

    @ExceptionHandler({BadBloodTypeException.class})  //  独自例外
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<Object> handleBadBloodTypeException(BadBloodTypeException e) {
        return new ResponseEntity<>("不正なリクエストです。", HttpStatus.BAD_REQUEST);
    }
}

```

## 画面上で確認

http://localhost:8080/A でアクセス

![スクリーンショット 2021-09-26 11.13.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496032/4c16b432-a66a-2daa-bada-308cb85f0b04.png)


http://localhost:8080/AAA でアクセス

![スクリーンショット 2021-09-26 11.15.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496032/2e200960-a372-86d2-11e2-51691775661c.png)