Table of Contents
「Goslings開発メモ - その2: Spring Boot続編 (DI)」の続き。
Spring Boot続続編で、例外処理について。
Spring MVCアプリにおける例外処理
Goslingsは前々回書いたようにspring-boot-starter-web
というスターターを使っていて、つまりSpring MVCアプリだ。
Spring MVCアプリにおける例外処理についてはちょっと古いがこの記事に詳しい。
まず、Goslingsの構成で例外処理を何も書かなかった場合、コントローラのリクエストハンドラから例外が投げられると、ログにスタックトレースが出力され、クライアントにはHTTPステータスコード500 (Internal Server Error)
とともに以下の様なデフォルトのエラーページが返る。
なんだかこれでも十分な気がするが、実際にはちゃんと明示的に例外処理をしたほうがいいだろう。 エラー時に返すHTTPステータスコードをカスタマイズしたり、遷移するページを変えたりしたくなるだろうから。
記事によれば、リクエストハンドラ内で例外をキャッチして処理するのはイケてなくて、関心事の分離のために別の場所に処理を書くのが良いらしい。
Spring MVCアプリにおける例外処理には以下の3つの段階がある。
- 投げる例外をカスタマイズする
- 例外クラス毎の例外ハンドラをコントローラに実装する
- コントローラ間で共用する例外ハンドラクラスを作る
以下それぞれについて書く。
1. 投げる例外をカスタマイズする
リクエストハンドラから投げる例外に@ResponseStatus
をつけることで、クライアントに返すHTTPステータスコード(とリーズンフレーズ)をカスタマイズできる。
例えば以下のような例外を投げると、HTTPステータスコード500 (Internal Server Error)
の代わりに400 (Bad Request)
がクライアントに返る。
@ResponseStatus(HttpStatus.BAD_REQUEST)
public final class BadRequestException extends RuntimeException {
// 省略
}
2. 例外クラス毎の例外ハンドラをコントローラに実装する
コントローラのメソッドに@ExceptionHandler
をつけてやると、そのメソッドは例外ハンドラになり、そのコントローラのリクエストハンドラから特定の例外が投げられたときの処理を書くことができる。
さらに例外ハンドラに@ResponseStatus
をつければ、HTTPステータスコードをカスタマイズできる。
例外ハンドラの戻り値はリクエストハンドラのと同様に処理されるので、遷移するページ等も自由にカスタマイズできる。
Goslingsでは、上記BadRequestException
からは@ResponseStatus
を削除したうえで、RestApiV1Controller
に以下の様に例外ハンドラを書いた。
public final class RestApiV1Controller {
// 例外ハンドラ
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BadRequestException.class)
ErrorInfo handleBadRequestException(HttpServletRequest req, Exception ex) {
return new ErrorInfo(req.getRequestURL().toString(), ex);
}
}
(RestApiV1Controller.javaの完全なソースはこちら)
こう書くと、RestApiV1Controller
の任意のリクエストハンドラからBadRequestException
が投げられると、handleBadRequestException
が呼び出され、HTTPステータスコード400 (Bad Request)
とともにクライアントにHTTPレスポンスが返る。
RestApiV1Controller
はREST APIコントローラなので、このHTTPレスポンスのボディは、handleBadRequestException
の戻り値であるErrorInfo
オブジェクトをJSONに変換したものになる。
例外ハンドラの仮引数は、上のコードに書いたもののほか、サーブレット関係のクラスなど(e.g. HttpServletResponse
やHttpSession
。詳しくはJavadoc参照)を適当に書いておくとSpring MVCがよしなに渡してくれる。
冒頭に貼った記事には例外ハンドラはModel
を受け取れないとあるが、これは古い情報で、今は受け取れるっぽい。
3. コントローラ間で共用する例外ハンドラクラスを作る
コントローラから例外処理を完全に分離したい場合や、複数のコントローラで例外ハンドラを共有したい場合は、コントローラアドバイスクラスを書けばいい。
コントローラアドバイスクラスは@ControllerAdvice
を付けて定義したクラスで、このクラスに例外ハンドラを書いておくと複数のコントローラで有効になる。
コントローラアドバイスクラスには例外ハンドラ以外も書ける。
コントローラアドバイスクラスが適用されるのはデフォルトでは全てのコントローラクラスだが、@ControllerAdvice
の値により適用範囲を絞ることもできる。
詳しくはJavadoc参照。
Goslingsではコントローラアドバイスクラスは作らなかった。
今日はここまで。 次回もまたSpring Bootで、ロギングについて。