BeanリファレンスとCreationalContext (3)

「BeanリファレンスとCreationalContext」というテーマで、過去2回に渡ってCreationalContextの役割を調べてきました。

今回はCreationalContextの破棄についてです。拡張モジュールを作るという流れからだいぶ横道に逸れてしまったので、これでCreationalContextをテーマにしたブログはひとまず区切りとしたいと思います。

Beanインスタンス破棄とCreationalContextとの間の関連

Beanのインスタンスが解放される流れを整理してみましょう。コンテキストが終了してから、それに関連付けられたBeanが破棄されるシーケンスは以下のようになります。

1. AbstractContext::destory()
1-1 Contextual::destroy(T instance, CreationalContext<T> creationalContext)
1-1-1.  <<コンテキストの種別に依存した処理>>
1-1-2.  CreationalContext::release()

CDI仕様書では意外なことにContextインタフェースにdestroyのようなpublicメソッドは定義されていません。Weld1.1.2実装では、org.jboss.weld.context.AbstractContextでdestroy()メソッドが定義されています。

Contextual::destroy()の具体例を見てみましょう。前回に引き続き、再びこのブログのBean<MyBean>の定義を見てください。MyBean::destroy()メソッドの引数としてCreationalContextが引き渡されています(これはcreate()で渡されたものと同じインスタンスである必要があります)。destory() メソッド実装内部ではpreDestroy()やdispose()のメソッドの実行後に、CreationalContextのrelease()が呼び出されています。

public void destroy(MyBean instance,
                    CreationalContext ctx) {
 log("Bean::destroy");
 it.preDestroy(instance);
 it.dispose(instance);
 ctx.release();
}

最後の行のrelease()メソッドは、Beanのすべての依存オブジェクトを破棄します。CreationalContextの実装は、release()が呼び出されたタイミングで、その中に蓄えているインスタンスを解放します。

CDIコンテナはどこでCreationalContextを保持するのか

Beanインスタンスが生成されるのはBeanがインジェクトされるときで、そのライフサイクルはコンテキストによって管理されます。CreationalContextが生成されるのは、トップレベルの(言い換えればRootになる)Beanインスタンスが生成されたとき、と考えられます。そして、コンテキストが終了するときに生成したときに使ったCreationalContextを使ってBeanインスタンスを破棄します。

では、一体、CDIコンテナはどこでCreationalContextのインスタンスを保持するのでしょうか。

Weld実装では、org.jboss.weld.context.api.ContextualInstanceがBeanに関する以下の3点の情報を保持していて、CDIコンテキストがこのContextualInstanceの集まりを管理するという構造になっています。

public interface ContextualInstance {
 public T getInstance(); // コンテキストで管理するインスタンス(例: MyBean)

 public CreationalContext getCreationalContext();

 public Contextual getContextual(); // インスタンスを生成するBean (例: Bean)
}

このContextualInstanceは、CDIのコンテキストのget()にアクセスしたときに、コンテキストにバインドするインスタンスが存在しない場合に生成されます(つまり、これまでBeanインスタンスと漠然と呼んでいたものが実はこのような構造を持って実現されていたということです)。Weld実装ではAbstractContext.javaの125行めがこれに相当します。

さて、ここまでWeldのソースを見てきたおかげでCDIコンテキスト周辺の作りが理解できたような気がします。次回は、CDI拡張モジュールを作る上で重要なBeanManagerについて書こうと思います。

BeanリファレンスとCreationalContext (2)

BeanリファレンスとCreationalContextというテーマの続きです。前回のブログで、Beanインスタンスを生成するBean::create()の中で、(1) 実際にBeanインスタンスを生成するproduce()と、(2) インジェクションを実行するinject()の両方にメソッド引数として同じCreationalContextのインスタンスが渡されていていることを確認しました。このCreationalContextの役割を調べるのが今回のテーマです。

CreationalContextは@Dependentのコンテキスト

CreationalContextはDependent擬似スコープを実現するためにコンテナが提供するコンテキストです。

Dependent擬似スコープとは、Application/Session/Requestなどの通常のコンテキストとは異なる特殊なスコープです。この擬似スコープはCDIでのデフォルトスコープになっていて、明示的にスコープ宣言をしない場合はこのDependentスコープが適用されます。複数の異なるインジェクションポイントから同じBeanインスタンスを共有しないことになっています。

インジェクションが発生するとき、インジェクションポイントにはBeanインスタンスではなく、Beanリファレンスが設定されます。Beanリファレンスの内容は、BeanスコープがNormalScope否かによって変わります。

NormalScope

  • Beanリファレンスとしてクライアントプロキシが生成される。

Dependent擬似スコープ

  • BeanリファレンスとしてBeanインスタンスが生成される。
DependentスコープのBeanインスタンスを生成すると仮定しましょう。Beanインスタンスは他のBeanインスタンスを@Injectによって参照しているしているかもしれません。最初のトップレベルのBeanを生成するときにCreationalContextが作られ、生成するメソッド引数として引き渡されます。

さらに、このトップレベルのBeanインスタンスを作成する途中でインジェクションが発生し、参照する先のインスタンス(これもDependentスコープ)が作成された場合を考えます。そのときに発生するインジェクションでは、inject()の引数として同じCreationalContextが引き渡されます。そして、生成されたBeanインスタンスはこのCreationalContextに次々にpush()されます。

こうして、あるBeanインスタンスを生成する過程で、CreationalContextにBeanインスタンスが次々と蓄積されていきます。再帰的に処理が進み、最終的にトップレベルのBeanのcreate()に制御が返ると、CreatinalContextには依存関係にあるBeanインスタンスが登録されているはずです。

Weld 1.1.2でCreationalContextが使われているところ

CreationalContextの具体的な使い方を知るにはWeldのソースを読むのが良いでしょう。まずCreationalContextにBeanインスタンスをpush()をしているところです。

  • ManagedBean.javaの235行を見るとproduce()メソッド内で生成したBeanインスタンスを引数であるCreationalContextにpush()しています。SessionBeanなど他のBeanでも同様です。

次はCreationalContextにpush()したインスタンスを参照するところです。インジェクションのときにBeanリファレンスを作成するので、BeanManager::getInjectableReference()の実装を調べるとよいでしょう。

BeanManagerImpl::getInjectableReference()は内部でBeanManagerImpl::getReference(InjectionPoint injectionPoint, Bean<?> resolvedBean, CreationalContext<?> creationalContext)を呼び出しています。

  • BeanManagerImpl.javaの765行から777行までのコードでは、メソッド引数のcreationalContextにすでにBeanインスタンスが登録されていたら、クライアントプロキシを生成するのではなく、その代わりにcreationalContextにpush()で登録されたインスタンスを返しています。
ここで、なぜクライアントプロキシではなく、その代わりにBeanインスタンスを返すのかを考えてみます。

CreationalContextのもう一つの役割とは

CreationalContextのもう一つの役割は循環依存関係に関わると考えています(前回のブログを見て)。これはCDI仕様書には明確に説明されていないことなので、以下は私個人の解釈になります。鵜呑みにはしないでくださいね。

具体例をもとに考えてみます。仮に、インジェクションによって A → B → C → B のように依存関係が一巡しているとしたら何が起こるでしょうか。Dependent擬似スコープの場合は、循環依存関係があった場合は、同じBeanインスタンスを再び生成しないという明白なメリットがあります。では、NormalScopeの場合はどうでしょうか。以後、A → B → C → BにおけるそれぞれのBeanはNormalScopeであるとします。

CDIのインジェクションは、この順において、A を起点としてB → Cまではクライアントプロキシを生成するでしょう。そして、 C → Bでの2回めのBをインジェクトしようとしたとき、それはCreationalContextを見ることでBが登録済みのBeanインスタンスであることがわかるので、Cに対して(すでに生成済みの)Beanインスタンスをインジェクトすることでしょう。これによって、クライアントプロキシの生成と、プロキシに付随して発生するコンテキストルックアップを防ぐことができるでしょう。

しかし、気になる点があります。A → B → C → Bの依存関係で、Cに対してBのクライアントプロキシではなくBのBeanインスタンスをインジェクトすることによって別の問題は発生しないのでしょうか。

クライアントプロキシの場合は、動的にコンテキストをルックアップするので、前回のスコープが終了している場合には新たにBeanインスタンスが生成されます。一方でBeanインスタンスをインジェクトしてしまうとこのような動的なインスタンスの変更はできません。例えば、CのスコープがSessionScopedで、BのスコープがRequestScopedの場合、WebアプリケーションからBのインスタンスにアクセスするときに毎回新しく生成されなければなりませんが、それには対応できません。

でも、よくよく考えてみると、そのような状況が発生したときには、A → BにおいてBのプロキシにアクセスすると、Bのスコープが切り替わることでBの新しいBeanインスタンスを返すはずです。これによって、(古いB) → C → Bという循環参照構造はゴミになっていることでしょうから、C→Bの参照が古くなってしまうことは気にする必要はありません。もし、問題が発生するとしてら、Cのインスタンスをどこかのフィールドから直接参照している場合です。

@Injectによって生成したBeanインスタンスを@Injectしている以外のフィールドから参照するということは、すでにコンテナによって削除されたはずのBeanインスタンスを掴んでいる可能性があるということです。CDIではBeanインスタンスの生成・削除はコンテナに任せるのが基本になります。コンテキストによって生成したBeanインスタンスをインジェクション以外の手段でフィールド等から参照するのは避けた方がよさそうです。

次回は、Beanインスタンスの削除とCreationalContextとの間の関係を調べます。

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