5.7. 例外ハンドリング¶
Caution
本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。
本ガイドラインで作成する、Webアプリケーションの例外ハンドリング指針について説明する。
5.7.1. Overview¶
本節では、Spring MVC配下の処理で発生する例外のハンドリングについて説明する。説明対象は、以下の通りである。
5.7.1.1. 例外の分類¶
アプリケーション実行時に発生する例外は、以下3つに分類される。
項番 | 分類 | 説明 | 例外の種類 |
---|---|---|---|
(1)
|
オペレータの再操作(入力値の変更など)によって、発生原因が解消できる例外
|
オペレータの再操作によって、発生原因が解消できる例外は、アプリケーションコードで例外をハンドリングし、例外処理を行う。
|
1. ビジネス例外
|
(2)
|
オペレータの再操作によって、発生原因が解消できない例外
|
オペレータの再操作によって、発生原因が解消できない例外は、フレームワークで例外をハンドリングし、例外処理を行う。
|
|
(3)
|
クライアントからの不正リクエストにより発生する例外
|
クライアントからの不正リクエストにより発生する例外は、フレームワークで例外をハンドリングし、例外処理を行う。
|
Note
誰が、例外を意識する必要があるのか?
- (1)はアプリケーション開発者が意識する例外となる。
- (2)と(3)はアプリケーションアーキテクトが意識する例外となる。
5.7.1.2. 例外のハンドリング方法¶
項番 | ハンドリング方法 | 説明 | 例外ハンドリングのパターン |
---|---|---|---|
(1)
|
アプリケーションコードにて、
try-catch を使い、例外ハンドリングを行う。 |
リクエスト(Controllerのメソッド)単位に、例外をハンドリングする場合に使用する。
詳細は、リクエスト単位でControllerクラスがハンドリングする場合の基本フローを参照されたい。
|
|
(2)
|
@ExceptionHandler アノテーションを使い、アプリケーションコードで例外ハンドリングを行う。 |
ユースケース(Controller)単位に、例外をハンドリングする場合に使用する。
詳細は、ユースケース単位でControllerクラスがハンドリングする場合の基本フローを参照されたい。
|
|
(3)
|
フレームワークから提供されているHanlderExceptionResolverの仕組みを使い、例外ハンドリングを行う。
|
サーブレット単位に、例外をハンドリングする場合に使用する。
HanlderExceptionResolverは、
<mvc:annotation-driven> を指定した際に、自動的に、登録されるクラスと、本フレームワークから提供しているSystemExceptionResolver を使用する。詳細は、サーブレット単位でフレームワークがハンドリングする場合の基本フローを参照されたい。
|
|
(4)
|
サーブレットコンテナのerror-page機能を使い、例外ハンドリングを行う。
|
致命的なエラー、Spring MVC管理外で発生する例外をハンドリングする場合に使用する。
詳細は、Webアプリケーション単位でサーブレットコンテナがハンドリングする場合の基本フローを参照されたい。
|
Note
誰が例外ハンドリングを行うのか?
- (1)と(2)はアプリケーション開発者が設計・実装する。
- (3)と(4)はアプリケーションアーキテクトが設計・設定する。
Note
自動的に登録されるHanlderExceptionResolverについて
<mvc:annotation-driven> を指定した際に、自動的に登録されるHanlderExceptionResolverの役割は、以下の通りである。
優先順は、以下の並び順の通りとなる。
項番 クラス(優先順位) 役割 (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 の役割は?
<mvc:annotation-driven> を指定した際に、自動的に登録されるHanlderExceptionResolverによって、 ハンドリングされない例外をハンドリングするためのクラスである。 そのため優先順は、DefaultHandlerExceptionResolverの後になるように設定する。
Note
Spring Framework 3.2 より追加された@ControllerAdviceアノテーションについて
@ControllerAdvice
の登場により、サーブレット単位で、@ExceptionHandler
を使った例外ハンドリングを行えるようになった。
@ControllerAdvice
アノテーションが付与されたクラスで、@ExceptionHandler
アノテーションを付与したメソッドを定義すると、サーブレット内のすべてのControllerに適用される。
以前のバージョンで同じことを実現する場合、@ExceptionHandler
アノテーションが付与されたメソッドを、Controllerのベースクラスのメソッドとして定義し、
各Controllerでベースクラスを継承する必要があった。
Note
@ControllerAdviceアノテーションの使いどころ
- サーブレット単位で行う例外ハンドリングに対して、View名と、HTTPレスポンスコードの解決以外の処理が必要な場合。
(View名とHTTPレスポンスコードの解決のみでよい場合は、
SystemExceptionResolver
で対応できる ) - サーブレット単位で行う例外ハンドリングに対して、エラー応答用のレスポンスデータをJSPなどのテンプレートエンジンを使わずに、 エラー用のモデル(JavaBeans)を、JSONやXML形式にシリアライズして生成したい場合 (AJAXや、REST用のControllerを作成する際の、エラーハンドリングとして使用する)。
5.7.2. Detail¶
5.7.2.1. 例外の種類¶
アプリケーション実行時に発生する例外は、以下6種類に分類される。
項番 | 例外の種類 | 説明 |
---|---|---|
(1)
|
ビジネスルールの違反を検知したことを通知する例外
|
|
(2)
|
フレームワーク、およびライブラリ内で発生する例外のうち、システムが、正常稼働している時に発生する可能性のある例外
|
|
(3)
|
システムが、正常稼働している時に、発生してはいけない状態を検知したことを通知する例外
|
|
(4)
|
システムが、正常稼働している時には発生しない非検査例外
|
|
(5)
|
システム(アプリケーション)全体に影響を及ぼす、致命的な問題が発生していることを通知するエラー
|
|
(6)
|
フレームワークが、リクエスト内容の不正を検知したことを通知する例外
|
5.7.2.1.1. ビジネス例外¶
- 旅行を予約する際に予約日が期限を過ぎている場合
- 商品を注文する際に在庫切れの場合
- etc …
Note
該当する例外クラス
org.terasoluna.gfw.common.exception.BusinessException
(共通ライブラリから提供しているクラス)。- 細かくハンドリングする必要がある場合は、BusinessExceptionを継承した例外クラスを作成すること。
- 共通ライブラリで用意しているビジネス例外クラスで、要件を満たせない場合は、プロジェクト毎にビジネス例外クラスを作成すること。
5.7.2.1.2. 正常稼働時に発生するライブラリ例外¶
- 複数のオペレータによって、同じデータを同時に更新しようとした場合に、発生する楽観排他例外や、悲観排他例外。
- 複数のオペレータによって、同じデータを同時に登録しようとした場合に、発生する一意制約違反例外。
- etc …
Note
該当する例外クラスの例
org.springframework.dao.OptimisticLockingFailureException
(楽観排他でエラーが発生した場合に発生する例外)。org.springframework.dao.PessimisticLockingFailureException
(悲観排他でエラーが発生した場合に発生する例外)。org.springframework.dao.DuplicateKeyException
(一意制約違反となった場合に発生する例外)。- etc …
Todo
JPA(Hibernate)を使用すると、現状意図しないエラーとなることが発覚している。
- 一意制約違反が発生した場合、
DuplicateKeyException
ではなく、org.springframework.dao.DataIntegrityViolationException
が発生する。 - 悲観ロックに失敗した場合、
PessimisticLockingFailureException
ではなく、org.springframework.dao.UncategorizedDataAccessException
の子クラスが発生する。
悲観エラー時に発生するUncategorizedDataAccessException
は、システムエラーに分類される例外なので、
アプリケーションでハンドリングすることは推奨しない。
しかしながら、最悪ハンドリングを行う必要があるかもしれない。
原因例外には、悲観ロックエラーが発生したことを通知する例外が格納されているので、ハンドリングできる。
⇒継続調査。
現状以下の動作となる。
- PostgreSQL + for update nowait
- org.springframework.orm.hibernate3.HibernateJdbcException
- Caused by: org.hibernate.PessimisticLockException
- Oracle + for update
- org.springframework.orm.hibernate3.HibernateSystemException
- Caused by: Caused by: org.hibernate.dialect.lock.PessimisticEntityLockException
- Caused by: org.hibernate.exception.LockTimeoutException
- Oracle / PostgreSQL + 一意制約
- org.springframework.dao.DataIntegrityViolationException
- Caused by: org.hibernate.exception.ConstraintViolationException
5.7.2.1.3. システム例外¶
- 事前に存在しているはずのマスタデータ、ディレクトリ、ファイルなどが存在しない場合。
- フレームワーク、ライブラリ内で発生する検査例外のうち、システム異常に分類される例外を捕捉した場合(ファイル操作時のIOExceptionなど)。
- etc …
Note
該当する例外クラス
org.terasoluna.gfw.common.exception.SystemException
(共通ライブラリから提供しているクラス)。- 遷移先のエラー画面や、HTTPレスポンスコードを細かく分ける場合は、SystemExceptionを継承した例外クラスを作成すること。
- 共通ライブラリで用意しているシステム例外クラスだと要件を満たせない場合は、プロジェクト毎にシステム例外クラスを作成すること。
5.7.2.1.4. 予期しないシステム例外¶
- アプリケーション、フレームワーク、ライブラリにバグが潜んでいる場合。
- DBサーバなどがダウンしている場合。
- etc …
Note
該当する例外クラスの例
java.lang.NullPointerException
(バグ起因で発生する例外)。org.springframework.dao.DataAccessResourceFailureException
(DBサーバがダウンしている場合に発生する例外)。- etc …
5.7.2.1.5. 致命的なエラー¶
- Java仮想マシンで使用できるメモリが不足している場合。
- etc …
Note
該当するエラークラスの例
java.lang.OutOfMemoryError
(メモリ不足時に発生するエラー)。- etc …
5.7.2.1.6. リクエスト不正時に発生するフレームワーク例外¶
- POSTメソッドのみ許容しているリクエストパスに対して、GETメソッドでアクセスした場合に発生する例外。
@PathVariable
アノテーションを使って、URIから値を抽出する際に、URIに型変換できない値が、指定された場合に発生する例外。- etc …
Note
該当する例外クラスの例
org.springframework.web.HttpRequestMethodNotSupportedException
(サポート外のメソッドでアクセスされた場合に発生する例外)。org.springframework.beans.TypeMismatchException
(URIに型変換できない値が指定された場合に発生する例外)。- etc …
DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについての中の、HTTPステータスコードが「4XX」の例外が該当するクラス。
5.7.2.2. 例外ハンドリングのパターン¶
項番 | ハンドリングの目的 | ハンドリング対象となり得る例外 | ハンドリング方法 | ハンドリング単位 |
---|---|---|---|---|
(1)
|
1. ビジネス例外
|
アプリケーションコード
(try-catch)
|
リクエスト
|
|
(2)
|
1. ビジネス例外
|
アプリケーションコード
(@ExceptionHandler)
|
ユースケース
|
|
(3)
|
1. システム例外
2. 予期しないシステム例外
|
フレームワーク
(ハンドリングルールを、
spring-mvc.xml に指定する) |
サーブレット
|
|
(4)
|
フレームワーク
|
サーブレット
|
||
(5)
|
1. 致命的なエラー
|
サーブレットコンテナ
(ハンドリングルールを、
web.xml に指定する) |
Webアプリケーション
|
|
(6)
|
1. プレゼンテーション層で発生する全ての例外及びエラー
|
サーブレットコンテナ
(ハンドリングルールを、
web.xml に指定する) |
Webアプリケーション
|
5.7.2.2.1. ユースケースの一部やり直し(途中からのやり直し)を促す場合¶
ユースケースの一部やり直し(途中からのやり直し)を促す場合は、Controllerクラスのアプリケーションコードで捕捉(try-catch)し、リクエスト単位で例外処理を行う。
Note
ユースケースの一部やり直しを促す場合の例
- ショッピングサイトで注文処理を行った際に、在庫不足を通知するビジネス例外が発生する場合。このケースの場合、個数を減らせば注文処理が行えるため、個数が変更できる画面に遷移し、個数変更を促すメッセージを表示する。
etc …
5.7.2.2.2. ユースケースのやり直し(先頭からのやり直し)を促す場合¶
ユースケースのやり直し(先頭からのやり直し)を促す場合は、@ExceptionHandler
を使って捕捉し、ユースケース単位で例外処理を行う。
Note
ユースケースのやり直し(先頭からのやり直し)を促す場合の例
- ショッピングサイト(管理者向けサイト)で商品マスタの変更を行った際に、変更対象の商品マスタが他のオペレータによって変更されていた場合(楽観排他例外が発生した場合)。このケースの場合、他のユーザが行った変更内容を確認してから操作してもらう必要があるため、ユースケースの先頭画面(例えば商品マスタの検索画面)に遷移し、再操作を促すメッセージを表示する。
etc …
5.7.2.2.3. システム、またはアプリケーションが、正常な状態でない事を通知する場合¶
システム、またはアプリケーションが、正常な状態でないことを通知する例外を検知する場合は、SystemExceptionResolverで捕捉し、サーブレット単位で例外処理を行う。
Note
システム、またはアプリケーションが正常な状態でないことを通知する場合の例
- 外部システムとの接続を行うユースケースにて、外部システムが、閉塞中であることを通知する例外が発生した場合。このケースの場合、外部システムが開局するまで実行できないため、エラー画面に遷移し、外部システムが開局するまでユースケースが実行できない旨を通知する。
- アプリケーションで指定した値を、条件にマスタ情報の検索を行った際に、該当するマスタ情報が存在しない場合。このケースの場合、マスタメンテナンス機能のバグ又はシステム運用者によるデータ投入ミス(リリースミス)の可能性があるため、システムエラー画面に遷移し、システム異常が発生した旨を通知する。
- ファイル操作時にAPIからIOExceptionが発生した場合。このケースの場合、ディスク異常などが考えられるため、システムエラー画面に遷移し、システム異常が発生した旨を通知する。
etc …
5.7.2.2.4. リクエスト内容が、不正であることを通知する場合¶
フレームワークによって、検知されたリクエスト不正を通知する場合は、DefaultHandlerExceptionResolverで捕捉し、サーブレット単位で例外処理を行う。
Note
リクエスト内容が不正であることを通知する場合の例
- POSTメソッドのみ許可されているURIで、GETメソッドを使ってアクセスした場合。このケースの場合、ブラウザのお気に入り機能などを使って直接アクセスしている事が考えられるため、エラー画面に遷移し、リクエスト内容が不正であることを通知する。
@PathVariable
アノテーションを使ってURIから値を抽出する際に、URIから値を抽出できなかった場合。このケースの場合、ブラウザのアドレスバーの値を書き換えて、直接アクセスしている事が考えられるため、エラー画面に遷移し、リクエスト内容が不正であることを通知する。etc …
5.7.2.2.6. プレゼンテーション層(JSPなど)で、例外が発生したことを通知する場合¶
プレゼンテーション層(JSPなど)で、例外が発生したことを通知する場合、サーブレットコンテナで捕捉し、Webアプリケーション単位で例外処理を行う。
5.7.2.3. 例外ハンドリングの基本フロー¶
- リクエスト単位でControllerクラスがハンドリングする場合の基本フロー
- ユースケース単位でControllerクラスがハンドリングする場合の基本フロー
- サーブレット単位でフレームワークがハンドリングする場合の基本フロー
- Webアプリケーション単位でサーブレットコンテナがハンドリングする場合の基本フロー
5.7.2.3.1. リクエスト単位でControllerクラスがハンドリングする場合の基本フロー¶
org.terasoluna.gfw.common.exception.BusinessException
)をハンドリングする場合の基本フローである。org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor
)を使用して、出力する。- Serviceクラスにて、 BusinessExceptionを生成し、スローする。
- ResultMessagesLoggingInterceptorは、 ExceptionLoggerを呼び出し、warnレベルのログ(監視ログとアプリケーションログ)を出力する。 ResultMessagesLoggingInterceptorはResultMessagesNotificationExceptionのサブ例外(BusinessException/ResourceNotFoundException)が発生した場合のみ、ログを出力するクラスである。
- Controllerクラスは、 BusinessExceptionを捕捉し、 BusinessExceptionに設定されているメッセージ情報(ResultMessage)を画面表示用にModelに設定する(6’)。
- Controllerクラスは、遷移先のView名を返却する。
- DispatcherServletは、返却されたView名に対応するJSPを呼び出す。
- JSPは、MessagesPanelTagを使用して、メッセージ情報(ResultMessage)を取得し、メッセージ表示用のHTMLコードを生成する。
- JSPで生成されたレスポンスが表示される。
5.7.2.3.2. ユースケース単位でControllerクラスがハンドリングする場合の基本フロー¶
@ExceptionHandler
を使って捕捉し、例外処理を行う。XxxException
)をハンドリングする場合の、基本フローである。org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor
)を使用して、出力する。- Controllerクラスから呼び出されたServiceクラスにて、例外(XxxException)が発生する。
- DispatcherServletは、XxxExceptionを捕捉し、ExceptionHandlerExceptionResolverを呼び出す。
- ExceptionHandlerExceptionResolverは、Controllerクラスに用意されている例外ハンドリングメソッドを呼び出す。
- Controllerクラスは、メッセージ情報(ResultMessage)を生成し、画面表示用としてModelに設定する。
- Controllerクラスは、遷移先のView名を返却する。
- ExceptionHandlerExceptionResolverは、Controllerより返却されたView名を返却する。
- HandlerExceptionResolverLoggingInterceptorは、ExceptionLoggerを呼び出し、例外コードに対応するレベル(info, warn, error)のログ(監視ログとアプリケーションログ)を出力する。
- HandlerExceptionResolverLoggingInterceptorは、ExceptionHandlerExceptionResolverより返却されたView名を返却する。
- DispatcherServletは、返却されたView名に対応するJSPを呼び出す。
- JSPは、MessagesPanelTagを使用して、メッセージ情報(ResultMessage)を取得し、メッセージ表示用のHTMLコードを生成する。
- JSPで生成されたレスポンスが表示される。
5.7.2.3.3. サーブレット単位でフレームワークがハンドリングする場合の基本フロー¶
org.terasoluna.gfw.common.exception.SystemException
)を、org.terasoluna.gfw.web.exception.SystemExceptionResolver
を使ってハンドリングする場合の基本フローである。org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor
)を使用して、出力する。- Serviceクラスにて、システム例外に該当する状態を検知したため、SystemExceptionを発生させる。
- DispatcherServletは、SystemExceptionを捕捉し、SystemExceptionResolverを呼び出す。
- SystemExceptionResolverは、SystemExceptionから例外コードを取得し、画面表示用にHttpServletRequestに設定する(6’)。
- SystemExceptionResolverは、SystemException発生時の遷移先のView名を返却する。
- HandlerExceptionResolverLoggingInterceptorは、ExceptionLoggerを呼び出し、例外コードに対応するレベル(info, warn, error)のログ(監視ログとアプリケーションログ)を出力する。
- HandlerExceptionResolverLoggingInterceptorは、SystemExceptionResolverより返却されたView名を返却する。
- DispatcherServletは、返却されたView名に対応するJSPを呼び出す。
- JSPは、HttpServletRequestより例外コードを取得し、メッセージ表示用のHTMLコードに埋め込む。
- JSPで生成されたレスポンスが表示される。
5.7.2.3.4. Webアプリケーション単位でサーブレットコンテナがハンドリングする場合の基本フロー¶
org.terasoluna.gfw.web.exception.ExceptionLoggingFilter
)を使用して、出力する。- DispatcherServletは、XxxErrorを捕捉し、ServletExceptionにラップしてスローする。
- ExceptionLoggingFilterは、ServletExceptionを捕捉し、ExceptionLoggerを呼び出す。ExceptionLoggerは、例外コードに対応するレベル(info, warn, error)のログ(監視ログとアプリケーションログ)を出力する。ExceptionLoggingFilterは、ServletExceptionを再スローする。
- ServletContainerは、ServletExceptionを捕捉し、サーバログにログを出力する。ログのレベルは、アプリケーションサーバによって異なる。
- ServletContainerは、
web.xml
に定義されている遷移先(HTMLなど)を呼び出す。 - 呼び出された遷移先で生成されたレスポンスが表示される。
5.7.3. How to use¶
例外ハンドリング機能の使用方法について説明する。
共通ライブラリから提供している例外ハンドリング用のクラスについては、共通ライブラリから提供している例外ハンドリング用のクラスについてを参照されたい。
5.7.3.1. アプリケーションの設定¶
5.7.3.1.1. 共通設定¶
1. 例外のログ出力を行うロガークラス(ExceptionLogger
)を、bean定義に追加する。
- applicationContext.xml
<!-- Exception Code Resolver. --> <bean id="exceptionCodeResolver" class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver"> <!-- (1) --> <!-- Setting and Customization by project. --> <property name="exceptionMappings"> <!-- (2) --> <map> <entry key="ResourceNotFoundException" value="e.xx.fw.5001" /> <entry key="BusinessException" value="e.xx.fw.8001" /> </map> </property> <property name="defaultExceptionCode" value="e.xx.fw.9001" /> <!-- (3) --> </bean> <!-- Exception Logger. --> <bean id="exceptionLogger" class="org.terasoluna.gfw.common.exception.ExceptionLogger"> <!-- (4) --> <property name="exceptionCodeResolver" ref="exceptionCodeResolver" /> <!-- (5) --> </bean>
項番 説明 (1)ExceptionCodeResolver
を、bean定義に追加する。 (2) ハンドリング対象とする例外名と、適用する「例外コード(メッセージID)」のマッピングを指定する。上記の設定例では、例外クラス(又は親クラス)のクラス名に、”BusinessException”が含まれている場合は、”w.xx.fw.8001”、 “ResourceNotFoundException”が含まれている場合は、”w.xx.fw.5001”が「例外コード(メッセージID)」となる。Note
例外コード(メッセージID)について
ここでは、”BusinessException”に、メッセージIDが指定されなかった場合の対応で定義をしているが、 後述の”BusinessException”を発生させる実装側で、「例外コード(メッセージID)」を指定することを推奨する。 “BusinessException”に対する「例外コード(メッセージID)」の指定は、”BusinessException”発生時に指定されなかった場合の救済策である。
【プロジェクト毎にカスタマイズする箇所】 (3) デフォルトの「例外コード(メッセージID)」を指定する。上記の設定例では、例外クラス(または親クラス)のクラス名に”BusinessException”、または”ResourceNotFoundException”が含まれない場合、”e.xx.fw.9001”が例外コード(メッセージID)」となる。【プロジェクト毎にカスタマイズする箇所】Note
例外コード(メッセージID)について
例外コードは、ログに出力するのみ。(画面での取得もできる。JSPへのリンク) プロパティに定義している形式でなくとも、運用上でわかるIDにすることが可能である。 例えば、MA7001等
(4)ExceptionLogger
を、bean定義に追加する。 (5)ExceptionCodeResolver
をDIする。
2. ログ定義を追加する。
- logback.xml
監視ログ用のログ定義を追加する。
<appender name="MONITORING_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- (1) --> <file>log/projectName-monitoring.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>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:%msg%n]]></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>log/projectName-application.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>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:%msg%n]]></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”に出力される。【プロジェクト毎にカスタマイズする箇所】
5.7.3.1.2. ドメイン層の設定¶
ResultMessagesを保持する例外(BisinessException,ResourceNotFoundException)が発生した際に、ログを出力するためのインタセプタクラス(ResultMessagesLoggingInterceptor
)と、AOPの設定を、bean定義に追加する。
- xxx-domain.xml
<!-- interceptor bean. --> <bean id="resultMessagesLoggingInterceptor" class="org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor"> <!-- (1) --> <property name="exceptionLogger" ref="exceptionLogger" /> <!-- (2) --> </bean> <!-- setting AOP. --> <aop:config> <aop:advisor advice-ref="resultMessagesLoggingInterceptor" pointcut="@within(org.springframework.stereotype.Service)" /> <!-- (3) --> </aop:config>
項番 説明 (1)ResultMessagesLoggingInterceptor
を、bean定義に追加する。 (2) 例外のログ出力を行うロガーオブジェクトをDIする。applicationContext.xml
に定義している “exceptionLogger” を指定する。 (3) Serviceクラス(@Service
アノテーションが付いているクラス)のメソッドに対して、ResultMessagesLoggingInterceptorを適用する。
5.7.3.1.3. アプリケーション層の設定¶
<mvc:annotation-driven> を指定した際に、自動的に登録されるHanlderExceptionResolverによって、ハンドリングされない例外をハンドリングするためのクラス(SystemExceptionResolver
)を、bean定義に追加する。
- spring-mvc.xml
<!-- Setting Exception Handling. --> <!-- Exception Resolver. --> <bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <!-- (1) --> <property name="exceptionCodeResolver" ref="exceptionCodeResolver" /> <!-- (2) --> <!-- Setting and Customization by project. --> <property name="order" value="3" /> <!-- (3) --> <property name="exceptionMappings"> <!-- (4) --> <map> <entry key="ResourceNotFoundException" value="common/error/resourceNotFoundError" /> <entry key="BusinessException" value="common/error/businessError" /> <entry key="InvalidTransactionTokenException" value="common/error/transactionTokenError" /> <entry key=".DataAccessException" value="common/error/dataAccessError" /> </map> </property> <property name="statusCodes"> <!-- (5) --> <map> <entry key="common/error/resourceNotFoundError" value="404" /> <entry key="common/error/businessError" value="409" /> <entry key="common/error/transactionTokenError" value="409" /> <entry key="common/error/dataAccessError" value="500" /> </map> </property> <property name="defaultErrorView" value="common/error/systemError" /> <!-- (6) --> <property name="defaultStatusCode" value="500" /> <!-- (7) --> </bean> <!-- Settings View Resolver. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- (8) --> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean>
項番 説明 (1)SystemExceptionResolver
を、bean定義に追加する。 (2) 例外コード(メッセージID)を解決するオブジェクトをDIする。applicationContext.xml
に定義している、”exceptionCodeResolver”を指定する。 (3) ハンドリングの優先順位を指定する。値は、基本的に「3」で良い。<mvc:annotation-driven>
を指定した際に、自動的に、登録されるクラスの方が、優先順位が上となる。Hint
DefaultHandlerExceptionResolverで行われる例外ハンドリングを無効化する方法
DefaultHandlerExceptionResolver
で例外ハンドリングされた場合、HTTPレスポンスコードは設定されるが、Viewの解決がされないため、Viewの解決は、web.xml
のError Pageで行う必要がある。 Viewの解決をweb.xml
ではなく、HanlderExceptionResolver
で行いたい場合は、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) 遷移するデフォルトのView名を、指定する。上記の設定では、例外クラスに”ResourceNotFoundException”、”BusinessException”、”InvalidTransactionTokenException”や例外クラス(または親クラス)のクラス名に、”.DataAccessException”が含まれない場合、”common/error/systemError”が、遷移先のView名となる。【プロジェクト毎にカスタマイズする箇所】 (7) レスポンスヘッダに設定するHTTPステータスコードのデフォルト値を指定する。 “500”(Internal Server Error) を設定することを推奨する。Warning
指定を省略した場合の挙動
“200”(OK)扱いになるので、注意すること。
(8) 実際に遷移する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定義に追加する。
- spring-mvc.xml
<!-- Setting AOP. --> <bean id="handlerExceptionResolverLoggingInterceptor" class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor"> <!-- (1) --> <property name="exceptionLogger" ref="exceptionLogger" /> <!-- (2) --> </bean> <aop:config> <aop:advisor advice-ref="handlerExceptionResolverLoggingInterceptor" pointcut="execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolveException(..))" /> <!-- (3) --> </aop:config>
項番 説明 (1)ExceptionHandlerLoggingInterceptor
を、bean定義に追加する。 (2) 例外のログ出力を行うロガーオブジェクトを、DIする。applicationContext.xml
に定義している”exceptionLogger”を指定する。 (3)HandlerExceptionResolver
インタフェースのresolveExceptionメソッドに対して、HandlerExceptionResolverLoggingInterceptor
を適用する。デフォルトの設定では、共通ライブラリから提供しているorg.terasoluna.gfw.common.exception.ResultMessagesNotificationException
のサブクラスの例外は、このクラスで行われるログ出力の対象外となっている。ResultMessagesNotificationException
のサブクラスの例外をログ出力対象外としている理由は、org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor
によってログ出力されるためである。デフォルトの設定を変更する必要がある場合は、 HandlerExceptionResolverLoggingInterceptorの設定項目について を参照されたい。
致命的なエラー、Spring MVC管理外で発生する例外を、ログに出力するためのFilterクラス(ExceptionLoggingFilter
)を、bean定義とweb.xml
に追加する。
- applicationContext.xml
<!-- Filter. --> <bean id="exceptionLoggingFilter" class="org.terasoluna.gfw.web.exception.ExceptionLoggingFilter" > <!-- (1) --> <property name="exceptionLogger" ref="exceptionLogger" /> <!-- (2) --> </bean>
項番 説明 (1)ExceptionLoggingFilter
を、bean定義に追加する。 (2) 例外のログ出力を行うロガーオブジェクトを、DIする。applicationContext.xml
に定義している”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管理外をログ出力するため、/*
を推奨する。
- 出力ログ
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
5.7.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などの動的コンテンツでなく、 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”が遷移先のファイルとなる。【プロジェクト毎にカスタマイズする箇所】
5.7.3.2. コーディングポイント(Service編)¶
例外ハンドリングを行う際の、Serviceでのコーディングポイントを、以下に示す。
5.7.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 ommited
...
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 org.terasoluna.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'
表示される画面
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を発生させる。
5.7.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 ommited
表示される画面
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を発生させる。
5.7.3.2.3. 例外をキャッチして、処理を継続させる¶
@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.findOne(customerId); // ...
項番 説明 (1) 共通ライブラリで提供しているorg.terasoluna.gfw.common.exception.ExceptionLogger
をログ出力するため、オブジェクトをDIする。 (2) ハンドリング対象の例外を、キャッチする。 (3) ハンドリングした例外を、ログに出力する。例では、logメソッドを呼び出しているが、出力レベルが決まっており、後に変更する可能性がない場合は、info、warn、errorメソッドを直接呼び出してもよい。 (4) (3)でログを出力したのみで、処理を継続する。
下記のような、アプリケーションログが出力される。
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 ommitedWarning
exceptionLogger
で、log()を使用した場合には、errorレベルで出力されるため、デフォルトで監視ログにも出力される。date:2013-09-19 21:31:47 X-Track:df5271ece2304b12a2c59ff494806397 level:ERROR message:[e.xx.fw.9001] Test example exceptionこの例のように、処理を継続させて問題ない場合に、運用監視で監視ログを監視している場合は、出力レベルで監視されないレベルにするか、メッセージから監視されないよう定義が必要である。} catch (RestClientException e) { exceptionLogger.info(e); }デフォルトの設定では、errorレベル以外の監視ログは出力されない。アプリケーションログには、以下のように出力される。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
5.7.3.3. コーディングポイント(Controller編)¶
例外ハンドリングを行う際の、Controllerでのコーディングポイントを、以下に示す。
5.7.3.3.1. リクエスト単位で例外をハンドリングする方法¶
@RequestMapping(value = "change", method = RequestMethod.POST) 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名を返却する。
5.7.3.3.2. ユースケース単位で例外をハンドリングする方法¶
@ExceptionHandler(BusinessException.class) // (1) @ResponseStatus(HttpStatus.CONFLICT) // (2) public ModelAndView handdleBusinessException(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を生成し、返却する。
5.7.3.4. コーディングポイント(JSP編)¶
例外ハンドリングを行う際の、JSPでのコーディングポイントを、以下に示す。
5.7.3.4.1. MessagesPanelTagを使用して、メッセージを画面表示する方法¶
任意の場所に、ResultMessagesを出力する際の実装例を、以下に示す。
<t:messagesPanel /> <!-- (1) -->
項番 説明 (1)メッセージを出力したい場所に、<t:messagesPanel>タグを指定する。 <t:messagesPanel>タグの使用方法の詳細については、メッセージ管理を参照されたい。
5.7.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有り)
- 出力画面(exceptionCode無し)
Note
システム例外時に出力するメッセージについて
- システム例外が発生した場合、エラー原因が特定できる、または推測できる詳細メッセージを出力せず、システム例外が発生したことだけを伝えるメッセージを表示することを推奨する。
- エラー原因が特定できる、または推測できる詳細メッセージを表示した場合、システムの脆弱性を公開してしまう可能性がある。
Note
例外コード(メッセージID)について
- システム例外が発生した場合、詳細メッセージの代わりに、例外コード(メッセージID)を出力することを推奨する。
- 例外コード(メッセージID)を出力することで、システム利用者からの問い合わせに、素早く対応することができる。
- 例外コード(メッセージID)からエラー原因を特定できるのは、システム管理者だけなので、システムの脆弱性を公開する危険性は少なくなる。
5.7.5. Appendix¶
- 共通ライブラリから提供している例外ハンドリング用のクラスについて
- SystemExceptionResolverの設定項目について
- DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについて
5.7.5.1. 共通ライブラリから提供している例外ハンドリング用のクラスについて¶
項番 | クラス | 役割 |
---|---|---|
(1)
|
ExceptionCode
Resolver
|
例外クラスに対応する例外コード(メッセージID)を解決するためのインタフェース。
例外コードとは、どのような例外が発生したのかを識別するためのコードで、システムエラー画面や、ログに出力することを想定している。
ExceptionLogger 、SystemExceptionResolver などから参照される。 |
(2)
|
SimpleMapping
ExceptionCode
Resolver
|
ExceptionCodeResolver の実装クラスで、例外クラスの名前と、例外コードのマッピングを保持することで、例外コードの解決を実現する。例外クラスの名前は、FQCNではなく、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.RumtimeException を継承しているため、デフォルトの動作として、トランザクションは、ロールバックされる。トランザクションをコミットしたい場合は、
@Transactional アノテーションの noRollbackFor 、または noRollbackForClassName に、本例外クラスを指定する必要がある。 |
(9)
|
Resource
NotFoundException
|
指定されたリソース(データ)が、システム内に存在しないことを通知するための例外クラスで、主に、ドメイン層のロジックで発生させる例外である。
java.lang.RumtimeException を継承しているため、デフォルトの動作として、トランザクションは、ロールバックされる。 |
(10)
|
ResultMessages
Notification
Exception
|
結果メッセージ(
ResultMessages )を保持している例外であることを通知するための抽象例外クラスで、共通ライブラリでは、BusinessException と、ResourceNotFoundException が継承している。java.lang.RumtimeException を継承しているため、デフォルトの動作としてはトランザクションはロールバックされる。本例外クラスを継承すると、
ResultMessagesLoggingInterceptor によって、warnレベルのログが出力される。 |
(11)
|
SystemException
|
システム又はアプリケーションの異常を検知した事を通知するための例外クラスで、アプリケーション層又はドメイン層のロジックで発生させる例外である。
java.lang.RumtimeException を継承しているため、デフォルトの動作として、トランザクションは、ロールバックされる。 |
(12)
|
ExceptionCodeProvider
|
例外コードを保持する役割があることを示すインタフェースで、共通ライブラリでは、
SystemException が実装している。本インタフェースを実装した例外クラスを作成すると、共通ライブラリから提供している例外ハンドリング処理にて、例外で保持している例外コードで、そのまま使われる。
|
項番 | クラス | 役割 |
---|---|---|
(13)
|
SystemException
Resolver
|
<mvc:annotation-driven> を指定した際に、自動的に登録されるHanlderExceptionResolver によって、ハンドリングされない例外をハンドリングするためのクラス。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 を使用して出力している。 |
(15)
|
ExceptionLogging
Filter
|
致命的なエラー、Spring MVC管理外で発生する例外を、ログに出力するためのFilterクラス。
ログは、すべてERRORレベルで出力する。
本Filterを使用した場合、致命的なエラー、およびSpring MVC管理外で発生するすべての例外を、ログに出力することができる。
ログは、
ExceptionLogger を使用して出力している。 |
5.7.5.2. SystemExceptionResolverの設定項目について¶
本編で説明していない設定項目について、説明する。 要件に応じて、設定を行うこと。
項番 | 項目名 | プロパティ名 | 説明 | デフォルト値 |
---|---|---|---|---|
(1)
|
結果メッセージの属性名
|
resultMessagesAttribute
|
ビジネス例外に設定されているメッセージ情報として、モデルに設定する際の属性名(String)を指定する。
View(JSP)から結果メッセージにアクセスする際の、属性名となる。
|
resultMessages |
(2)
|
例外コード(メッセージID)の属性名
|
exceptionCode
Attribute
|
例外コード(メッセージID)として、HttpServletRequestに設定する際の属性名(String)を指定する。
View(JSP)から例外コード(メッセージID)にアクセスする際の属性名となる。
|
exceptionCode |
(3)
|
例外コード(メッセージID)のヘッダー名
|
exceptionCode
Header
|
例外コード(メッセージID)として、HttpServletResponseのレスポンスヘッダーに設定する際のヘッダー名(String)を指定する。
|
X-Exception-Code |
(4)
|
例外オブジェクトの属性名
|
exceptionAttribute
|
ハンドリングした例外オブジェクトとして、モデルに設定する際の属性名(String)を指定する。
View(JSP)から例外オブジェクトにアクセスする際の属性名となる。
|
exception |
(5)
|
本ExceptionResolverとして、使用するハンドラー(Controller)のオブジェクト一覧
|
mappedHandlers
|
本ExceptionResolverを使用するハンドラーの、オブジェクト一覧(Set)を指定する。
指定したハンドラーオブジェクトで発生した例外のみ、ハンドリングが行われる。
この設定項目は指定してはいけない。
|
指定なし
指定した場合の動作は、保証しない。
|
(6)
|
本ExceptionResolverを使用するハンドラー(Controller)のクラス一覧
|
mappedHandlerClasses
|
本ExceptionResolverを使用するハンドラーのクラス一覧(Class[])を指定する。
指定したハンドラークラスで発生した例外のみハンドリングが行われる。
この設定項目は指定してはいけない。
|
指定なし
指定した場合の動作は、保証しない。
|
(7)
|
HTTPレスポンスのキャッシュ制御有無
|
preventResponseCaching
|
HTTPレスポンス時のキャッシュ制御の有無(true:有 false:無)を指定する。
true:有を指定すると、キャッシュを無効にするためのHTTPレスポンスヘッダーが追加される。
|
false:無
|
org.terasoluna.gfw.web.exception.SystemExceptionResolver
の設定項目。org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
の設定項目。org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver
の設定項目。5.7.5.2.1. 結果メッセージの属性名¶
spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <!-- omitted --> <property name="resultMessagesAttribute" value="resultMessagesForExceptionResolver" /> <!-- (1) --> <!-- omitted --> </bean>
jsp
<t:messagesPanel messagesAttributeName="resultMessagesForExceptionResolver"/> <!-- (2) -->
項番 説明 (1) 結果メッセージの属性名(resultMessagesAttribute)に、”resultMessagesForExceptionResolver”を指定する。 (2) メッセージ属性名(messagesAttributeName)に、SystemExceptionResolverで設定した属性名を指定する。
5.7.5.2.2. 例外コード(メッセージID)の属性名¶
spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <!-- omitted --> <property name="exceptionCodeAttribute" value="exceptionCodeForExceptionResolver" /> <!-- (1) --> <!-- omitted --> </bean>
jsp
<p> <c:if test="${!empty exceptionCodeForExceptionResolver}"> <!-- (2) --> [${f:h(exceptionCodeForExceptionResolver)}] <!-- (3) --> </c:if> <spring:message code="e.cm.fw.9999" /> </p>
項番 説明 (1) 例外コード(メッセージID)の属性名(exceptionCodeAttribute)に、”exceptionCodeForExceptionResolver”を指定する。 (2) SystemExceptionResolverに設定した値(exceptionCodeForExceptionResolver)を、テスト対象(空チェック対応)の変数名として指定する。 (3) SystemExceptionResolverに設定した値(exceptionCodeForExceptionResolver)を、出力対象の変数名として指定する。
5.7.5.2.3. 例外コード(メッセージID)のヘッダー名¶
spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <!-- omitted --> <property name="exceptionCodeHeader" value="X-Exception-Code-ForExceptionResolver" /> <!-- (1) --> <!-- omitted --> </bean>
項番 説明 (1) 例外コード(メッセージID)のヘッダー名(exceptionCodeHeader)に、”X-Exception-Code-ForExceptionResolver”を指定する。
5.7.5.2.4. 例外オブジェクトの属性名¶
spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <!-- omitted --> <property name="exceptionAttribute" value="exceptionForExceptionResolver" /> <!-- (1) --> <!-- omitted --> </bean>
jsp
<p>[Exception Message]</p> <p>${f:h(exceptionForExceptionResolver.message)}</p> <!-- (2) -->
項番 説明 (1) 例外オブジェクトの属性名(exceptionAttribute)に、”exceptionForExceptionResolver”を指定する。 (2) SystemExceptionResolverに設定した値(exceptionForExceptionResolver)を、例外オブジェクトからメッセージを取得するための変数名として、指定する。
5.7.5.2.5. HTTPレスポンスのキャッシュ制御有無¶
spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <!-- omitted --> <property name="preventResponseCaching" value="true" /> <!-- (1) --> <!-- omitted --> </bean>
項番 説明 (1) HTTPレスポンスのキャッシュ制御有無(preventResponseCaching)に、true:有を指定する。Note
有を指定した場合のHTTPレスポンスヘッダー
HTTPレスポンスのキャッシュ制御有無を有にすると、以下のHTTPレスポンスヘッダーが出力される。
Cache-Control:no-storeCache-Control:no-cacheExpires:Thu, 01 Jan 1970 00:00:00 GMTPragma:no-cache
5.7.5.3. HandlerExceptionResolverLoggingInterceptorの設定項目について¶
本編で説明していない設定項目について、説明する。 要件に応じて、設定を行うこと。
項番 | 項目名 | プロパティ名 | 説明 | デフォルト値 |
---|---|---|---|---|
(1)
|
ログ出力対象から除外する例外クラスの一覧
|
ignoreExceptions
|
HandlerExceptionResolver によってハンドリングされた例外のうち、ログ出力しない例外クラスをリスト形式で指定する。指定した例外クラス及びサブクラスの例外が発生した場合、 本クラスでログの出力は行われない。
本項目に指定する例外クラスは、別の場所(別の仕組み)でログ出力さえっる例外のみ指定すること。
|
ResultMessagesNotificationException.class ResultMessagesNotificationException.class 及びサブクラスの例外は、 ResultMessagesLoggingInterceptor でログ出力されるため、デフォルト設定として除外している。 |
5.7.5.3.1. ログ出力対象から除外する例外クラスの一覧¶
プロジェクトで用意した例外クラスをログ出力対象から除外したい場合は、以下のような設定となる。
- spring-mvc.xml
<bean id="handlerExceptionResolverLoggingInterceptor" class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor"> <property name="exceptionLogger" ref="exceptionLogger" /> <property name="ignoreExceptions"> <set> <!-- (1) --> <value>org.terasoluna.gfw.common.exception.ResultMessagesNotificationException</value> <!-- (2) --> <value>com.example.common.XxxException</value> </set> </property> </bean>
項番 説明 (1) 共通ライブラリのデフォルト設定で指定されているResultMessagesNotificationException
を除外対象に指定する。 (2) プロジェクトで用意した例外クラスを除外対象に指定する。
全ての例外クラスをログ出力対象とする場合は、以下のような設定となる。
- spring-mvc.xml
<bean id="handlerExceptionResolverLoggingInterceptor" class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor"> <property name="exceptionLogger" ref="exceptionLogger" /> <!-- (3) --> <property name="ignoreExceptions"><null /></property> </bean>
項番 説明 (3) ignoreExceptionsプロパティにnull
を指定する。null
を指定すると、全ての例外クラスがログ出力対象となる。
5.7.5.4. DefaultHandlerExceptionResolverで設定されるHTTPレスポンスコードについて¶
DefaultHandlerExceptionResolverでハンドリングされるフレームワーク例外と、HTTPステータスコードのマッピングを、以下に記載する。
項番 | ハンドリングされるフレームワーク例外 | HTTPステータスコード |
---|---|---|
(1)
|
org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException
|
404
|
(2)
|
org.springframework.web.HttpRequestMethodNotSupportedException
|
405
|
(3)
|
org.springframework.web.HttpMediaTypeNotSupportedException
|
415
|
(4)
|
org.springframework.web.HttpMediaTypeNotAcceptableException
|
406
|
(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
|