4.3. 例外ハンドリング


本ガイドラインで作成する、Webアプリケーションの例外ハンドリング指針について説明する。


4.3.1. Overview

本節では、Spring MVC配下の処理で発生する例外のハンドリングについて説明する。説明対象は、以下の通りである。

description target

図-説明対象

  1. 例外の分類

  2. 例外のハンドリング方法


4.3.1.1. 例外の分類

アプリケーション実行時に発生する例外は、以下3つに分類される。

表-アプリケーション実行時に発生する例外の分類

項番

分類

説明

例外の種類

(1)
オペレータの再操作(入力値の変更など)によって、発生原因が解消できる例外
オペレータの再操作によって、発生原因が解消できる例外は、アプリケーションコードで例外をハンドリングし、例外処理を行う。
(2)
オペレータの再操作によって、発生原因が解消できない例外
オペレータの再操作によって、発生原因が解消できない例外は、フレームワークで例外をハンドリングし、例外処理を行う。
(3)
クライアントからの不正リクエストにより発生する例外
クライアントからの不正リクエストにより発生する例外は、フレームワークで例外をハンドリングし、例外処理を行う。

Note

誰が、例外を意識する必要があるのか?

  • (1)はアプリケーション開発者が意識する例外となる。

  • (2)と(3)はアプリケーションアーキテクトが意識する例外となる。


4.3.1.2. 例外のハンドリング方法

アプリケーション実行時に発生する例外は、以下4つの方法でハンドリングを行う。
ハンドリング方法毎のハンドリングフローの詳細は、例外ハンドリングの基本フローを参照されたい。
表-例外のハンドリング方法

項番

ハンドリング方法

説明

例外ハンドリングのパターン

(1)
アプリケーションコードにて、try-catchを使い、例外ハンドリングを行う。
リクエスト(Controllerのメソッド)単位に、例外をハンドリングする場合に使用する。
(2)
@ExceptionHandlerアノテーションを使い、アプリケーションコードで例外ハンドリングを行う。
ユースケース(Controller)単位に、例外をハンドリングする場合に使用する。
(3)
フレームワークから提供されているHandlerExceptionResolverの仕組みを使い、例外ハンドリングを行う。
サーブレット単位に、例外をハンドリングする場合に使用する。
HandlerExceptionResolverは、WebMvcConfigurerインタフェースを実装(もしくは<mvc:annotation-driven>を指定)した際に、自動的に登録されるクラスと共通ライブラリから提供しているSystemExceptionResolverを使用する。
(4)
サーブレットコンテナのerror-page機能を使い、例外ハンドリングを行う。
Spring MVC管理外で発生する例外をハンドリングする場合に使用する。
handling method

図-例外のハンドリング方法

Note

誰が例外ハンドリングを行うのか?

  • (1)と(2)はアプリケーション開発者が設計・実装する。

  • (3)と(4)はアプリケーションアーキテクトが設計・設定する。

Note

自動的に登録されるHandlerExceptionResolverについて

WebMvcConfigurerインタフェースを実装(もしくは<mvc:annotation-driven>を指定)した際に、自動的に登録されるHandlerExceptionResolverの役割は、以下の通りである。

優先順は、以下の並び順の通りとなる。

項番

クラス(優先順位)

役割

(1)
ExceptionHandlerExceptionResolver
(order=0)
@ExceptionHandlerアノテーションが付与されているControllerクラスのメソッドを呼び出し、例外ハンドリングを行うためのクラス。
No.2のハンドリング方法を実現するために必要なクラス。
(2)
ResponseStatusExceptionResolver
(order=1)
クラスアノテーションとして、@ResponseStatusが付与されている例外をハンドリングするためのクラス。
@ResponseStatusに指定されている値で、HttpServletResponse#sendError(int sc, String msg)が呼び出される。
(3)
DefaultHandlerExceptionResolver
(order=2)
Spring MVC内で発生するフレームワーク例外を、ハンドリングするためのクラス。
フレームワーク例外に対応するHTTPレスポンスコードの値で、HttpServletResponse#sendError(int sc)が呼び出される。
設定されるHTTPレスポンスコードの詳細は、DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについてを参照されたい。

Note

共通ライブラリから提供している SystemExceptionResolver の役割は?

WebMvcConfigurerインタフェースを実装(もしくは<mvc:annotation-driven>を指定)した際に、自動的に登録されるHandlerExceptionResolverによって、ハンドリングされない例外をハンドリングするためのクラスである。

そのため優先順は、DefaultHandlerExceptionResolverの後になるように設定する。

Note

Spring Framework 3.2 より追加された@ControllerAdviceアノテーションについて

@ControllerAdviceの登場により、サーブレット単位で、@ExceptionHandlerを使った例外ハンドリングを行えるようになった。

@ControllerAdviceアノテーションが付与されたクラスで、@ExceptionHandlerアノテーションを付与したメソッドを定義すると、サーブレット内のすべてのControllerに適用される。

以前のバージョンで同じことを実現する場合、@ExceptionHandlerアノテーションが付与されたメソッドを、Controllerのベースクラスのメソッドとして定義し、各Controllerでベースクラスを継承する必要があった。

Spring Framework 4.0 より追加された@ControllerAdviceアノテーションの属性について

@ControllerAdviceアノテーションの属性を指定することで、@ControllerAdviceが付与されたクラスで実装したメソッドを適用するControllerを柔軟に指定できるように改善されている。

属性の詳細については、@ControllerAdviceの属性を参照されたい。

Note

@ControllerAdviceアノテーションの使いどころ

  1. サーブレット単位で行う例外ハンドリングに対して、View名と、HTTPレスポンスコードの解決以外の処理が必要な場合。
    (View名とHTTPレスポンスコードの解決のみでよい場合は、SystemExceptionResolverで対応できる )
  2. サーブレット単位で行う例外ハンドリングに対して、エラー応答用のレスポンスデータをJSPやThymeleafなどのテンプレートエンジンを使わずに、エラー用のモデル(JavaBeans)を、JSONやXML形式にシリアライズして生成したい場合
    (AJAXや、REST用のControllerを作成する際の、エラーハンドリングとして使用する)。

4.3.2. Detail

  1. 例外の種類

  2. 例外ハンドリングのパターン

  3. 例外ハンドリングの基本フロー


4.3.2.1. 例外の種類

アプリケーション実行時に発生する例外は、以下6種類に分類される。

表-例外の種類

項番

例外の種類

説明

(1)
ビジネスルールの違反を検知したことを通知する例外
(2)
フレームワーク、およびライブラリ内で発生する例外のうち、システムが、正常稼働している時に発生する可能性のある例外
(3)
システムが、正常稼働している時に、発生してはいけない状態を検知したことを通知する例外
(4)
システムが、正常稼働している時には発生しない非検査例外
(5)
システム(アプリケーション)全体に影響を及ぼす、致命的な問題が発生していることを通知するエラー
(6)
フレームワークが、リクエスト内容の不正を検知したことを通知する例外

4.3.2.1.1. ビジネス例外

ビジネスルールの違反を検知したことを通知する例外
本例外は、ドメイン層のロジック内で発生させる。
アプリケーションとして想定される状態なので、システム運用者による対処は、不要である。
  • 旅行を予約する際に予約日が期限を過ぎている場合

  • 商品を注文する際に在庫切れの場合

  • etc …

Note

該当する例外クラス

  • org.terasoluna.gfw.common.exception.BusinessException (共通ライブラリから提供しているクラス)。

  • 細かくハンドリングする必要がある場合は、BusinessExceptionを継承した例外クラスを作成すること。

  • 共通ライブラリで用意しているビジネス例外クラスで、要件を満たせない場合は、プロジェクト毎にビジネス例外クラスを作成すること。


4.3.2.1.2. 正常稼働時に発生するライブラリ例外

フレームワーク、およびライブラリ内で発生する例外のうち、システムが、正常稼働している時に発生する可能性のある例外。
フレームワーク、およびライブラリ内で発生する例外とは、Spring Frameworkや、その他のライブラリ内で発生する例外クラスを対象とする。
アプリケーションとして想定される状態なので、システム運用者による対処は、不要である。
  • 複数のオペレータによって、同じデータを同時に更新しようとした場合に、発生する楽観排他例外や、悲観排他例外。

  • 複数のオペレータによって、同じデータを同時に登録しようとした場合に、発生する一意制約違反例外。

  • etc …

Note

該当する例外クラスの例

  • org.springframework.dao.OptimisticLockingFailureException (楽観排他でエラーが発生した場合に発生する例外)。

  • org.springframework.dao.PessimisticLockingFailureException (悲観排他でエラーが発生した場合に発生する例外)。

  • org.springframework.dao.DuplicateKeyException (一意制約違反となった場合に発生する例外)。

  • etc …


4.3.2.1.3. システム例外

システムが、正常稼働している時に、発生してはいけない状態を検知したことを通知する例外
本例外は、アプリケーション層、およびドメイン層のロジックで発生させる。
システム運用者による対処が必要となる。
  • 事前に存在しているはずのマスタデータ、ディレクトリ、ファイルなどが存在しない場合。

  • フレームワーク、ライブラリ内で発生する検査例外のうち、システム異常に分類される例外を捕捉した場合(ファイル操作時のIOExceptionなど)。

  • etc …

Note

該当する例外クラス

  • org.terasoluna.gfw.common.exception.SystemException (共通ライブラリから提供しているクラス)。

  • 遷移先のエラー画面や、HTTPレスポンスコードを細かく分ける場合は、SystemExceptionを継承した例外クラスを作成すること。

  • 共通ライブラリで用意しているシステム例外クラスだと要件を満たせない場合は、プロジェクト毎にシステム例外クラスを作成すること。


4.3.2.1.4. 予期しないシステム例外

システムが、正常稼働している時には発生しない非検査例外。
システム運用者による対処、またはシステム開発者による解析が必要となる。
予期しないシステム例外は、アプリケーションコードでハンドリング(try-catch)すべきでない。
  • アプリケーション、フレームワーク、ライブラリにバグが潜んでいる場合。

  • DBサーバなどがダウンしている場合。

  • etc …

Note

該当する例外クラスの例

  • java.lang.NullPointerException(バグ起因で発生する例外)。

  • org.springframework.dao.DataAccessResourceFailureException(DBサーバがダウンしている場合に発生する例外)。

  • etc …


4.3.2.1.5. java.lang.Errorを継承しているエラー

システム(アプリケーション)全体に影響を及ぼす、致命的な問題が発生している事を通知するエラー
システム運用者、またはシステム開発者による対処・リカバリが必要となる。
java.lang.Errorを継承しているエラーは、基本的にアプリケーションコードでハンドリング(try-catch)してはいけない。
  • Java仮想マシンで使用できるメモリが不足している場合。

  • etc …

Note

該当するエラークラスの例

  • java.lang.OutOfMemoryError(メモリ不足時に発生するエラー)。

  • etc …


4.3.2.1.6. リクエスト不正時に発生するフレームワーク例外

フレームワークが、リクエスト内容の不正を検知したことを通知する例外
本例外は、フレームワーク(Spring MVC)内で発生する。
原因は、クライアント側に存在するため、システム運用者による対処は、不要である。
  • POSTメソッドのみ許容しているリクエストパスに対して、GETメソッドでアクセスした場合に発生する例外。

  • @PathVariableアノテーションを使って、URIから値を抽出する際に、URIに型変換できない値が、指定された場合に発生する例外。

  • etc …

Note

該当する例外クラスの例

  • org.springframework.web.HttpRequestMethodNotSupportedException(サポート外のメソッドでアクセスされた場合に発生する例外)。

  • org.springframework.beans.TypeMismatchException(URIに型変換できない値が指定された場合に発生する例外)。

  • etc …

DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについての中の、HTTPステータスコードが「4XX」の例外が該当するクラス。


4.3.2.2. 例外ハンドリングのパターン

例外ハンドリングは、目的に応じて、以下5種類のパターンに分類される。
(1)-(2)はユースケース毎、(3)-(5)はシステム(アプリケーション)全体でハンドリングを行う。
表-例外ハンドリングのパターン

項番

ハンドリングの目的

ハンドリング対象となり得る例外

ハンドリング方法

ハンドリング単位

(1)
アプリケーションコード
(try-catch)
リクエスト
(2)
アプリケーションコード
(@ExceptionHandler)
ユースケース
(3)
フレームワーク
(ハンドリングルールを、spring-mvc.xmlに指定する)
サーブレット
(4)
フレームワーク
サーブレット
(5)
1. プレゼンテーション層で発生する全ての例外及びエラー
サーブレットコンテナ
(ハンドリングルールを、web.xmlに指定する)
Webアプリケーション

4.3.2.2.1. ユースケースの一部やり直し(途中からのやり直し)を促す場合

ユースケースの一部やり直し(途中からのやり直し)を促す場合は、Controllerクラスのアプリケーションコードで捕捉(try-catch)し、リクエスト単位で例外処理を行う。

Note

ユースケースの一部やり直しを促す場合の例

  • ショッピングサイトで注文処理を行った際に、在庫不足を通知するビジネス例外が発生する場合。
    このケースの場合、個数を減らせば注文処理が行えるため、個数が変更できる画面に遷移し、個数変更を促すメッセージを表示する。
  • etc …

class of exception handling for again from middle

図-ユースケースの一部やり直し(途中からのやり直し)を促す場合のハンドリング方法


4.3.2.2.2. ユースケースのやり直し(先頭からのやり直し)を促す場合

ユースケースのやり直し(先頭からのやり直し)を促す場合は、@ExceptionHandlerを使って捕捉し、ユースケース単位で例外処理を行う。

Note

ユースケースのやり直し(先頭からのやり直し)を促す場合の例

  • ショッピングサイト(管理者向けサイト)で商品マスタの変更を行った際に、変更対象の商品マスタが他のオペレータによって変更されていた場合(楽観排他例外が発生した場合)。
    このケースの場合、他のユーザが行った変更内容を確認してから操作してもらう必要があるため、ユースケースの先頭画面(例えば商品マスタの検索画面)に遷移し、再操作を促すメッセージを表示する。
  • etc …

class of exception handling for again from first

図-ユースケースのやり直し(先頭からのやり直し)を促す場合のハンドリング方法


4.3.2.2.3. システム、またはアプリケーションが、正常な状態でない事を通知する場合

システム、またはアプリケーションが、正常な状態でないことを通知する例外を検知する場合は、SystemExceptionResolverで捕捉し、サーブレット単位で例外処理を行う。
サーブレットで発生したjava.lang.Errorを継承しているエラーオブジェクトもSystemExceptionResolverで捕捉される。

Note

システム、またはアプリケーションが正常な状態でないことを通知する場合の例

  • 外部システムとの接続を行うユースケースにて、外部システムが、閉塞中であることを通知する例外が発生した場合。
    このケースの場合、外部システムが開局するまで実行できないため、エラー画面に遷移し、外部システムが開局するまでユースケースが実行できない旨を通知する。
  • アプリケーションで指定した値を、条件にマスタ情報の検索を行った際に、該当するマスタ情報が存在しない場合。
    このケースの場合、マスタメンテナンス機能のバグ又はシステム運用者によるデータ投入ミス(リリースミス)の可能性があるため、システムエラー画面に遷移し、システム異常が発生した旨を通知する。
  • ファイル操作時にAPIからIOExceptionが発生した場合。
    このケースの場合、ディスク異常などが考えられるため、システムエラー画面に遷移し、システム異常が発生した旨を通知する。
  • etc …

class of exception handling for system error

図-システム、またはアプリケーションが、正常な状態でないことを通知する例外を検知する場合のハンドリング方法

Note

SystemExceptionResolverで捕捉せずにサーブレットコンテナで捕捉させたい場合

SystemExceptionResolverのexcludedExceptionsに例外クラスを設定することで、SystemExceptionResolverで捕捉させたくない例外を除外することができる。

この設定により、サーブレットコンテナに例外を通知できるようになるが、サーブレットで発生した例外はServletExceptionIOException(もしくはそのサブクラス)のみが通知可能であり、それ以外の例外についてはServletExceptionにネストされて通知さてしまう。

SystemExceptionResolverではネストされている例外を見ていないため、SystemExceptionResolverの除外対象としたい場合は、発生した例外をServletExceptionを継承した別クラスにラッピングして再スローする必要がある点に注意されたい。


4.3.2.2.4. リクエスト内容が、不正であることを通知する場合

フレームワークによって、検知されたリクエスト不正を通知する場合は、DefaultHandlerExceptionResolverで捕捉し、サーブレット単位で例外処理を行う。

Note

リクエスト内容が不正であることを通知する場合の例

  • POSTメソッドのみ許可されているURIで、GETメソッドを使ってアクセスした場合。
    このケースの場合、ブラウザのお気に入り機能などを使って直接アクセスしている事が考えられるため、エラー画面に遷移し、リクエスト内容が不正であることを通知する。
  • @PathVariableアノテーションを使ってURIから値を抽出する際に、URIから値を抽出できなかった場合。
    このケースの場合、ブラウザのアドレスバーの値を書き換えて、直接アクセスしている事が考えられるため、エラー画面に遷移し、リクエスト内容が不正であることを通知する。
  • etc …

class of exception handling for request error

図-リクエスト内容が不正であることを通知する場合のハンドリング方法


4.3.2.2.5. プレゼンテーション層(JSP/Thymeleafなど)で、例外が発生したことを通知する場合

プレゼンテーション層(JSP/Thymeleafなど)で、例外が発生したことを通知する場合、サーブレットコンテナで捕捉し、Webアプリケーション単位で例外処理を行う。

class of exception handling for fatal error

図-プレゼンテーション層(JSP/Thymeleafなど)で例外が発生した事を通知する場合のハンドリング方法


4.3.2.3. 例外ハンドリングの基本フロー

例外処理の基本フローを示す。
共通ライブラリから提供しているクラスの概要については、共通ライブラリから提供している例外ハンドリング用のクラスについてを参照されたい。
アプリケーションコードで行う処理(実装が必要な処理)についての説明は、太字で表現している。
例外メッセージ、およびスタックトレースのログ出力は、共通ライブラリから提供しているクラス(FilterやInterceptorクラス)で行う。
例外メッセージ、およびスタックトレース以外の情報を、ログ出力する必要がある場合は、各ロジックで個別にログを出力すること。
例外ハンドリングのフロー説明であるため、Serviceクラスを呼び出すまでのフローに関する説明は、省略する。
  1. リクエスト単位でControllerクラスがハンドリングする場合の基本フロー

  2. ユースケース単位でControllerクラスがハンドリングする場合の基本フロー

  3. サーブレット単位でフレームワークがハンドリングする場合の基本フロー

  4. Webアプリケーション単位でサーブレットコンテナがハンドリングする場合の基本フロー


4.3.2.3.1. リクエスト単位でControllerクラスがハンドリングする場合の基本フロー

例外をリクエスト単位でハンドリングする場合、Controllerクラスのアプリケーションコードで捕捉(try-catch)し、例外処理を行う。
基本フローは、以下の通りである。
下記の図は、 共通ライブラリから提供しているビジネス例外(org.terasoluna.gfw.common.exception.BusinessException)をハンドリングする場合の基本フローである。
ログは、結果メッセージを保持している例外が発生したことを記録するインタセプタ(org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor)を使用して、出力する。
flow of exception handling using catch

図-リクエスト単位でControllerクラスがハンドリングする場合の基本フロー

  1. Serviceクラスにて、 BusinessExceptionを生成し、スローする。

  2. ResultMessagesLoggingInterceptorは、 ExceptionLoggerを呼び出し、warnレベルのログ(監視ログとアプリケーションログ)を出力する。
    ResultMessagesLoggingInterceptorはResultMessagesNotificationExceptionのサブ例外(BusinessException/ResourceNotFoundException)が発生した場合のみ、ログを出力するクラスである。
  3. Controllerクラスは、 BusinessExceptionを捕捉し、 BusinessExceptionに設定されているメッセージ情報(ResultMessage)を画面表示用にModelに設定する(6’)。

  4. Controllerクラスは、遷移先のView名を返却する。

  5. DispatcherServletは、返却されたView名に対応するJSPを呼び出す。

  6. JSPは、MessagesPanelTagを使用して、メッセージ情報(ResultMessage)を取得し、メッセージ表示用のHTMLコードを生成する。

  7. JSPで生成されたレスポンスが表示される。


4.3.2.3.2. ユースケース単位でControllerクラスがハンドリングする場合の基本フロー

例外をユースケース単位でハンドリングする場合、Controllerクラスの@ExceptionHandlerを使って捕捉し、例外処理を行う。
基本フローは、以下の通りである。
下記の図は、 任意の例外(XxxException)をハンドリングする場合の、基本フローである。
ログは、HandlerExceptionResolverによって、例外ハンドリングすることを記録するインタセプタ(org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor)を使用して、出力する。
flow of exception handling using annotation

図-ユースケース単位で、Controllerクラスがハンドリングする場合の基本フロー

  1. Controllerクラスから呼び出されたServiceクラスにて、例外(XxxException)が発生する。

  2. DispatcherServletは、XxxExceptionを捕捉し、ExceptionHandlerExceptionResolverを呼び出す。

  3. ExceptionHandlerExceptionResolverは、Controllerクラスに用意されている例外ハンドリングメソッドを呼び出す。

  4. Controllerクラスは、メッセージ情報(ResultMessage)を生成し、画面表示用としてModelに設定する。

  5. Controllerクラスは、遷移先のView名を返却する。

  6. ExceptionHandlerExceptionResolverは、Controllerより返却されたView名を返却する。

  7. HandlerExceptionResolverLoggingInterceptorは、ExceptionLoggerを呼び出し、HTTPステータスコードに対応するレベル(info, warn, error)のログ(監視ログとアプリケーションログ)を出力する。

  8. HandlerExceptionResolverLoggingInterceptorは、ExceptionHandlerExceptionResolverより返却されたView名を返却する。

  9. DispatcherServletは、返却されたView名に対応するJSPを呼び出す。

  10. JSPは、MessagesPanelTagを使用して、メッセージ情報(ResultMessage)を取得し、メッセージ表示用のHTMLコードを生成する。

  11. JSPで生成されたレスポンスが表示される。


4.3.2.3.3. サーブレット単位でフレームワークがハンドリングする場合の基本フロー

例外をフレームワーク(サーブレット単位)でハンドリングする場合、SystemExceptionResolverで捕捉し例外処理を行う。
基本フローは、以下の通りである。
下記の図は、 共通ライブラリから提供しているシステム例外(org.terasoluna.gfw.common.exception.SystemException)を、org.terasoluna.gfw.web.exception.SystemExceptionResolverを使ってハンドリングする場合の基本フローである。
ログは、例外ハンドリングメソッドの引数に指定された例外を記録するインタセプタ(org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor)を使用して、出力する。
flow of exception handling using resolver

図-サーブレット単位でフレームワークがハンドリングする場合の基本フロー

  1. Serviceクラスにて、システム例外に該当する状態を検知したため、SystemExceptionを発生させる。

  2. DispatcherServletは、SystemExceptionを捕捉し、SystemExceptionResolverを呼び出す。

  3. SystemExceptionResolverは、SystemExceptionから例外コードを取得し、画面表示用にHttpServletRequestに設定する(6’)。

  4. SystemExceptionResolverは、SystemException発生時の遷移先のView名を返却する。

  5. HandlerExceptionResolverLoggingInterceptorは、ExceptionLoggerを呼び出し、HTTPステータスコードに対応するレベル(info, warn, error)のログ(監視ログとアプリケーションログ)を出力する。

  6. HandlerExceptionResolverLoggingInterceptorは、SystemExceptionResolverより返却されたView名を返却する。

  7. DispatcherServletは、返却されたView名に対応するJSPを呼び出す。

  8. JSPは、HttpServletRequestより例外コードを取得し、メッセージ表示用のHTMLコードに埋め込む。

  9. JSPで生成されたレスポンスが表示される。


4.3.2.3.4. Webアプリケーション単位でサーブレットコンテナがハンドリングする場合の基本フロー

例外をWebアプリケーション単位でハンドリングする場合、サーブレットコンテナで捕捉し、例外処理を行う。
致命的なエラー、フレームワークでハンドリング対象となっていない例外(JSP/Thymeleaf内で発生した例外など)、Filterで発生した例外をハンドリングする。
基本フローは以下の通りである。
下記フローは、java.lang.Exceptionを、”error page”でハンドリングする場合のフローである。
ログ出力は、ハンドリングされていない例外が発生したことを記録するサーブレットフィルタ(org.terasoluna.gfw.web.exception.ExceptionLoggingFilter)を使用して、出力する。
flow of exception handling using container

図-Webアプリケーション単位でサーブレットコンテナがハンドリングする場合の基本フロー

  1. DispatcherServletは、XxxErrorを捕捉し、ServletExceptionにラップしてスローする。

  2. ExceptionLoggingFilterは、ServletExceptionを捕捉し、ExceptionLoggerを呼び出す。ExceptionLoggerは、errorレベルのログ(監視ログとアプリケーションログ)を出力する。ExceptionLoggingFilterは、ServletExceptionを再スローする。

  3. ServletContainerは、ServletExceptionを捕捉し、サーバログにログを出力する。ログのレベルは、アプリケーションサーバによって異なる。

  4. ServletContainerは、web.xml に定義されている遷移先(HTMLなど)を呼び出す。

  5. 呼び出された遷移先で生成されたレスポンスが表示される。


4.3.3. How to use

例外ハンドリング機能の使用方法について説明する。

共通ライブラリから提供している例外ハンドリング用のクラスについては、共通ライブラリから提供している例外ハンドリング用のクラスについてを参照されたい。

  1. アプリケーションの設定

  2. コーディングポイント(Service編)

  3. コーディングポイント(Controller編)

  4. コーディングポイント(JSP/Thymeleaf編)


4.3.3.1. アプリケーションの設定

例外ハンドリングを使用する際に、必要なアプリケーション設定を、以下に示す。
なお、ブランクプロジェクトは、既に設定済みの状態になっているので、【プロジェクト毎にカスタマイズする箇所】の部分を変更すればよい。
  1. 共通設定

  2. ドメイン層の設定

  3. アプリケーション層の設定

  4. サーブレットコンテナの設定


4.3.3.1.1. 共通設定

  1. 例外のログ出力を行うロガークラス(ExceptionLogger)を、bean定義に追加する。

  • ApplicationContextConfig.java

    // (1)
    @Bean("exceptionCodeResolver")
    public ExceptionCodeResolver exceptionCodeResolver() {
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("ResourceNotFoundException", "e.xx.fw.5001");
        map.put("BusinessException", "e.xx.fw.8001");
        bean.setExceptionMappings(map); //(2)
        bean.setDefaultExceptionCode("e.xx.fw.9001"); // (3)
        return bean;
    }
    
    // (4)
    @Bean("exceptionLogger")
    public ExceptionLogger exceptionLogger() {
        ExceptionLogger bean = new ExceptionLogger();
        bean.setExceptionCodeResolver(exceptionCodeResolver()); // (5)
        return bean;
    }
    

    項番

    説明

    (1)
    ExceptionCodeResolverを、bean定義に追加する。
    (2)
    ハンドリング対象とする例外名と、適用する「例外コード(メッセージID)」のマッピングを指定する。
    上記の設定例では、例外クラス(又は親クラス)のクラス名に、BusinessExceptionが含まれている場合は、”e.xx.fw.8001”、 ResourceNotFoundExceptionが含まれている場合は、”e.xx.fw.5001”が「例外コード(メッセージID)」となる。

    Note

    例外コード(メッセージID)について

    ここでは、BusinessExceptionに、メッセージIDが指定されなかった場合の対応で定義をしているが、後述のBusinessExceptionを発生させる実装側で、メッセージIDを指定することを推奨する。

    BusinessExceptionに対する「例外コード(メッセージID)」の指定は、BusinessException発生時に指定されなかった場合の救済策である。

    【プロジェクト毎にカスタマイズする箇所】
    (3)
    デフォルトの「例外コード(メッセージID)」を指定する。
    上記の設定例では、例外クラス(または親クラス)のクラス名にBusinessException、またはResourceNotFoundExceptionが含まれない場合、”e.xx.fw.9001”が例外コード(メッセージID)」となる。
    【プロジェクト毎にカスタマイズする箇所】

    Note

    例外コード(メッセージID)について

    例外コードは、ExceptionLoggerによりログに出力される。(画面での取得も可能である。View(JSP/テンプレートHTML)から例外コードを参照する方法については、例外コードを画面表示する方法を参照されたい。)

    またコード体系については、プロパティに定義している形式でなくともよい。

    例えば、MA7001等。

    (4)
    ExceptionLoggerを、bean定義に追加する。
    (5)
    ExceptionCodeResolverをDIする。
  1. ログ定義を追加する。

  • logback.xml

監視ログ用のログ定義を追加する。

<appender name="MONITORING_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- (1) -->
    <file>${app.log.dir:-log}/projectName-monitoring.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${app.log.dir:-log}/projectName-monitoring-%d{yyyyMMdd}.log</fileNamePattern>
        <maxHistory>7</maxHistory>
    </rollingPolicy>
    <encoder>
        <charset>UTF-8</charset>
        <pattern><![CDATA[date:%d{yyyy-MM-dd HH:mm:ss}\tX-Track:%X{X-Track}\tlevel:%-5level\tmessage:%replace(%msg){'(\r\n|\r|\n)','$1  '}%n%replace(%replace(%xEx){'(\r\n|\r|\n)','$1  '}){'  $',''}%nopex]]></pattern>
    </encoder>
</appender>

<logger name="org.terasoluna.gfw.common.exception.ExceptionLogger.Monitoring" additivity="false"> <!-- (2) -->
    <level value="error" /> <!-- (3) -->
    <appender-ref ref="MONITORING_LOG_FILE" /> <!-- (4) -->
</logger>

項番

説明

(1)
監視ログを出力するための、appender定義を指定する。上記の設定例では、ファイルに出力するappenderとしているが、システム要件に一致するappenderを使うこと。
【プロジェクト毎にカスタマイズする箇所】
(2)
監視ログ用の、ロガー定義を指定する。ExceptionLoggerを作成する際に、任意のロガー名を指定していない場合は、上記設定のままでよい。

Warning

additivityの設定値について

falseを指定すること。trueを指定すると、上位のロガー(例えば、root)によって、同じログが出力されてしまう。

(3)
出力レベルを指定する。ExceptionLoggerではinfo, warn, errorの3種類のログを出力しているが、システム要件にあったレベルを指定すること。errorレベルを推奨する。
【プロジェクト毎にカスタマイズする箇所】
(4)
出力先となるappenderを指定する。
【プロジェクト毎にカスタマイズする箇所】

アプリケーションログ用のログ定義を追加する。

<appender name="APPLICATION_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- (1) -->
    <file>${app.log.dir:-log}/projectName-application.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${app.log.dir:-log}/projectName-application-%d{yyyyMMdd}.log</fileNamePattern>
        <maxHistory>7</maxHistory>
    </rollingPolicy>
    <encoder>
        <charset>UTF-8</charset>
        <pattern><![CDATA[date:%d{yyyy-MM-dd HH:mm:ss}\tthread:%thread\tX-Track:%X{X-Track}\tlevel:%-5level\tlogger:%-48logger{48}\tmessage:%replace(%msg){'(\r\n|\r|\n)','$1  '}%n%replace(%replace(%xEx){'(\r\n|\r|\n)','$1  '}){'  $',''}%nopex]]></pattern>
    </encoder>
</appender>

<logger name="org.terasoluna.gfw.common.exception.ExceptionLogger"> <!-- (2) -->
    <level value="info" /> <!-- (3) -->
</logger>

<root level="warn">
    <appender-ref ref="STDOUT" />
    <appender-ref ref="APPLICATION_LOG_FILE" /> <!-- (4) -->
</root>

項番

説明

(1)
アプリケーションログを出力するための、appender定義を指定する。上記の設定例では、ファイルに出力するappenderとしているが、システム要件に一致するappenderを使うこと。
【プロジェクト毎にカスタマイズする箇所】
(2)
アプリケーションログ用の、ロガー定義を指定する。ExceptionLoggerを作成する際に、任意のロガー名を指定していない場合は、上記設定のままでよい。

Note

アプリケーションログ出力用のappender定義について

アプリケーションログ用のappenderは、例外出力用に個別に定義するのではなく、フレームワークや、アプリケーションコードで出力するログ用のappenderと、同じものを使うことを推奨する。

同じ出力先にすることで、例外が発生するまでの過程が追いやすくなる。

(3)
出力レベルを指定する。ExceptionLoggerでは、info, warn, errorの3種類のログを出力しているが、システム要件にあったレベルを指定すること。本ガイドラインでは、infoレベルを推奨する。
【プロジェクト毎にカスタマイズする箇所】
(4)
(2)で設定したロガーは、appenderを指定していないので、rootに流れる。そのため、出力先となるappenderを指定する。ここでは、”STDOUT”と”APPLICATION_LOG_FILE”に出力される。
【プロジェクト毎にカスタマイズする箇所】

4.3.3.1.2. ドメイン層の設定

ResultMessagesを保持する例外(BisinessException,ResourceNotFoundException)が発生した際に、ログを出力するためのインタセプタクラス(ResultMessagesLoggingInterceptor)と、AOPの設定を、bean定義に追加する。

  • XxxDomainConfig.java

    // (1)
    @Bean("resultMessagesLoggingInterceptor")
    public ResultMessagesLoggingInterceptor resultMessagesLoggingInterceptor(
            ExceptionLogger exceptionLogger) {
        ResultMessagesLoggingInterceptor bean = new ResultMessagesLoggingInterceptor();
        bean.setExceptionLogger(exceptionLogger); // (2)
        return bean;
    }
    
    @Bean
    public Advisor resultMessagesLoggingInterceptorAdvisor(
            ResultMessagesLoggingInterceptor resultMessagesLoggingInterceptor) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(
                "@within(org.springframework.stereotype.Service)"); // (3)
        return new DefaultPointcutAdvisor(pointcut, resultMessagesLoggingInterceptor);
    }
    

    項番

    説明

    (1)
    ResultMessagesLoggingInterceptorを、bean定義に追加する。
    (2)
    例外のログ出力を行うロガーオブジェクトをDIする。ApplicationContextConfig.javaに定義しているexceptionLoggerを指定する。
    (3)
    Serviceクラス(@Serviceアノテーションが付いているクラス)のメソッドに対して、ResultMessagesLoggingInterceptorを適用する。

4.3.3.1.3. アプリケーション層の設定

WebMvcConfigurerインタフェースを実装(もしくは<mvc:annotation-driven>を指定)した際に、自動的に登録されるHandlerExceptionResolverによって、ハンドリングされない例外をハンドリングするためのクラス(SystemExceptionResolver)を、bean定義に追加する。

  • SpringMvcConfig.java

    @EnableAspectJAutoProxy
    @EnableWebMvc
    @Configuration
    public class SpringMvcConfig implements WebMvcConfigurer {
    
        // (1)
        @Bean("systemExceptionResolver")
        public SystemExceptionResolver systemExceptionResolver(
                ExceptionCodeResolver exceptionCodeResolver) {
            SystemExceptionResolver bean = new SystemExceptionResolver();
            bean.setExceptionCodeResolver(exceptionCodeResolver); // (2)
            bean.setOrder(3); // (3)
    
            Properties exceptionMappings = new Properties();
            exceptionMappings.setProperty("ResourceNotFoundException",
                    "common/error/resourceNotFoundError");
            exceptionMappings.setProperty("BusinessException",
                    "common/error/businessError");
            exceptionMappings.setProperty("InvalidTransactionTokenException",
                    "common/error/transactionTokenError");
            exceptionMappings.setProperty(".DataAccessException",
                    "common/error/dataAccessError");
            bean.setExceptionMappings(exceptionMappings); // (4)
    
            Properties statusCodes = new Properties();
            statusCodes.setProperty("common/error/resourceNotFoundError", String
                    .valueOf(HttpStatus.NOT_FOUND.value()));
            statusCodes.setProperty("common/error/businessError", String
                    .valueOf(HttpStatus.CONFLICT.value()));
            statusCodes.setProperty("common/error/transactionTokenError", String
                    .valueOf(HttpStatus.CONFLICT.value()));
            statusCodes.setProperty("common/error/dataAccessError", String
                    .valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
            bean.setStatusCodes(statusCodes); // (5)
    
            // bean.setExcludedExceptions(); // (6)
    
            bean.setDefaultErrorView("common/error/systemError"); // (7)
            bean.setDefaultStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value()); // (8)
            return bean;
        }
    
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp("/WEB-INF/views/", ".jsp"); // (9)
        }
    

    項番

    説明

    (1)
    SystemExceptionResolverを、bean定義に追加する。
    (2)
    例外コード(メッセージID)を解決するオブジェクトをDIする。ApplicationContextConfig.javaに定義している、exceptionCodeResolverを指定する。
    (3)
    ハンドリングの優先順位を指定する。値は、基本的に「3」で良い。WebMvcConfigurerインタフェースを実装した際に、自動的に、登録されるクラスの方が、優先順位が上となる。

    Hint

    DefaultHandlerExceptionResolverで行われる例外ハンドリングを無効化する方法

    DefaultHandlerExceptionResolverで例外ハンドリングされた場合、HTTPレスポンスコードは設定されるが、Viewの解決がされないため、Viewの解決は、web.xmlのError Pageで行う必要がある。

    Viewの解決をweb.xmlではなく、HandlerExceptionResolverで行いたい場合は、SystemExceptionResolverの優先順位を「1」にすると、DefaultHandlerExceptionResolverより前にハンドリング処理を実行することができる。

    DefaultHandlerExceptionResolverでハンドリングされた場合の、HTTPレスポンスコードのマッピングについては、DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについてを参照されたい。

    (4)
    ハンドリング対象とする例外名と、遷移先となるView名のマッピングを指定する。
    上記の設定では、例外クラス(または親クラス)のクラス名に.DataAccessExceptionが含まれている場合、common/error/dataAccessErrorが、遷移先のView名となる。
    例外クラスがResourceNotFoundExceptionの場合、common/error/resourceNotFoundErrorが、遷移先のView名となる。
    【プロジェクト毎にカスタマイズする箇所】
    (5)
    遷移先となるView名と、HTTPステータスコードのマッピングを指定する。
    上記の設定では、View名がcommon/error/resourceNotFoundErrorの場合に、”404(Not Found)”がHTTPステータスコードとなる。
    【プロジェクト毎にカスタマイズする箇所】
    (6)
    ハンドリング対象外とする例外クラスを指定する。
    ブランクプロジェクトでは空にしている。
    (7)
    遷移するデフォルトのView名を、指定する。
    上記の設定では、例外クラスにResourceNotFoundExceptionBusinessExceptionInvalidTransactionTokenExceptionや例外クラス(または親クラス)のクラス名に、.DataAccessExceptionが含まれない場合、common/error/systemErrorが、遷移先のView名となる。
    【プロジェクト毎にカスタマイズする箇所】
    (8)
    レスポンスヘッダに設定するHTTPステータスコードのデフォルト値を指定する。“500”(Internal Server Error)を設定することを推奨する。

    Warning

    指定を省略した場合の挙動

    “200”(OK)扱いになるので、注意すること。

    (9)

    実際に遷移するViewは、ViewResolverの設定に依存する。

    上記の設定では、

    • /WEB-INF/views/common/error/systemError.jsp

    • /WEB-INF/views/common/error/resourceNotFoundError.jsp

    • /WEB-INF/views/common/error/businessError.jsp

    • /WEB-INF/views/common/error/transactionTokenError.jsp

    • /WEB-INF/views/common/error/dataAccessError.jsp

    が遷移先となる。

HandlerExceptionResolverでハンドリングされた例外を、ログに出力するためのインタセプタクラス(HandlerExceptionResolverLoggingInterceptor)と、AOPの設定を、bean定義に追加する。

  • SpringMvcConfig.java

    @EnableAspectJAutoProxy // (1)
    @EnableWebMvc
    @Configuration
    public class SpringMvcConfig implements WebMvcConfigurer {
    
        // (2)
        @Bean("handlerExceptionResolverLoggingInterceptor")
        public HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor(
                ExceptionLogger exceptionLogger) {
            HandlerExceptionResolverLoggingInterceptor bean = new HandlerExceptionResolverLoggingInterceptor();
            bean.setExceptionLogger(exceptionLogger); // (3)
            return bean;
        }
    
        @Bean
        public Advisor handlerExceptionResolverLoggingInterceptorAdvisor(
                HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(
                    "execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolveException(..))"); // (4)
            return new DefaultPointcutAdvisor(pointcut, handlerExceptionResolverLoggingInterceptor);
        }
    

    項番

    説明

    (1)
    @EnableAspectJAutoProxyアノテーションを定義することにより、AspectJサポートを有効化する。

    Note

    AspectJサポートの有効化について

    アノテーションベースのAOP設定を有効化する設定となるので、Java-based configurationで定義する場合や、AspectJが提供しているアノテーションを利用したインターセプタクラスを追加する場合は必要となる。

    Java-based configurationでは任意設定となるが、冗長で定義していてもアノテーションベースのAOP設定が有効化されるだけで、AOP自体の動作には影響はない。

    なお、AspectJサポートを有効化する場合は、アプリケーションコンテキスト単位で設定する必要があるため、ApplicationContextConfig.javaSpringMvcConfig.javaそれぞれに定義する必要がある。

    (2)
    HandlerExceptionResolverLoggingInterceptorを、bean定義に追加する。
    (3)
    例外のログ出力を行うロガーオブジェクトを、DIする。ApplicationContextConfig.javaに定義しているexceptionLoggerを指定する。
    (4)
    HandlerExceptionResolverインタフェースのresolveExceptionメソッドに対して、HandlerExceptionResolverLoggingInterceptorを適用する。

    デフォルトの設定では、共通ライブラリから提供しているorg.terasoluna.gfw.common.exception.ResultMessagesNotificationExceptionのサブクラスの例外は、このクラスで行われるログ出力の対象外となっている。
    ResultMessagesNotificationExceptionのサブクラスの例外をログ出力対象外としている理由は、 org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptorによってログ出力されるためである。
    デフォルトの設定を変更する必要がある場合は、 HandlerExceptionResolverLoggingInterceptorの設定項目についてを参照されたい。

致命的なエラー、Spring MVC管理外で発生する例外を、ログに出力するためのFilterクラス(ExceptionLoggingFilter)を、bean定義とweb.xmlに追加する。

  • ApplicationContextConfig.java

    // (1)
    @Bean("exceptionLoggingFilter")
    public ExceptionLoggingFilter exceptionLoggingFilter(
            ExceptionLogger exceptionLogger) {
        ExceptionLoggingFilter bean = new ExceptionLoggingFilter();
        bean.setExceptionLogger(exceptionLogger); // (2)
        return bean;
    }
    

    項番

    説明

    (1)
    ExceptionLoggingFilterを、bean定義に追加する。
    (2)
    例外のログ出力を行うロガーオブジェクトを、DIする。ApplicationContextConfig.javaに定義しているexceptionLoggerを指定する。
  • web.xml

    <filter>
        <filter-name>exceptionLoggingFilter</filter-name> <!-- (1) -->
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- (2) -->
    </filter>
    <filter-mapping>
        <filter-name>exceptionLoggingFilter</filter-name> <!-- (3) -->
        <url-pattern>/*</url-pattern> <!-- (4) -->
    </filter-mapping>
    

    項番

    説明

    (1)
    フィルター名を指定する。applicationContext.xmlに定義したExceptionLoggingFilterのbean名と、一致させる。
    (2)
    フィルタークラスを指定する。org.springframework.web.filter.DelegatingFilterProxy固定。
    (3)
    マッピングするフィルターのフィルター名を指定する。(1)で指定した値。
    (4)
    フィルターを適用するURLパターンを指定する。致命的なエラー、Spring MVC管理外をログ出力するため、/*を推奨する。
  • 出力ログ

    (JSP)
    date:2013-09-25 19:51:52    thread:tomcat-http--3   X-Track:f94de92148f1489b9ceeac3b2f17c969        level:ERROR     logger:o.t.gfw.common.exception.ExceptionLogger         message:[e.xx.fw.9001] An exception occurred processing JSP page /WEB-INF/views/exampleJSPException.jsp at line 13
    
    (Thymeleaf)
    date:2013-09-25 19:51:52    thread:tomcat-http--3   X-Track:f94de92148f1489b9ceeac3b2f17c969        level:ERROR     logger:o.t.gfw.common.exception.ExceptionLogger         message:[e.xx.fw.9001] Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#messages.msgWithParams(message.code, message.args)" (template: "staff/register" - line 32, col 11)
    

4.3.3.1.4. サーブレットコンテナの設定

Spring MVCの、デフォルトの例外ハンドリング機能によって行われるエラー応答(HttpServletResponse#sendError)、致命的なエラー、Spring MVC管理外で発生する例外をハンドリングするために、サーブレットコンテナのError Page定義を追加する。

  • web.xml

    Spring MVCの、デフォルトの例外ハンドリング機能によって行われるエラー応答(HttpServletResponse#sendError)を、ハンドリングするための定義を追加する。

    <error-page>
        <!-- (1) -->
        <error-code>404</error-code>
        <!-- (2) -->
        <location>/WEB-INF/views/common/error/resourceNotFoundError.jsp</location>
    </error-page>
    

    項番

    説明

    (1)
    ハンドリング対象とするHTTPレスポンスコードを指定する。
    【プロジェクト毎にカスタマイズする箇所】
    Spring MVCの、デフォルトの例外ハンドリング機能で応答されるHTTPレスポンスコードについては、DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについてを参照されたい。
    (2)
    遷移するファイル名を指定する。Webアプリケーションルートからのパスで、指定する。上記の設定では、”${WebAppRoot}/WEB-INF/views/common/error/resourceNotFoundError.jsp”が、遷移先のファイルとなる。
    【プロジェクト毎にカスタマイズする箇所】

    致命的なエラー、Spring MVC管理外で発生する例外をハンドリングするための定義を追加する。

    <error-page>
        <!-- (3) -->
        <location>/WEB-INF/views/common/error/unhandledSystemError.html</location>
    </error-page>
    

    項番

    説明

    (3)
    遷移するファイル名を指定する。Webアプリケーションルートからのパスで指定する。上記の設定では、”${WebAppRoot}/WEB-INF/views/common/error/unhandledSystemError.html”が、遷移先のファイルとなる。
    【プロジェクト毎にカスタマイズする箇所】

Note

locationに指定するパスについて

動的コンテンツのパスを指定した場合、致命的なエラーが発生していた場合に、別のエラーが発生する可能性が高くなるため、locationには、JSP/Thymeleafなどの動的コンテンツでなく、HTMLなどの静的コンテンツへのパスを指定することを推奨する。

Note

開発中に原因が特定できないエラーが発生した場合

上記の設定が行われている状態で想定外のエラー応答(HttpServletResponse#sendError)が発生した場合、どのようなエラー応答が発生したのか特定できないケースがある。

locationタグに指定したエラー画面が表示されるが、ログなどからエラーの原因を特定できない場合は、上記設定をコメントアウトして動かすことで、発生したエラー応答(HTTPレスポンスコード)を、画面で確認することできる。

Spring MVC管理外で発生する例外を、個別にハンドリングする必要がある場合は、例外毎の定義を追加する。

<error-page>
    <!-- (4) -->
    <exception-type>java.io.IOException</exception-type>
    <!-- (5) -->
    <location>/WEB-INF/views/common/error/systemError.jsp</location>
</error-page>

項番

説明

(4)
ハンドリング対象とする例外クラス名(FQCN)を指定する。
(5)
遷移するファイル名を指定する。Webアプリケーションルートからのパスで指定する。上記の設定では、”${WebAppRoot}/WEB-INF/views/common/error/systemError.jsp”が遷移先のファイルとなる。
【プロジェクト毎にカスタマイズする箇所】

4.3.3.2. コーディングポイント(Service編)

例外ハンドリングを行う際の、Serviceでのコーディングポイントを、以下に示す。

  1. ビジネス例外を発生させる

  2. システム例外を発生させる

  3. 例外をキャッチして、処理を継続させる


4.3.3.2.1. ビジネス例外を発生させる

ビジネス例外(BusinessException)の発生方法を、以下に示す。

Note

ビジネス例外の発生方法に関する注意事項

  • 基本的には、ロジックでビジネスルールの違反を検知して、ビジネス例外を発生させる方法を推奨する。
  • 既存資材や、基盤機能(FWや共通機能)のAPI仕様として、ビジネスルールの違反が、例外によって通知される場合のみ、例外を捕捉してビジネス例外を発生させてもよい。
    例外を、処理フローを制御するために使用すると、処理全体の見通しが悪くなり、保守性を低下させる可能性がある。
ロジックでビジネスルールの違反を検知して、ビジネス例外を発生させる。

Warning

  • デフォルトでは、ビジネス例外は、Serviceで発生させることを想定している。AOPの設定で、@Serviceアノテーションを付与したクラスで発生したビジネス例外のログを出力としている。
    Controllerなどでビジネス例外は、ログを出力しない。プロジェクトでの考えがある場合は変更すること。
  • XxxService.java

    ...
    @Service
    public class ExampleExceptionServiceImpl implements ExampleExceptionService {
        @Override
        public String throwBisinessException(String test) {
            ...
            // int stockQuantity = 5;
            // int orderQuantity = 6;
    
            if (stockQuantity < orderQuantity) {                  // (1)
                ResultMessages messages = ResultMessages.error(); // (2)
                messages.add("e.ad.od.5001", stockQuantity);      // (3)
                throw new BusinessException(messages);            // (4)
            }
            ...
    

    項番

    説明

    (1)
    ビジネスルールの違反がないか、チェックを行う。
    (2)
    違反している場合、ResultMessagesを生成する。上記の実装例では、errorレベルのResultMessagesを生成している。
    ResultMessagesの生成方法の詳細については、メッセージ管理を参照されたい。
    (3)
    ResultMessagesに、ResultMessageを追加する。第1引数(必須)にメッセージIDを、第2引数(任意)にメッセージ埋め込み値を指定する。
    メッセージ埋め込み値は、可変長パラメータなので、複数指定することができる。
    (4)
    ResultMessagesを指定して、BusinessExceptionを発生させる。

    Tip

    上記のXxxService.javaは説明用に(2)-(4)に分けて処理をしているが、1ステップで実装することができる。

    throw new BusinessException(ResultMessages.error().add(
         "e.ad.od.5001", stockQuantity));
    
  • xxx.properties

    参考としてプロパティの設定を記述する。

    e.ad.od.5001 = Order number is higher than the stock quantity={0}. Change the order number.
    

下記のようなアプリケーションログが出力される。

date:2013-09-17 22:25:55    thread:tomcat-http--8   X-Track:6cfb0b378c124b918e40ac0c32a1fac7        level:WARN      logger:o.t.gfw.common.exception.ExceptionLogger         message:[e.xx.fw.8001] ResultMessages [type=error, list=[ResultMessage [code=e.ad.od.5001, args=[5], text=null]]]
org.terasoluna.gfw.common.exception.BusinessException: ResultMessages [type=error, list=[ResultMessage [code=e.ad.od.5001, args=[5], text=null]]]

// stackTarace omitted
...

date:2013-09-17 22:25:55    thread:tomcat-http--8   X-Track:6cfb0b378c124b918e40ac0c32a1fac7        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Resolving exception from handler [public java.lang.String com.exception.app.example.ExampleExceptionController.home(java.util.Locale,org.springframework.ui.Model)]: org.terasoluna.gfw.common.exception.BusinessException: ResultMessages [type=error, list=[ResultMessage [code=e.ad.od.5001, args=[5], text=null]]]
date:2013-09-17 22:25:55    thread:tomcat-http--8   X-Track:6cfb0b378c124b918e40ac0c32a1fac7        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Resolving to view 'common/error/businessError' for exception of type [org.terasoluna.gfw.common.exception.BusinessException], based on exception mapping [BusinessException]
date:2013-09-17 22:25:55    thread:tomcat-http--8   X-Track:6cfb0b378c124b918e40ac0c32a1fac7        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Applying HTTP status code 409
date:2013-09-17 22:25:55    thread:tomcat-http--8   X-Track:6cfb0b378c124b918e40ac0c32a1fac7        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Exposing Exception as model attribute 'exception'

表示される画面

screen business exception

Warning

ビジネス例外は、Controllerでハンドリングし、各業務画面でメッセージを表示させることを推奨する。

上記例は、Controllerでハンドリングしなかった場合に、表示される画面となる。

例外を捕捉して、ビジネス例外を発生させる

try {
    order(orderQuantity, itemId );
} catch (StockNotEnoughException e) {                  // (1)
    throw new BusinessException(ResultMessages.error().add(
            "e.ad.od.5001", e.getStockQuantity()), e); // (2)
}

項番

説明

(1)
ビジネスルールに違反した際に、発生する例外を捕捉する。
(2)
ResultMessagesと、原因例外(e)を指定して、BusinessExceptionを発生させる。

4.3.3.2.2. システム例外を発生させる

システム例外(SystemException)の発生方法を、以下に示す。

ロジックで、システム異常を検知し、システム例外を発生させる。

if (itemEntity == null) {                                      // (1)
    throw new SystemException("e.ad.od.9012",
        "not found item entity. item code [" + itemId + "]."); // (2)
}

項番

説明

(1)
システムが、正常な状態であることをチェックする。
ここでは、例として、リクエストされた商品コード(itemId)が、商品マスタ(Item Mastar)上に存在するかチェックし、
存在しない場合、システムで用意するべきリソースがないと判断して、システムエラーにしている。
(2)
システムが異常な状態の場合、第1引数に例外コード(メッセージID)を指定する。第2引数に例外メッセージを指定して、SystemExceptionを発生させる。
上記の実装例では、メッセージ本文に、変数”itemId”の値を埋め込んでいる。

下記のような、アプリケーションログが出力される。

date:2013-09-19 21:03:06    thread:tomcat-http--3   X-Track:c19eec546b054d54a13658f94292b24f        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Resolving exception from handler [public java.lang.String org.terasoluna.exception.app.example.ExampleExceptionController.home(java.util.Locale,org.springframework.ui.Model)]: org.terasoluna.gfw.common.exception.SystemException: not found item entity. item code [10-123456].
date:2013-09-19 21:03:06    thread:tomcat-http--3   X-Track:c19eec546b054d54a13658f94292b24f        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Resolving to default view 'common/error/systemError' for exception of type [org.terasoluna.gfw.common.exception.SystemException]
date:2013-09-19 21:03:06    thread:tomcat-http--3   X-Track:c19eec546b054d54a13658f94292b24f        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Applying HTTP status code 500
date:2013-09-19 21:03:06    thread:tomcat-http--3   X-Track:c19eec546b054d54a13658f94292b24f        level:DEBUG     logger:o.t.gfw.web.exception.SystemExceptionResolver    message:Exposing Exception as model attribute 'exception'
date:2013-09-19 21:03:06    thread:tomcat-http--3   X-Track:c19eec546b054d54a13658f94292b24f        level:ERROR     logger:o.t.gfw.common.exception.ExceptionLogger         message:[e.ad.od.9012] not found item entity. item code [10-123456].
org.terasoluna.gfw.common.exception.SystemException: not found item entity. item code [10-123456].
    at org.terasoluna.exception.domain.service.ExampleExceptionServiceImpl.throwSystemException(ExampleExceptionServiceImpl.java:14) ~[ExampleExceptionServiceImpl.class:na]
...
// stackTarace omitted

表示される画面

screen system exception

Note

システムエラー画面は、個別に用意せず、共通的に決めることを推奨する。

本ガイドラインの画面では、システムエラーのためのメッセージID(業務毎)を表示し、文言は固定にしている。その理由は、オペレータに対して、エラーの細かい内容を知らせる必要がなく、システムに異常があることだけを伝えればよいためである。

そこで、開発側では、解析を簡易にするために、キーとなるメッセージIDを画面に表示して、システム異常の問い合わせに対するレスポンスを向上しようとしている。

表示される画面については、各プロジェクトでUI規約に従い、用意すること。

例外を捕捉して、システム例外を発生させる

try {
    return new File(preUploadDir.getFile(), key);
} catch (FileNotFoundException e) { // (1)
    throw new SystemException("e.ad.od.9007",
        "not found upload file. file is [" + preUploadDir.getDescription() + "]."
        e); // (2)
}

項番

説明

(1)
システム異常に分類される検査例外を捕捉する。
(2)
例外コード(メッセージID)、メッセージ、原因例外(e)を指定して、SystemExceptionを発生させる。

4.3.3.2.3. 例外をキャッチして、処理を継続させる

例外をキャッチして、処理を継続させる必要がある場合、発生した例外をログに出力してから、処理を継続するようにする。

Note

監視ログの出力について

発生した例外をログに出力する際には、例外の種類に応じて監視ログを出力する事を検討する。

共通ライブラリでは、アプリケーションログと監視ログを同時に出力する機能を持つorg.terasoluna.gfw.common.exception.ExceptionLoggerを提供している。

アプリケーションログと合わせて、監視ログの出力も必要となる場合には、ExceptionLoggerを使用する事を推奨する。

下記は、外部システムから、顧客対応履歴の取得に失敗した場合に、顧客対応履歴以外の情報を取得する処理を、継続する場合の例である。
この例では、顧客対応履歴の情報が取得できなくても、業務は継続できるため、処理を継続している。
@Inject
ExceptionLogger exceptionLogger; // (1)

// ...
InteractionHistory interactionHistory = null;
try {
    interactionHistory = restTemplete.getForObject(uri, InteractionHistory.class, customerId);
} catch (RestClientException e) { // (2)
    exceptionLogger.log(e); // (3)
}

// (4)
Customer customer = customerRepository.findByCustomerId(customerId);

// ...

項番

説明

(1)
ログ出力のため、共通ライブラリで提供しているorg.terasoluna.gfw.common.exception.ExceptionLoggerをDIする。
(2)
ハンドリング対象の例外をキャッチする。
(3)
ExceptionLoggerを利用して、キャッチした例外をログに出力する。例では、例外コードに応じた出力レベルでログ出力するためlogメソッドを呼び出しているが、出力レベルが決まっており、
後に変更する可能性がない場合は、info、warn、errorメソッドを直接呼び出してもよい。
(4)
(3)でログを出力したのみで、処理を継続する。
上記例の場合、以下のような、アプリケーションログ、及び監視ログが出力される。
なお、この挙動は、ExceptionCodeResolverの設定がデフォルトの場合を前提としている。
  • アプリケーションログ

    date:2013-09-19 21:31:47    thread:tomcat-http--3   X-Track:df5271ece2304b12a2c59ff494806397        level:ERROR     logger:o.t.gfw.common.exception.ExceptionLogger         message:[e.xx.fw.9001] Test example exception
    org.springframework.web.client.RestClientException: Test example exception
    ...
    // stackTarace omitted
    
  • 監視ログ

    date:2013-09-19 21:31:47    X-Track:df5271ece2304b12a2c59ff494806397        level:ERROR     message:[e.xx.fw.9001] Test example exception
    
ExceptionLoggerを利用してログ出力する場合、デフォルトの設定では、errorレベルのログが監視ログに出力される。
その為、処理を継続させて問題ない場合など、ExceptionLoggerを利用してログ出力する際に監視ログへの出力対象外にするには、error以外のログレベルで出力すれば良い。
これには、以下のいずれかの方法を取れば良い。
  • infoまたはwarnメソッドでログ出力する。

  • ExceptionCodeResolverで該当する例外の例外コードの先頭をe(error)以外に設定し、logメソッドでログ出力する。

  • ログ出力する例外がSystemExceptionである場合には、セットする例外コードの先頭をe(error)以外に設定し、logメソッドでログ出力する。

次の例では、infoメソッドでログ出力する例を示す。

} catch (RestClientException e) {
    exceptionLogger.info(e);
}

上記例の場合は、以下のようにアプリケーションログのみが出力される。

date:2013-09-19 22:17:53    thread:tomcat-http--3   X-Track:999725b111b4445b8d10b4ea44639c61        level:INFO      logger:o.t.gfw.common.exception.ExceptionLogger         message:[e.xx.fw.9001] Test example exception
org.springframework.web.client.RestClientException: Test example exception

4.3.3.3. コーディングポイント(Controller編)

例外ハンドリングを行う際の、Controllerでのコーディングポイントを、以下に示す。

  1. リクエスト単位で例外をハンドリングする方法

  2. ユースケース単位で例外をハンドリングする方法


4.3.3.3.1. リクエスト単位で例外をハンドリングする方法

例外をリクエスト単位でハンドリングし、引き継ぎ情報(メッセージ情報)を、Modelに設定する。
その後、遷移する画面を表示するためのメソッドを呼び出すことで、遷移先で必要なモデルを生成し、View名を決定する。
@PosttMapping("change")
public String change(@Validated UserForm userForm,
                     BindingResult result,
                     RedirectAttributes redirectAttributes,
                     Model model) {         // (1)

    // omitted

    User user = userHelper.convertToUser(userForm);
    try {
        userService.change(user);
    } catch (BusinessException e) {                                   // (2)
        model.addAttribute(e.getResultMessages());                    // (3)
        return viewChangeForm(user.getUserId(), model);               // (4)
    }

    // omitted

}

項番

説明

(1)
エラー情報を、Viewと連携するためのオブジェクトとして、Modelを引数に定義する。
(2)
ハンドリング対象となる例外を、アプリケーションコードで捕捉する。
(3)
ResultMessagesオブジェクトを、Modelに追加する。
(4)
エラー時の遷移先を表示するためのメソッドを呼び出し、View表示に必要なモデルと、View名を取得した後に、表示するView名を返却する。

4.3.3.3.2. ユースケース単位で例外をハンドリングする方法

例外を、ユースケース単位でハンドリングし、引き継ぎ情報(メッセージ情報など)が格納されたModelMap(ExtendedModelMap)を生成する。
その後、遷移する画面を表示するためのメソッドを呼び出すことで、遷移先で必要なモデルを生成し、View名を決定する。
@ExceptionHandler(BusinessException.class) // (1)
@ResponseStatus(HttpStatus.CONFLICT) // (2)
public ModelAndView handleBusinessException(BusinessException e) {
    ExtendedModelMap modelMap = new ExtendedModelMap();                 // (3)
    modelMap.addAttribute(e.getResultMessages());                       // (4)
    String viewName = top(modelMap);                                    // (5)
    return new ModelAndView(viewName, modelMap);                        // (6)
}

項番

説明

(1)
@ExceptionHandlerアノテーションのvalue属性に、ハンドリング対象とする例外クラスを指定する。ハンドリング対象とする例外は、複数指定することもできる。
(2)
@ResponseStatusアノテーションの、value属性に返却するHTTPステータスコードを指定する。例では、「409:Conflict」を指定している。
(3)
エラー情報と、モデル情報を、Viewと連携するためのオブジェクトとして、ExtendedModelMapを生成する。
(4)
ResultMessagesオブジェクトを、ExtendedModelMapに追加する。
(5)
エラー時の遷移先を表示するためのメソッドを呼び出し、View表示に必要なモデルと、View名を取得する。
(6)
(3)-(5)の処理で取得したView名と、Modelが格納されているModelAndViewを生成し、返却する。

4.3.3.4. コーディングポイント(JSP/Thymeleaf編)

例外ハンドリングを行う際の、JSP/テンプレートHTMLでのコーディングポイントを、以下に示す。

  1. メッセージを画面表示する方法

  2. 例外コードを画面表示する方法

Tip

アプリケーションでInternet Explorer/Microsoft Edgeをサポートする場合、エラー画面の応答として生成されるHTMLのサイズに注意する必要がある。

Internet Explorer/Microsoft Edgeでは、応答されたHTMLのサイズが規定値以下だと、アプリケーションが用意したエラー画面の代わりに、Internet Explorer/Microsoft Edgeが用意した簡易メッセージが表示されるためである。

参考までに、Internet Explorerでの詳細な条件は、「Friendly HTTP Error Pages」を参照されたい。


4.3.3.4.1. メッセージを画面表示する方法

任意の場所に、ResultMessagesを出力する際の実装例を、以下に示す。

<t:messagesPanel /> <!-- (1) -->

項番

説明

(1)

メッセージを出力したい場所に、<t:messagesPanel>タグを指定する。 <t:messagesPanel>タグの使用方法の詳細については、メッセージ管理を参照されたい。


4.3.3.4.2. 例外コードを画面表示する方法

任意の場所に、例外コード(メッセージID)と、固定メッセージを表示する際の実装例を、以下に示す。

<p>
    <c:if test="${!empty exceptionCode}">  <!-- (1) -->
        [${f:h(exceptionCode)}]            <!-- (2) -->
    </c:if>
    <spring:message code="e.cm.fw.9999" /> <!-- (3) -->
</p>

項番

説明

(1)
例外コード(メッセージID)の存在チェックを行う。上記の実装例のように、記号などで例外コード(メッセージID)を囲む場合は、存在チェックを行うこと。
(2)
例外コード(メッセージID)を出力する。
(3)
メッセージ定義より取得した、固定メッセージを出力する。
  • 出力画面(exceptionCode有り)

    screen system exception messagecode
  • 出力画面(exceptionCode無し)

    screen system exception no messagecode

    Note

    システム例外時に出力するメッセージについて

    • システム例外が発生した場合、エラー原因が特定できる、または推測できる詳細メッセージを出力せず、システム例外が発生したことだけを伝えるメッセージを表示することを推奨する。

    • エラー原因が特定できる、または推測できる詳細メッセージを表示した場合、システムの脆弱性を公開してしまう可能性がある。

    Note

    例外コード(メッセージID)について

    • システム例外が発生した場合、詳細メッセージの代わりに、例外コード(メッセージID)を出力することを推奨する。

    • 例外コード(メッセージID)を出力することで、システム利用者からの問い合わせに、素早く対応することができる。

    • 例外コード(メッセージID)からエラー原因を特定できるのは、システム管理者だけなので、システムの脆弱性を公開する危険性は少なくなる。


4.3.4. How to use (Ajax)

Ajaxの例外ハンドリングについては、Ajaxを参照されたい。


4.3.5. Appendix

  1. 共通ライブラリから提供している例外ハンドリング用のクラスについて

  2. SystemExceptionResolverの設定項目について

  3. DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについて


4.3.5.1. 共通ライブラリから提供している例外ハンドリング用のクラスについて

Spring MVCが提供しているクラスとは別に、共通ライブラリより例外ハンドリングを行うためのクラスを提供している。
クラスの役割は、以下の通りである。
表- org.terasoluna.gfw.common.exception パッケージ配下のクラス

項番

クラス

役割

(1)
ExceptionCode
Resolver
例外クラスに対応する例外コード(メッセージID)を解決するためのインタフェース。
例外コードとは、どのような例外が発生したのかを識別するためのコードで、システムエラー画面や、ログに出力することを想定している。
ExceptionLoggerSystemExceptionResolverなどから参照される。
(2)
SimpleMapping
ExceptionCode
Resolver
ExceptionCodeResolverの実装クラスで、例外クラスの名前と、例外コードのマッピングを保持することで、例外コードの解決を実現する。
例外クラスの名前は、FQCNではなく、FQCNの一部や、親クラスの名前でもよい。

Warning

  • FQCNの一部を指定した場合、場合によっては想定していなかったクラスとマッチしてしまうことがあるので注意が必要である。

  • 親クラスの名前を指定した場合、全ての子クラスがマッチするので注意が必要である。

(3)
enums.
ExceptionLevel
例外クラスに対応する例外レベルを表現するenum。
INFO, WARN, ERRORが定義されている。
(4)
ExceptionLevel
Resolver
例外クラスに対応する例外レベル(ログレベル)を解決するためのインタフェース。
例外レベルとは、どのようなレベルの例外が発生したのかを識別するためのコードで、ログの出力レベルを切り替えるために使われる。
ExceptionLoggerから参照される。
(5)
DefaultException
LevelResolver
ExceptionLevelResolverの実装クラスで、例外コードの先頭1文字で、例外レベルを解決している。
先頭の1文字目(case insensitive)が、
1. “i”の場合はExceptionLevel.INFO
2. “w”の場合はExceptionLevel.WARN
3. “e”の場合はExceptionLevel.ERROR
4. 上記以外の場合はExceptionLevel.ERROR
レベルとして扱う。
本クラスは、メッセージのガイドラインに記載されている、メッセージIDのルールに則った実装となっている。
(6)
ExceptionLogger
例外をログ出力するためのクラス。
監視ログ(メッセージのみ)と、アプリケーションログ(メッセージと、スタックトレースの両方)を出力することができる。
本クラスは、フレームワークより提供しているFilterや、Interceptorクラスから使用されている。
アプリケーションコードで、例外をハンドリングして処理を継続する場合、本クラスを用いて、ログを出力すること。
(7)
ResultMessages
LoggingInterceptor
ResultMessagesを保持している例外(ResultMessagesNotificationExceptionのサブ例外 )が発生した事をログに出力するためのInterceptorクラス。
ログは全てWARNレベルで出力する。
本Interceptorは、@Serviceアノテーションが付与されているクラスのメソッドに対して適用することを想定している。
ログは、ExceptionLoggerを使用して出力している。
(8)
BusinessException
ビジネスルールの違反を検知したことを通知するための例外クラスで、ドメイン層のロジックで発生させる例外である。
java.lang.RuntimeExceptionを継承しているため、デフォルトの動作として、トランザクションは、ロールバックされる。
トランザクションをコミットしたい場合は、@Transactionalアノテーションの noRollbackFor 、または noRollbackForClassName に、本例外クラスを指定する必要がある。
(9)
Resource
NotFoundException
指定されたリソース(データ)が、システム内に存在しないことを通知するための例外クラスで、主に、ドメイン層のロジックで発生させる例外である。
java.lang.RuntimeExceptionを継承しているため、デフォルトの動作として、トランザクションは、ロールバックされる。
(10)
ResultMessages
Notification
Exception
結果メッセージ(ResultMessages)を保持している例外であることを通知するための抽象例外クラスで、共通ライブラリでは、BusinessExceptionと、ResourceNotFoundExceptionが継承している。
java.lang.RuntimeExceptionを継承しているため、デフォルトの動作としてトランザクションはロールバックされる。
本例外クラスを継承すると、ResultMessagesLoggingInterceptorによって、warnレベルのログが出力される。
(11)
SystemException
システム又はアプリケーションの異常を検知した事を通知するための例外クラスで、アプリケーション層又はドメイン層のロジックで発生させる例外である。
java.lang.RuntimeExceptionを継承しているため、デフォルトの動作として、トランザクションは、ロールバックされる。
(12)
ExceptionCodeProvider
例外コードを保持する役割があることを示すインタフェースで、共通ライブラリでは、SystemExceptionが実装している。
本インタフェースを実装した例外クラスを作成すると、共通ライブラリから提供している例外ハンドリング処理にて、例外で保持している例外コードで、そのまま使われる。

表- org.terasoluna.gfw.web.exception パッケージ配下のクラス

項番

クラス

役割

(13)
SystemException
Resolver
WebMvcConfigurerインタフェースを実装(もしくは<mvc:annotation-driven>を指定)した際に、自動的に登録されるHandlerExceptionResolverによって、ハンドリングされない例外をハンドリングするためのクラス。
Spring MVCより提供されているSimpleMappingExceptionResolverを継承し、例外コード及びResultMessagesを、Viewから参照できるように機能追加を行っている。
(14)
HandlerException
ResolverLogging
Interceptor
HandlerExceptionResolverでハンドリングされた例外を、ログに出力するためのInterceptorクラス。
本Interceptorクラスでは、HandlerExceptionResolverで解決されたHTTPレスポンスコードの分類に応じて、ログの出力レベルを切り替えている。
1. “100-399”の場合は、 INFOレベルで出力する。
2. “400-499”の場合は、 WARNレベルで出力する。
3. “500-“の場合は ERRORレベルで出力する。
4. “-99”の場合は ログ出力しない。
本Interceptorを使用することで、Spring MVC管理下で発生する全ての例外を、ログに出力することができる。
ログは、ExceptionLoggerを使用して出力している。
プロジェクトの要件に応じてlogメソッドを拡張することで、デフォルトの挙動を変更してログ出力することが可能である。
(15)
ExceptionLogging
Filter
致命的なエラー、Spring MVC管理外で発生する例外を、ログに出力するためのFilterクラス。
ログは、すべてERRORレベルで出力する。
本Filterを使用した場合、致命的なエラー、およびSpring MVC管理外で発生するすべての例外を、ログに出力することができる。
ログは、ExceptionLoggerを使用して出力している。

4.3.5.2. SystemExceptionResolverの設定項目について

本編で説明していない設定項目について、説明する。 要件に応じて、設定を行うこと。

本編で説明していない設定項目一覧

項番

項目名

プロパティ名

説明

デフォルト値

(1)
結果メッセージの属性名
resultMessagesAttribute
ビジネス例外に設定されているメッセージ情報として、モデルに設定する際の属性名(String)を指定する。
View(JSP/テンプレートHTML)から結果メッセージにアクセスする際の、属性名となる。

resultMessages

(2)
例外コード(メッセージID)の属性名
exceptionCode
Attribute
例外コード(メッセージID)として、HttpServletRequestに設定する際の属性名(String)を指定する。
View(JSP/テンプレートHTML)から例外コード(メッセージID)にアクセスする際の属性名となる。

exceptionCode

(3)
例外コード(メッセージID)のヘッダ名
exceptionCode
Header
例外コード(メッセージID)として、HttpServletResponseのレスポンスヘッダに設定する際のヘッダ名(String)を指定する。

X-Exception-Code

(4)
例外オブジェクトの属性名
exceptionAttribute
ハンドリングした例外オブジェクトとして、モデルに設定する際の属性名(String)を指定する。
View(JSP/テンプレートHTML)から例外オブジェクトにアクセスする際の属性名となる。

exception

(5)
本ExceptionResolverとして、使用するハンドラー(Controller)のオブジェクト一覧
mappedHandlers
本ExceptionResolverを使用するハンドラーの、オブジェクト一覧(Set)を指定する。
指定したハンドラーオブジェクトで発生した例外のみ、ハンドリングが行われる。
この設定項目は指定してはいけない。
指定なし

指定した場合の動作は、保証しない。
(6)
本ExceptionResolverを使用するハンドラー(Controller)のクラス一覧
mappedHandlerClasses
本ExceptionResolverを使用するハンドラーのクラス一覧(Class[])を指定する。
指定したハンドラークラスで発生した例外のみハンドリングが行われる。
この設定項目は指定してはいけない。
指定なし

指定した場合の動作は、保証しない。
(7)
HTTPレスポンスのキャッシュ制御有無
preventResponseCaching
HTTPレスポンス時のキャッシュ制御の有無(true:有 false:無)を指定する。
true:有を指定すると、キャッシュを無効にするためのHTTPレスポンスヘッダが追加される。
false:無
(1)-(3)は、org.terasoluna.gfw.web.exception.SystemExceptionResolverの設定項目。
(4)は、org.springframework.web.servlet.handler.SimpleMappingExceptionResolverの設定項目。
(5)-(7)は、org.springframework.web.servlet.handler.AbstractHandlerExceptionResolverの設定項目。

4.3.5.2.1. 結果メッセージの属性名

SystemExceptionResolverでハンドリングして設定したメッセージと、アプリケーションコードでハンドリングして設定したメッセージを、View(JSP/テンプレートHTML)で別のmessagesPanelとして出力したい場合は、SystemExceptionResolver専用の属性名を指定する。
下記に示す例は、デフォルト値から「resultMessagesForExceptionResolver」に変更する場合の、設定&実装例である。
  • SpringMvcConfig.java

    @Bean("systemExceptionResolver")
    public SystemExceptionResolver systemExceptionResolver(
            ExceptionCodeResolver exceptionCodeResolver) {
    
        // omitted
    
        bean.setResultMessagesAttribute(resultMessagesForExceptionResolver()); // (1)
    
  • View

<t:messagesPanel messagesAttributeName="resultMessagesForExceptionResolver"/> <!-- (2) -->

項番

説明

(1)
結果メッセージの属性名(resultMessagesAttribute)に、resultMessagesForExceptionResolverを指定する。
(2)
メッセージ属性名(messagesAttributeName)に、SystemExceptionResolverで設定した属性名を指定する。

4.3.5.2.2. 例外コード(メッセージID)の属性名

デフォルトの属性名をアプリケーションコードで使用している場合は、重複を避けるために、別の値を設定すること。重複がない場合は、デフォルト値を変更する必要はない。
下記は、デフォルト値から、「exceptionCodeForExceptionResolver」に変更する場合の、設定&実装例である。
  • SpringMvcConfig.java

    @Bean("systemExceptionResolver")
    public SystemExceptionResolver systemExceptionResolver(
            ExceptionCodeResolver exceptionCodeResolver) {
    
        // omitted
    
        bean.setExceptionCodeAttribute(exceptionCodeForExceptionResolver()); // (1)
    

    項番

    説明

    (1)
    例外コード(メッセージID)の属性名(exceptionCodeAttribute)に、exceptionCodeForExceptionResolverを指定する。
  • View

<p>
    <c:if test="${!empty exceptionCodeForExceptionResolver}">  <!-- (2) -->
        [${f:h(exceptionCodeForExceptionResolver)}]            <!-- (3) -->
    </c:if>
    <spring:message code="e.cm.fw.9999" />
</p>

項番

説明

(2)
SystemExceptionResolverに設定した値(exceptionCodeForExceptionResolver)を、テスト対象(空チェック対応)の変数名として指定する。
(3)
SystemExceptionResolverに設定した値(exceptionCodeForExceptionResolver)を、出力対象の変数名として指定する。

4.3.5.2.3. 例外コード(メッセージID)のヘッダ名

デフォルトのヘッダ名が使用されている場合、重複を避けるために、別の値を設定すること。重複がない場合は、デフォルト値を変更する必要はない。
下記は、デフォルト値から「X-Exception-Code-ForExceptionResolver」に変更する場合の、設定&実装例である。
  • SpringMvcConfig.java

    @Bean("systemExceptionResolver")
    public SystemExceptionResolver systemExceptionResolver(
            ExceptionCodeResolver exceptionCodeResolver) {
    
        // omitted
    
        bean.setExceptionCodeHeader("X-Exception-Code-ForExceptionResolver); // (1)
    

    項番

    説明

    (1)
    例外コード(メッセージID)のヘッダ名(exceptionCodeHeader)に、X-Exception-Code-ForExceptionResolverを指定する。

4.3.5.2.4. 例外オブジェクトの属性名

デフォルトの属性名をアプリケーションコードで使用している場合は、重複を避けるために、別の値を設定すること。重複がない場合は、デフォルト値を変更する必要はない。
下記は、デフォルト値から「exceptionForExceptionResolver」に変更する場合の、設定&実装例である。
  • SpringMvcConfig.java

    @Bean("systemExceptionResolver")
    public SystemExceptionResolver systemExceptionResolver(
            ExceptionCodeResolver exceptionCodeResolver) {
    
        // omitted
    
        bean.setExceptionAttribute(exceptionForExceptionResolver()); // (1)
    

    項番

    説明

    (1)
    例外オブジェクトの属性名(exceptionAttribute)に、exceptionForExceptionResolverを指定する。
  • View

<p>[Exception Message]</p>
<p>${f:h(exceptionForExceptionResolver.message)}</p> <!-- (2) -->

項番

説明

(2)
SystemExceptionResolverに設定した値(exceptionForExceptionResolver)を、例外オブジェクトからメッセージを取得するための変数名として、指定する。

4.3.5.2.5. HTTPレスポンスのキャッシュ制御有無

HTTPレスポンスに、キャッシュ制御用のヘッダを追加したい場合は、true:有を指定する。
  • SpringMvcConfig.java

    @Bean("systemExceptionResolver")
    public SystemExceptionResolver systemExceptionResolver(
            ExceptionCodeResolver exceptionCodeResolver) {
    
        // omitted
    
        bean.setPreventResponseCaching(true); // (1)
    

    項番

    説明

    (1)
    HTTPレスポンスのキャッシュ制御有無(preventResponseCaching)に、true:有を指定する。

Note

有を指定した場合のHTTPレスポンスヘッダ

HTTPレスポンスのキャッシュ制御有無を有にすると、以下のHTTPレスポンスヘッダが出力される。

Cache-Control:no-store

SystemExceptionResolverによるキャッシュ制御用のヘッダ追加はブラウザキャッシュによる意図しないエラー画面の表示を抑止するためのオプションであるが、Spring Securityの機能を使用してセキュリティの観点からキャッシュ制御用のヘッダを追加することも可能である。

Spring Securityの機能については、ブラウザのセキュリティ対策機能との連携を参照されたい。

Warning

SpringSecurityのCache-Controlヘッダを利用する場合の注意点

SystemExceptionResolverのキャッシュ制御とSpring SecurityのCache-Controlヘッダを有効にした場合、SystemExceptionResolverのキャッシュ制御が優先される。

これにより、正常時はSpring Securityではno-store以外も付与されるが、例外時はno-storeのみ付与されるため、意図したとおりにキャッシュを制御できない恐れがあることに注意されたい。


4.3.5.3. HandlerExceptionResolverLoggingInterceptorの設定項目について

本編で説明していない設定項目について、説明する。 要件に応じて、設定を行うこと。

本編で説明していない設定項目一覧

項番

項目名

プロパティ名

説明

デフォルト値

(1)
ログ出力対象から除外する例外クラスの一覧
ignoreExceptions
HandlerExceptionResolverによってハンドリングされた例外のうち、ログ出力しない例外クラスをリスト形式で指定する。
指定した例外クラス及びサブクラスの例外が発生した場合、 本クラスでログの出力は行われない。
本項目に指定する例外クラスは、別の場所(別の仕組み)でログ出力される例外のみ指定すること。
ResultMessagesNotificationException.class

ResultMessagesNotificationException.class及びサブクラスの例外は、ResultMessagesLoggingInterceptorでログ出力されるため、デフォルト設定として除外している。

4.3.5.3.1. ログ出力対象から除外する例外クラスの一覧

プロジェクトで用意した例外クラスをログ出力対象から除外したい場合は、以下のような設定となる。

  • SpringMvcConfig.java

    @Bean("handlerExceptionResolverLoggingInterceptor")
    public HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor(
            ExceptionLogger exceptionLogger) {
        HandlerExceptionResolverLoggingInterceptor bean = new HandlerExceptionResolverLoggingInterceptor();
        bean.setExceptionLogger(exceptionLogger);
        Set<Exception> ignoreExceptions = new HashSet<>();
        ignoreExceptions.add(ResultMessagesNotificationException.class); // (1)
        ignoreExceptions.add(XxxException.class); // (2)
        bean.setIgnoreExceptions(ignoreExceptions);
        return bean;
    }
    

    項番

    説明

    (1)
    共通ライブラリのデフォルト設定で指定されているResultMessagesNotificationExceptionを除外対象に指定する。
    (2)
    プロジェクトで用意した例外クラスを除外対象に指定する。

全ての例外クラスをログ出力対象とする場合は、以下のような設定となる。

  • SpringMvcConfig.java

    @Bean("handlerExceptionResolverLoggingInterceptor")
    public HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor(
            ExceptionLogger exceptionLogger) {
        HandlerExceptionResolverLoggingInterceptor bean = new HandlerExceptionResolverLoggingInterceptor();
        bean.setExceptionLogger(exceptionLogger);
        bean.setIgnoreExceptions(null); // (3)
        return bean;
    }
    

    項番

    説明

    (3)
    ignoreExceptionsプロパティにnullを指定する。
    nullを指定すると、全ての例外クラスがログ出力対象となる。

4.3.5.4. DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについて

DefaultHandlerExceptionResolverでハンドリングされるフレームワーク例外と、HTTPステータスコードのマッピングを、以下に記載する。

項番

ハンドリングされるフレームワーク例外

HTTPステータスコード

(1)
org.springframework.web.HttpRequestMethodNotSupportedException
405
(2)
org.springframework.web.HttpMediaTypeNotSupportedException
415
(3)
org.springframework.web.HttpMediaTypeNotAcceptableException
406
(4)
org.springframework.web.bind.MissingPathVariableException
500
(5)
org.springframework.web.bind.MissingServletRequestParameterException
400
(6)
org.springframework.web.bind.ServletRequestBindingException
400
(7)
org.springframework.beans.ConversionNotSupportedException
500
(8)
org.springframework.beans.TypeMismatchException
400
(9)
org.springframework.http.converter.HttpMessageNotReadableException
400
(10)
org.springframework.http.converter.HttpMessageNotWritableException
500
(11)
org.springframework.web.bind.MethodArgumentNotValidException
400
(12)
org.springframework.web.multipart.support.MissingServletRequestPartException
400
(13)
org.springframework.validation.BindException
400
(14)
org.springframework.web.servlet.NoHandlerFoundException
404
(15)
org.springframework.web.context.request.async.AsyncRequestTimeoutException
503