久しぶりに例外周りの設定を見ていたら、ResponseStatusアノテーション付けたときの挙動でちょっとハマりました。
知っている人にはなんてことない話ですが、こういうのよく忘れるタチなので自分用にメモしておきます。
自分がよく使っている例外ハンドリングの復習
ExceptionHandlerアノテーション
Controller内にExceptionHandlerアノテーションを付与したメソッドを定義すると、
そのController内で指定の型の例外がthrowされた場合にハンドリングできる。
@ExceptionHandler(Exception.class)
public String handleException(final Exception e) {
return "/hoge/error";
}
HandlerExceptionResolver
これ実装したクラスを作成して
package jp.hoge.fuga.web.handler;
public class GlobalExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(final HttpServletRequest request,
final HttpServletResponse response, final Object handler,
final Exception e) {
model.setViewName("/common/error");
return model;
}
}
設定しておくと
<beans:bean class="jp.hoge.fuga.web.handler.GlobalExceptionHandler"/>
前段で処理されてないthrowされた例外がハンドリングできる。
と、この2つの方法については、あまり深いこと考えずに使っていたわけです。
例外クラス自体にResponseStatusアノテーションを設定している場合
今回ちょいハマりしたのですが、独自例外にこういう設定ができますよね。
package jp.hoge.fuga.exception;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
今まで使ったことなかったんですが、これをthrowして指定したレスポンスコード(上記の場合は404)で
アプリケーションから応答返したい、という場合に使ったりするようです。
こうした独自例外をController内でthrowした場合、
- 例外の型が合っていれば、ExceptionHandlerアノテーション付きのメソッドでハンドリングできる
- 型に関わらず、独自にHandlerExceptionResolverを実装して設定したクラスではハンドリングできない
という挙動になったので、はて?となったのです。
...で、ここでやっと気付くわけですよ、
「ああ、mvc:annotation-driven指定してるから、デフォルトのHandlerExceptionResolverが設定されているんだよね。
それが作用してるんだよね」
と。
ただ、何がどういう順番で設定されているのかよく分かってなかったので、
デバッグがてら、デフォルトでどういった設定がされるのかを見てみました。
※ spring-webmvc-4.0.6.RELEASEで確認。
Spring MVCのデフォルトのHandlerExceptionResolverの設定
例外発生時に呼び出されるのは、
org.springframework.web.servlet.DispatcherServlet#processHandlerException
ここで
handlerExceptionResolvers
というListを持っていて、この中にHandlerExceptionResolver達が格納されており、
先頭から順に判定してどれを適用するか決めています。
格納順(=適用判定がされる順)は以下の通りでした。
- org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
→ ExceptionHandlerアノテーション付きの処理の適用を判定するHandlerExceptionResolver - org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
→ ResponseStatusアノテーションでステータスコードが設定されたものに適用するHandlerExceptionResolver - org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
→ Springで定義されている例外に適用するHandlerExceptionResolver - 独自にHandlerExceptionResolverを実装して設定したHandlerExceptionResolver
なるほど。
だからResponseStatusアノテーション付与した例外をthrowすると、独自HandlerExceptionResolverまでは到達しなかったんですね
と理解ができました。
ドキュメントとかにも書いてあるのかもしれませんが、実際のソース見ると色々と納得感あるよね、と改めて思った次第です。