Help us understand the problem. What is going on with this article?

SpringMVCのHandlerExceptionResolverの適用順

More than 3 years have passed since last update.

久しぶりに例外周りの設定を見ていたら、ResponseStatusアノテーション付けたときの挙動でちょっとハマりました。
知っている人にはなんてことない話ですが、こういうのよく忘れるタチなので自分用にメモしておきます。

自分がよく使っている例外ハンドリングの復習

ExceptionHandlerアノテーション

Controller内にExceptionHandlerアノテーションを付与したメソッドを定義すると、
そのController内で指定の型の例外がthrowされた場合にハンドリングできる。

@ExceptionHandlerを設定したメソッド
@ExceptionHandler(Exception.class)
public String handleException(final Exception e) {
    return "/hoge/error";
}

HandlerExceptionResolver

これ実装したクラスを作成して

HanlderExceptionResolverを実装したクラス
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;
    }
}

設定しておくと

servlet-context.xml
    <beans:bean class="jp.hoge.fuga.web.handler.GlobalExceptionHandler"/>

前段で処理されてないthrowされた例外がハンドリングできる。

と、この2つの方法については、あまり深いこと考えずに使っていたわけです。

例外クラス自体にResponseStatusアノテーションを設定している場合

今回ちょいハマりしたのですが、独自例外にこういう設定ができますよね。

@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までは到達しなかったんですね

と理解ができました。

ドキュメントとかにも書いてあるのかもしれませんが、実際のソース見ると色々と納得感あるよね、と改めて思った次第です。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away