Spring MVC(+ Spring Boot) における404時の動き

結局は404用のページを表示しようとするが、設定により処理の流れや発生する例外が変わる(変えることができる)のでまとめた。細かい例外処理のハンドリングを確認したり考たりする場合の参考にして下さい。

執筆時バージョン
Java

Java SE 8

静的リソースのからみがあるので、404になる場合の動作が設定により変わる。結局は404ページを表示しようとするが、設定される例外が異なったりする。

404用のページは「resources/templates/error/404.html」あるいは「resources/public/error/404.html」などを設定ことでデフォルトから変えることができる。

動作確認のバージョン
  • Spring Boot 1.4.0

  • Spring Framework 4.3.2

デフォルト設定の場合

設定を変えていない場合はResourceHttpRequestHandlerのマッピングに引っかかる。ResourceHttpRequestHandlerはstaticフォルダ等にある静的リソースにアクセスするためのハンドラで、「/**」にマッピングされる。これによりハンドラが見つからない状況に陥ることがない。

「/**」にマッピングされることを確認できるログの一部
2016-08-05 09:59:14.790  INFO 5144 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

「/**」でマッピングされるのでどんなURLでも引っかかることになる。ただ引っかかったとしてもリソースがない場合は、ResourceHttpRequestHandlerがHttpServletResponse#sendErrorを404で設定する。そうなるとステータス=404、例外=nullの状態でエラーコントローラへ遷移して、404用のページを表示する。

デフォルト設定時の404処理の流れ
  1. DispatcherServlet#doService()が呼び出され、処理を開始。(DispatcherServlet.java:862行目)

  2. DispatcherServlet#doDispatch()でHandlerを検索。ResourceHttpRequestHandlerが割り当てられる。(DispatcherServlet.java:936行目)

  3. ResourceHttpRequestHandlerは指定URLのリソースを検索するが、見つからない。見つからないのでHttpServletResponse#sendError()を404で設定して処理を終了する。(ResourceHttpRequestHandler.java:320-325行目)

  4. JavaEEコンテナはresponseにエラーステータスが設定されているので、デフォルトのエラーページ(/error)に遷移させる。

  5. デフォルトのエラーページにマッピングされたBasicErrorControllerが呼ばれる。

  6. BasicErrorControllerは404用の表示を設定する。RESTの場合はResponseEntityを設定する。(BasicErrorController.java:85行目)

静的リソースマッピングをしない場合

静的リソースマッピングは設定で解除・変更することができる。APIのみ提供のときなどに役に立つ設定。

spring:
  resources:
    add-mappings: false

静的リソースマッピングが外される場合は「/**」のマッピングがはずれるので、ハンドラが見つからないことになる。このときDispatcher#doDispatch()がHttpServletResponse#sendError()を404で設定する。その後の挙動はデフォルト設定の場合と同じく、ステータス=404、例外=nullの状況でエラーコントローラへ遷移して、404用のページを表示する。

静的リソース解除時の404処理の流れ
  1. DispatcherServlet#doService()が呼び出され、処理を開始。(DispatcherServlet.java:862行目)

  2. DispatcherServlet#doDispatch()でHandlerを検索するが見つからない。HandlerがないのでDispatcherServletは、HttpServletResponse#sendErrorを404で設定して処理を終了する。(DispatcherServlet.java:1155行目)

  3. JavaEEコンテナはresponseにエラーステータスが設定されているので、デフォルトのエラーページ(/error)に遷移させる。

  4. デフォルトのエラーページにマッピングされたBasicErrorControllerが呼ばれる。

  5. BasicErrorControllerは404用の表示を設定する。RESTの場合はResponseEntityを設定する。(BasicErrorController.java:85行目)

静的リソースマッピング解除 + NoHandlerFoundExceptionを投げる場合

ハンドラが見つからない場合に、NoHandlerFoundExceptionを投げる設定をすることができる。「/**」のマッピングがあるとハンドラは必ず見つかることになるので、静的リソースマッピングの変更とセットで設定することになる。

spring:
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false

静的リソースマッピングが外される場合は「/**」のマッピングがはずれるのでハンドラが見つからないことになる。このとき設定に従い、Dispatcher#doDispatchがNoHandlerFoundExceptionを投げる。このあとは通常の例外ハンドリングになっていく。DefaultHandlerExceptionResolverがステータスを設定し、エラーページへ遷移。ステータス=404、例外=NoHandlerFoundExceptionでエラーコントローラへ遷移して、404用のページを表示する。

静的リソースマッピング解除 + NoHandlerFoundExceptionを投げる場合の404処理の流れ
  1. DispatcherServlet#doService()が呼び出され、処理を開始。(DispatcherServlet.java:862行目)

  2. DispatcherServlet#doDispatch()でHandlerを検索するが見つからない。Handlerがない場合の設定により、NoHandlerFoundExceptionを投げる。(DispatcherServlet.java:1150-1151行目)

  3. 通常の例外ハンドリングになり、DefaultHandlerExceptionResolverが適用される。(DispatcherServlet.java:1187行目)

  4. DefaultHandlerExceptionResolverはNoHandlerFoundExceptionに対して、HttpServletResponse#sendError()を404で設定する。(DefaultHandlerExceptionResolver.java:477行目)

  5. JavaEEコンテナはresponseにエラーステータスが設定されているので、デフォルトのエラーページ(/error)に遷移させる。

  6. デフォルトのエラーページにマッピングされたBasicErrorControllerが呼ばれる。

  7. BasicErrorControllerは404用の表示を設定する。RESTの場合はResponseEntityを設定する。(BasicErrorController.java:85行目)

Appendix B: 改訂履歴

  • v1.0, 2016-08-06: 初稿