DeltaSpikeの例外処理 (2)

前回は、DeltaSpikeの例外処理を使うとハンドラーを定義して、そこで例外処理を記述できることを説明しました。今回は、DeltaSpikeの例外処理において、例外が入れ子になっていたり、同じ例外に複数のハンドラーメソッドが該当するといった、もうすこし複雑なケースについて紹介します。今回のブログ記事に対応するDeltaSpikeのドキュメントはこちらになります。

例外の前処理(@BeforeHandles)

例外ハンドラーのメソッドには@BeforeHandlesと@Handlesの2種類が存在します。同じ例外についてこれらのアノテーションのついたハンドラーメソッドが存在する場合は、@BeforeHandlesついたメソッドが先に実行されます。

@ExceptionHandler
public class MyHandlers
{
   void logExceptions(@BeforeHandles @WebRequest ExceptionEvent evt,
         Logger log)
   {
      log.warn("Something bad happened: " + evt.getException().getMessage());
   }

   void logExceptions(@Handles @WebRequest ExceptionEvent evt,
         Logger log)
   {
      // possibly send a HTTP Error code
   }
}

例外ハンドラーメソッドの実行順の明示的な指定

同じ例外に対して複数の例外ハンドラーメソッドが定義されている場合、どちらを先に実行するかをordinalによって数値で指定することができます。

void handleIOExceptionFirst(@Handles(ordinal = 100) ExceptionEvent evt)
{
   System.out.println("Invoked first");
}

void handleIOExceptionSecond(@Handles ExceptionEvent evt)
{
   System.out.println("Invoked second");
}

例外チェーン処理

Java EEの世界で入れ子になっている例外の例としてはEJBExceptionがあります。例えば、WebアプリからEJBを呼び出したシーンを考えてみましょう。SQLエラーによって発生したEJBExceptionでは、その例外を発生させた原因(cause)としてSQLExceptionが存在するとしましょう。WebアプリはEJBExceptionをキャッチし、その例外のgetCausedByException()メソッドによって原因となる例外を取り出し、そのエラーページに遷移させることができます。でも、この例外の入れ子が何段階も深い場合は、例外処理が複雑になってしまいます。

DeltaSpikeの例外処理では、EJBExceptionと、その原因となっている例外(SQLException)をハンドラ内の別々のメソッドとして処理を定義することができます。そして、最初に原因となった例外のハンドラーメソッドを実行し、次にその外側の例外のハンドラーメソッドを順次実行します。EJBExceptionの例では、まずSQLExceptionのハンドラーメソッドが実行され、次にEJBExceptionのメソッドが実行されます。このような例外の入れ子の構造にしたがって順次例外処理を実行することを例外チェーン処理(Exception Chain Processing)と言います。こうすることで、原因となった例外ごとに異なる振る舞いを書くことが容易になり、EJBを呼び出しているビュー層のコードが非常にすっきりします。

例外処理の順番(まとめ)

DeltaSpikeの例外チェーン処理は次のような順番に実行されます。

  1. 例外スタックの入れ子関係を調べる
  2. ルート例外から処理を開始する
  3. その例外に最も近いタイプの@BeforeHandlesのついた(複数の)メソッドを呼び出す
  4. その例外に最も近いタイプのハンドラを探す
  5. 同一のタイプ用に複数のハンドラーが存在する場合は、最も順序(ordinal)が高いハンドラーを最初に呼び出す
  6. スタックの各例外について上記のステップを繰り返す

DeltaSpikeの例外処理は、例外スタックの入れ子構造のチェーンと、例外クラスのクラス階層の両面から行われます。上のステップは、基本は例外スタックの入れ子をルートからさかのぼって例外を順次処理していきます。その例外に対応するハンドラーメソッドを検索する過程で、例外のクラスとまったく同一でなくとも、そのクラス階層上にある例外クラスで待っているハンドラーメソッドは処理の対象となります。

例外クラスのクラス階層上での例外ハンドラーメソッドの実行順

ステップ3とステップ4において、ハンドラーメソッドで待ち受けている例外クラスが、その例外クラスのメソッドとスローされた例外クラスのスーパークラスのメソッドが両方存在したとします。その場合、どちらが先に呼び出されるかは@BeforeHandlesと@Handlesによって異なります。

  • @BeforeHandlesはクラス階層の上位の例外クラスから下位に向かって順番に実行する
  • @Handlesはクラス階層の下位の例外クラスから上位に向かって順番に実行する

例外クラスがSampleExceptionのスーパークラスとしてSampleSuperExceptionが定義されていたとします。以下の例では、@Handlesで待っているので、例外クラスに近いクラスから上位のクラスに向かって順次実行されていきます(@BeforeHandlesの場合は、呼び出し順は逆になります)。@BeforeHandlesは、ログ出力のときに上位クラスから順番に調べていく、という使い方をするのでしょうね。

void handleIOExceptionFirst(@Handles ExceptionEvent evt)
{
   System.out.println("Invoked first");
}

void handleIOExceptionSecond(@Handles ExceptionEvent evt)
{
   System.out.println("Invoked second");
}