1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Spring Web MVC のエラーハンドリングのメモ

Posted at

Spring Web MVC のエラーハンドリング方法について調べたのでメモ

調べたバージョンは

  • Spring Boot 3.2.1
  • Spring Web MVC 6.1.2

例外をハンドリングできるところはいろいろあるかもしれないですが

  • @ExceptionHandler (Controller, ControllerAdviceに付与)
  • HandlerExceptionResolver
  • ErrorController

についてメモを残します。

HandlerExceptionResolver

説明の都合上先にこれから説明します。

HandlerExceptionResolver インターフェースを実装したクラスをBean定義をしておくことで例外のハンドリングを行うことができます。

resolveException を実装し、解決できた場合はModelAndViewを返し、未解決の場合はnullを返す実装を行います。

定義したBeanは DispatcherServletのhandlerExceptionResolversフィールドList<HandlerExceptionResolver> として保持されます。

initHandlerExceptionResolversメソッドで取得が行われます。
この時 AnnotationAwareOrderComparator によりソートが行われるのでOrderedインターフェースもしくは@Orderアノテーションを付与することで優先順位をつけることができます。
何も優先順位をつけていない場合は 数値が最大(つまり最後)となります。

Spring Bootで動かした場合デフォルトでは DefaultErrorAttributesHandlerExceptionResolverComposite が存在しています。

DefaultErrorAttributes は例外のハンドリングは行わず、後述のErrorController で利用できるように例外のインスタンスを退避します。

HandlerExceptionResolverComposite は内部にHandlerExceptionResolverの一覧を持っており後述の@ExceptionHandler をハンドリングしている ExceptionHandlerExceptionResolver などが存在します。

例外のハンドリングが行われるのはDispatcherServletのprocessHandlerExceptionメソッドでハンドリングが行われます。

意識しておいたほうがいいこと

なるべくOrderedをつけておいた方がいいかなと思います。

HandlerExceptionResolverCompositeDefaultHandlerExceptionResolver が一部の例外を解決するため、自作のHandlerExceptionResolverがこれよりも後の優先順位だと、すでに解決済みであるためresolveExceptionが呼ばれない場合があります。

他の注意点としてはハンドリング箇所を見れば明らかですが、サーブレットフィルターなどで例外が発生した場合はハンドリングが行われません。

ExceptionHandlerアノテーション

Controller@ControllerAdvice のクラスに@ExceptionHandler にハンドリング対象の例外を指定し、以下のような実装をすることで例外のハンドリングを行うことができます。
また未解決の場合はnullを返すようにします。

@ExceptionHandler({ MyException.class })
public Map<String, Object> errorHandling(Exception e) {
  return Map.of(
      
      "message", "例外です",

      "exceptionClass", e.getClass().getName(),

      "exceptionMessage", e.getMessage());
}

例外が起きた場合はExceptionHandlerExceptionResolverのgetExceptionHandlerMethodメソッドで対象のメソッドの特定が行われます。前半部分でControllerに付与したものからの特定、後半部分でControllerAdviceに付与したものからの特定が行われます。
その後対象メソッドの実行が行われます。

ExceptionHandlerExceptionResolverはHandlerExceptionResolverの実装クラスなので 同様にDispatcherServletのprocessHandlerExceptionから呼び出されます。

こちらも同様にHandlerExceptionResolverの仕組みを使っているためサーブレットフィルターなどで発生した例外はハンドリングできません。

ErrorController

Spring Bootで追加された仕組みです。

例外やsendErrorが発生したときに
warで起動しているときはErrorPageFilter、jarでTomcatで起動している時はStandardHostValveによって/error へリクエストがフォワードされます。

デフォルトだとBasicErrorControllerが使用されますが ErrorControllerの実装クラスをBean定義することで上書きが可能です。

実装方法の詳細は BasicErrorController を参考で ErrorAttributesなどから情報を取得しレスポンスを返します。

意識しておいたほうがいいこと

ErrorPageFilterStandardHostValve は一連の処理の手前の方で適用されるためサーブレットフィルターで発生した例外などもハンドリングすることが可能です。

/error への折り返しの際、ErrorControllerへ到達するまでに サーブレットフィルターやHandlerInterceptor を通過するため、場合によっては 以下のような適応対象外の対応が必要になるかもしれません。

(例えばHandlerInterceptorのなかで 何かのチェックを行っており、例外をスローしている場合などは /error でも同様のチェックが実行されて 再度例外がスローされてしまう可能性があります。)

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		//↓/errorの時は 適用しない
		registry.addInterceptor(new MyHandlerInterceptor()).excludePathPatterns("/error/**");
	}
}

また /error に折り返されているため HttpServletRequest などからパスを取得した際も元々のリクエストではなく /error となっているため注意が必要です。

元々のリクエストの情報を退避しておきたい場合は DefaultErrorAttributes を継承したクラスを作って退避しておくとよいかもしれないですね

まとめ

上記のポイントがよく使う箇所になるかなと思います。

それぞれ注意すべきポイントがあるので、それらを意識しながら使うとよいかなと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?