CDIコンテキストの振る舞い

拡張モジュールを作ることでカスタムコンテキストを定義できるというのはCDIの大きな魅力のひとつです。Seam 3のfacesモジュールではViewScopedというカスタムスコープが提供されています。自分でも試しにコンテキストを作ってみたいものです。

ここで問題になるのが、いったいどうやったらカスタムコンテキストを作れるのかということです。もちろん、CDIのContextインタフェースを実装して、コンテナに登録するまでは仕様書からわかるのですが、どうやってコンテキストの開始や終了を制御するのかが不明です。知りたいのはコンテキストを開発するときの作法です。

カスタムコンテキストの挙動

前回のブログではMyApplicationContextというコンテキストを定義して、AfterBeanDiscoveryイベントにそれを登録し、実際にそのカスタムコンテキストを動作させて、ログを出力させてみました。ログを見ると、getScope(), isActive(), get()のようなメソッドが次々と呼び出され、その結果、MyBeanのインスタンスが最終的にアクセスされていました。

MyBeanのインスタンスの違いがわかるようにログを出力すると、次のようなになります。今回はログを取り直す際に、MyBeanのコンストラクタとtoString()メソッドでインスタンスのidがわかるようにコードを修正しています。

08:25:26,312 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: MyBean::constructor=org.tanoseam.beans.MyBean$Proxy$_$$_WeldClientProxy@12fa9ae
08:25:26,358 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: MyApplicationContext::isActive
08:25:26,359 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: MyApplicationContext::get(Contextual<T>)
08:25:26,359 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: Get from Context: instance=null
08:25:26,360 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: MyApplicationContext::get(Contextual<T>, CreationalContext<T>)
08:25:26,360 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: MyBean::constructor=org.tanoseam.beans.MyBean@12edf9a
08:25:26,361 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: MyBean::initialize
08:25:26,361 INFO  [stdout] (http–127.0.0.1-8080-1) CDI: Put to Context: instance=org.tanoseam.beans.MyBean@12edf9a
08:25:26,361 INFO  [stdout] (http–127.0.0.1-8080-1) *************** bean=org.tanoseam.beans.MyBean@12edf9a

シーケンス図

ログに出力されたMyApplicationContext関連の挙動をシーケンス図を描いてみたのが下図になります(初回のMyBean::toString()の呼び出しまで)。

図. コンテキスト関連のシーケンス図

この図はCDIコンテナとコンテキストの関係を整理するために代表的なクラスだけをピックアップしてざっくりと描いたものになります(図中ではプロキシから直接コンテキストに呼び出しがあるように読めますが、実際のシーケンス中にはたくさんのクラスが関係しています)。

図中、4.1と4.2で2回get()が呼ばれていますが、それらは引数の異なる別のメソッドです。4.1のgetでコンテキストにバインドしていないことをチェックした後に、4.2のgetを使ってインスタンスを生成しています。
  • 4.1の方はget(Contextual<T> contextual) で、これはコンテキストにバインドされたBeanインスタンスを取得するためのものです
  • 4.2の方はget(Contextual<T> contextual, CreationalContext<T> creationalContext) で、これはコンテキストにバインドされていない場合は、インスタンスを生成します

このシーケンス図のポイントは次の2点です。

  • インジェクトされたMyBeanのインスタンスはプロキシである
  • プロキシにアクセスがあるとコンテキストにバインドしたMyBeanのインスタンスを取得する(バインドしていなければ、インスタンスを生成する)

Contextはコンテキスト管理をカプセル化する

コンテキストのget()メソッドにアクセスすることでBeanのインスタンスを取得できるということは、コンテキストの実装がインスタンスライフサイクルを制御できるということです。コンテキストの実装がインスタンスを破棄すれば、次回コンテキストにアクセスしたときはコンテキストは新規のインスタンスを生成することになるでしょう。
カスタムコンテキストを作るということは、このようなインスタンスの生成・破棄の独自のスコープを新たに定めるということになります。では、一体、どのタイミングでコンテキストにバインドしたインスタンスを破棄するのでしょうか。その答えは、Context実装が独自に定めることになります。例えば、TransactionContextのようなカスタムコンテキストであれば、実際のアプリケーションサーバー上のトランザクションの境界がコンテキストの終了のタイミングを定めることでしょう。
ここで注意しなければならないのは、Context実装のインスタンスは一つで、それ自身はAfterBeanDiscoveryイベントに登録されたまま変わらないということです。概念としてのコンテキストは複数存在したり、寿命が短く何度も新たに生成されるとしても、個々のコンテキストはContext実装内部で管理されます。そこで、CDI仕様は、コンテナがコンテキストにアクセスするときに利用可能なコンテキストがContext実装内部に現在存在するかをチェックしています。これがContext::isActive()メソッドになります。
まとめると、CDI Contextインタフェースは、コンテキストにバインドするインスタンス全体のライフサイクルを管理するものです。Contextインタフェースで要求しているメソッドはisActive()やget()など数少なく、コンテキストの寿命を管理するのはContextの実装になります。Contextの実装はコンテキストが実現しようとしているスコープの性格に応じて多様な実装があり得るはずです。
広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。