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との間の関係を調べます。

広告

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中

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