SpringBootの@RestControllerで例外処理をする

## 概要
SpringBootで@RestControllerを使っているときに、例外処理をする

## やってみる
```java
package com.example.sample;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
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 HelloExceptionHandler extends ResponseEntityExceptionHandler {

    // 自分で定義したMyExceptionをキャッチする
    @ExceptionHandler(MyException.class)
    public ResponseEntity<Object> handleMyException(MyException ex, WebRequest request) {
        return super.handleExceptionInternal(ex, "handleMyException", null, HttpStatus.BAD_REQUEST, request);
    }

    // SpringBoot内で用意されている例外については、ResponseEntityExceptionHandlerクラスで例外ごとに
    // 専用のメソッドが用意されているのでそれをオーバーライドする
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return super.handleExceptionInternal(ex, "MethodArgumentNotValidException", null, HttpStatus.INTERNAL_SERVER_ERROR, request);
    }

    // すべての例外をキャッチする
    // どこにもキャッチされなかったらこれが呼ばれる
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleAllException(Exception ex, WebRequest request) {
        return super.handleExceptionInternal(ex, "handleAllException", null, HttpStatus.INTERNAL_SERVER_ERROR, request);
    }

    // すべてのハンドリングに共通する処理を挟みたい場合はオーバーライドする
    // ex) ログを吐くなど
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {

        // 任意の処理

        return super.handleExceptionInternal(ex, body, headers, status, request);
    }
}
```

以下のコントローラを書いて試してみる

```java
package com.example.sample;

import org.springframework.web.bind.annotation.*;
import org.springframework.http.converter.HttpMessageNotReadableException;

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/my")
    public String get() {
        throw new MyException();
    }

    @RequestMapping("/message")
    public String get() {
        throw new HttpMessageNotReadableException("hoge");
    }

    @RequestMapping("/all")
    public String get() {
        throw new RuntimeException();
    }
}
```

`/hello/my` にリクエストがきたらMyExceptionを投げられる。
うまくいっていれば、handleMyExceptionが呼ばれるはず

`/hello/all`にリクエストがきたらRuntimeExceptionが投げられる。
RuntimeExceptionはハンドリングしてないので、handleAllExceptionが呼ばれるはず

```bash
$ curl "http://localhost:8080/hello/my"
handleMyException
$ curl "http://localhost:8080/hello/message"
HttpMessageNotReadableException
$ curl "http://localhost:8080/hello/all"
handleAllException
```

### 解説
例外ハンドリング用のクラスを作る際は、`ResponseEntityExceptionHandler`クラスを継承して`@RestControllerAdvice`アノテーションをつける。
実際に例外をハンドリングするメソッドを作る際は、`@ExceptionHandler(例外クラス.class)`アノテーションをつけてあげると、その例外が投げられたときに呼ばれるようになる。引数でその例外オブジェクトを型に指定する。
なので、ハンドルしたい例外ごとにメソッドをつくってハンドリングするのと、未知の例外が投げられたときに備えて最後の砦としてExceptionクラスをキャッチするメソッド(上記のコードでの`handleAllException`メソッド)を用意するのがよさそう。
例外処理全てに共通して行いたい処理(ex: ログを吐く等)を入れる場合は、`handleExceptionInternal`メソッドをオーバーライドするとよい。

#### 補足
Springが用意している例外については、継承している`ResponseEntityExceptionHandler`クラス内でそれぞれハンドリングする専用のメソッドが定義されている。ただ、おそらくオーバーライドされる前提のもので、そのままではレスポンス空っぽで返すようになっているのでオーバーライドして使う。(上記のコードでいう`handleMethodArgumentNotValid`メソッド)

用意されているメソッドは以下

|例外クラス|ハンドリングするメソッド|
|---|---|
|HttpRequestMethodNotSupportedException|handleHttpRequestMethodNotSupported|
|HttpMediaTypeNotSupportedException|handleHttpMediaTypeNotSupported|
|HttpMediaTypeNotAcceptableException|handleHttpMediaTypeNotAcceptable|
|MissingPathVariableException|handleMissingPathVariable|
|MissingServletRequestParameterException|handleMissingServletRequestParameter|
|ServletRequestBindingException|handleServletRequestBindingException|
|ConversionNotSupportedException|handleConversionNotSupported|
|TypeMismatchException|handleTypeMismatch|
|HttpMessageNotReadableException|handleHttpMessageNotReadable|
|HttpMessageNotWritableException|handleHttpMessageNotWritable|
|MethodArgumentNotValidException|handleMethodArgumentNotValid|
|MissingServletRequestPartException|handleMissingServletRequestPart|
|BindException|handleBindException|
|NoHandlerFoundException|handleNoHandlerFoundException|
|AsyncRequestTimeoutException|handleAsyncRequestTimeoutException|
## 参考
- Spring徹底入門 株式会社NTTデータ 2016