AbstractContextを使ったCDIコンテキストの実装 (2)
2012/10/21 コメントを残す
前回AbstractContextの概要について調べたので、すぐにそれを使ったコンテキストを作ってみたいところですが、まだ細部について理解できていないところもあると思います。今回は、実際に新規にコンテキストを作って見る前にコンテキストが提供すべき機能について調べてみようと思います。
ActiveContextの使用例
DeltaSpikeにおいてActiveContextのテストコードの一部としてDummyContextが作成されています。すでにActiveContextがコンテキストの基本機能を提供しているので、それを継承するDummyContextはすごくシンプルです。
public class DummyContext extends AbstractContext { private boolean active = true; private boolean concurrent; private ContextualStorage storage = null; private BeanManager beanManager; public DummyContext(BeanManager beanManager, boolean concurrent) { super(beanManager); this.concurrent = concurrent; this.beanManager = beanManager; } @Override protected ContextualStorage getContextualStorage(boolean createIfNotExists) { if (storage == null && createIfNotExists) { storage = new ContextualStorage(beanManager, concurrent, isPassivatingScope()); } return storage; } @Override public Class getScope() { return DummyScoped.class; } @Override public boolean isActive() { return active; } }
このDummyContextは一旦作成された後はBeanを保持し続ける作りになっています。そのコンストラクタは DummyContext(BeanManager beanManager, boolean concurrent) となっているので、第二引数にtrueを渡すことでマルチスレッドに対応させることもできます。 ここで注目したいのは DummyContext の最後のメソッドである isActive() です。これは常に true を返せば良いものでは無いと思います。どんなとき true を返し、どんなとき false を返すのか。ちょっと、isActive() について調べてみましょう。
アクティブコンテキストとは何か
CDIコンテキストにはアクティブが否かを調べるための isActive() というメソッドがあります。コンテキストはどんなときにアクティブで、どんなときにアクティブでないのか。CDI 1.0仕様書 6.2 には isActive() について次のように記されています。「カレントスレッド」というのがミソです。
At a particular point in the execution of the program a context object may be active with respect to the current thread. When a context object is active the isActive() method returns true. Otherwise, we say that the context object isinactive and the isActive() method returns false.
訳:プログラムの実行中のある特定の点で、コンテキストはカレントスレッドについてアクティブになることができる。コンテキストはisActive() メソッドが true を返すときはアクティブである。さもなければ、そのコンテキストオブジェクトはインアクティブで、isActive() メソッドは false を返す。
さらに、CDI 1.0 仕様書 6.5.1 には次のようにも書かれています。
From time to time, the container must obtain an active context object for a certain scope type. The container must search for an active instance of Context associated with the scope type. (中略). If there is exactly one active instance of Context associated with the scope type, we say that the scope is active.
訳:コンテナはスコープに対応するアクティブコンテキストを保持しなければならないときがある。コンテナはスコープに関連したコンテキストのアクティブなインスタンスを検索しなければならない。(中略)そのスコープタイプに関連するコンテキストのアクティブインスタンスが一つしか存在しないのであれば、そのスコープはアクティブであるという。
また、CDI 1.0仕様書 6.7 には次のように組み込みコンテキストのアクティブな様子についても書かれています。
The built-in context object is active during servlet, web service and EJB invocations, or in the case of the conversation context object, for JSF requests.
訳:サーブレット、Webサービス、EJBの呼び出しの間、または、conversationコンテキストについてはJSFリクエストの実行中は、組み込みコンテキストはアクティブである。
以上から、リクエストの種類はさて置き、リクエストを実行中の状態を管理するためのものがアクティブコンテキストと言えそうです。
組み込みコンテキストがアクティブか否かの判断
では次に、組み込みコンテキストについてアクティブコンテキストの実装について考えてみましょう。
RequestコンテキストやConversationコンテキストの場合、HTTPリクエストの実行に対応するのはスレッドですから、スレッドごとにBeanを保持するための独立した領域を用意することになります。これは、JavaであればThreadLocalが自然な実装になるでしょう。Conversationコンテキストが複数のリクエストをまたがって存在する場合(long-running conversation)は、Conversation ContextはHTTP Session内に保存され、リクエストパラメタとして渡ってくるConversation ID (cid) によって識別されるはずです。cidに対応したConversation Contextが存在しない場合はisActive() はfalseを返すでしょう。
一方で、SessionコンテキストやApplicationコンテキストのように複数のスレッドで共有されるコンテキストについては、スレッドごとに独立した領域を用意する必要はありません。このようなタイプのコンテキストについては isActive() は多くの場合はtrueを返すでしょう。
いつ始まって、いつ終わるのか
Beanのインジェクションが起こるタイミングで、コンテキストにそのBeanがすでに登録されているか否かがチェックされます。その際、コンテキストがアクティブでなければContextNotActiveExceptionがスローされます。しかし、そのコンテキストが初期化・破棄されるタイミングはコンテキストの種類によって様々です。
組み込みコンテキストの場合は、コンテナはRequestコンテキストやSessionコンテキストはServletListenerによってそれぞれのコンテキストが初期化・破棄されるタイミングを知ることができます。ViewScopedコンテキストの場合は、初期化のタイミングは特になく、isActive() でUIViewRootが取得できるか否かでアクティブかどうかを決めています。破棄のタイミングは、JSFコンテナからのViewが破棄されるイベント(PreDestroyViewMapEvent)を受信したときです。
これらのコンテキストでは、HTTPリクエストとRequestコンテキストのように、Java EEの世界にコンテキストと対応するオブジェクトが存在していて、コンテキストとその実体のライフサイクルが同期するように風に作られています。したがって、組み込みコンテキストの初期化・破棄のタイミングはその実体が初期化・破棄されるタイミングでコンテキストに通知されるようになっています。
今回調べた範囲でコンテキストと実体の間の対応関係が理解できました。コンテキストがアクティブというのは、カレントスレッドがこの実体にアクセスできるということだと考えると納得ができます。また、コンテキストと実体の間の呼び出し関係は実装依存(密結合)になるため、両者の間は標準的なAPIとして一般化されていないのだと思います。そして、そこがコンテキストの初期化と破棄を理解する上でわかりにくくしている一因のように思います。