ProcessInjectionTargetイベント:インジェクションの割り込み

CDI(Contexts and Dependency Injection)仕様でコンテキストと並んで重要なコンセプトはインジェクションです。典型的なインジェクションは次のように実行されます。

  • いつ:対象となるインスタンスが生成されるときに
  • どこで:@Injectが宣言されたフィールドやメソッドに対して
  • だれが:CDIコンテナが
  • なにを:スキャン済みのBeanからその型に合ったBeanを特定し
  • どうやって:コンテキストからBeanインスタンスを取得して注入します

CDI Portable Extensionでは、このインジェクションの機能はInjectionTargetインタフェースによって提供されます。CDIではBeanのcreate()メソッド内でこのInjectionTargetを使ってBeanインスタンスの生成(produce)、破棄(dispose)とインジェクション(inject)を行います。インスタンスに対してpostConstruct, preDestroyのコールバックメソッドを呼び出すメソッドもそれぞれ用意されています。

図. InjectionTargetインタフェース

具体例を見てみましょう。前回のブログでコンテキストの振る舞いについて書きました。次のスタックトレースのMyApplicationContext::get()の箇所を見ると、ManagedBean::create()からManagedBean$ManagedBeanInjectionTarget<T>.produce()が呼び出されています。確かにBeanの実装はInjectionTarget使ってインスタンスを生成しています。

MyBean.() line: 11
NativeConstructorAccessorImpl.newInstance0(Constructor, Object[]) line: not available [native method]
NativeConstructorAccessorImpl.newInstance(Object[]) line: 39
DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 27
Constructor.newInstance(Object...) line: 513
WeldConstructorImpl.newInstance(Object...) line: 239
ConstructorInjectionPoint.newInstance(BeanManagerImpl, CreationalContext) line: 134
ManagedBean.createInstance(CreationalContext) line: 385
ManagedBean$ManagedBeanInjectionTarget.produce(CreationalContext) line: 234
ManagedBean.create(CreationalContext) line: 338 
MyApplicationContext.get(Contextual, CreationalContext) line: 28
ContextBeanInstance.getInstance() line: 99
ProxyMethodHandler.invoke(Object, Method, Method, Object[]) line: 87
MyBean$Proxy$_$$_WeldClientProxy.toString() line: not available

InjectionTargetを実装してProjectInjectionTargetイベントにセットすることでインジェクションの機能を上書きすることができます。これを使ってもともとのインジェクションの機能自体を置き換えることはないと思いますが、インジェクションが発生するタイミングで、その処理をインターセプトして何らかの処理を行うことが可能です。

ProcessInjectionTargetイベント

InjectionTarget実装を設定する先はProcessInjectionTargetイベントです。このイベントは、このブログに書いたようにCDIコンテナのブート時にコンテナによって送信されるイベントになります。

ProcessInjectionTargetイベントは、@Observes ProcessInjectionTarget<T>のようにイベントハンドラを定義しておくと、Beanスキャンの過程でアーカイブに含まれる各Beanごとにイベントを受信できます。

次のコードはインジェクションにかかる時間を計測してログに出力する拡張モジュールのサンプルです。このサンプルはInjectionTarget実装を作成してinject()を上書きし、オリジナルのinject()の前後で時刻を測定することでインジェクションの時間を求めています。

public class InjectionTime implements Extension {

public InjectionTime() {
 log("InjectionTime::constructor");
 }

public  void processInjectionTarget(@Observes ProcessInjectionTarget pit) {
 final AnnotatedType at =pit.getAnnotatedType();
 log("InjectionTime::processInjectionTarget: at=" + at);

final InjectionTarget it = pit.getInjectionTarget();

 InjectionTarget wrapped = new InjectionTarget() {

public void inject(T instance, CreationalContext ctx) {
 log("Begin inject: at=" + at);
 long start = System.currentTimeMillis();
 it.inject(instance, ctx);
 long end = System.currentTimeMillis();
 log("End inject: injectionTime=" + (end - start) + " ms");
 }

public void postConstruct(T instance) {
 it.postConstruct(instance);
 }

public void preDestroy(T instance) {
 it.dispose(instance);
 }

public void dispose(T instance) {
 it.dispose(instance);
 }

public Set getInjectionPoints() {
 return it.getInjectionPoints();
 }

public T produce(CreationalContext ctx) {
 return it.produce(ctx);
 }
 };

pit.setInjectionTarget(wrapped);
 }

この拡張モジュールのログは次のようになります。CDIコンテナによるBeanスキャンのときにProcessInjectionTargetイベントをキャッチしているのがわかります。実際にinject()が呼び出されるのはアプリケーションの実行時になります。

00:31:45,075 INFO  [stdout] (MSC service thread 1-1) CDI: InjectionTime::processInjectionTarget: at=public@MyApplicationScoped class org.tanoseam.beans.MyBean
00:31:45,094 INFO  [stdout] (MSC service thread 1-1) CDI: InjectionTime::processInjectionTarget: at=public class org.jboss.weld.examples.login.Resources
00:31:45,095 INFO  [stdout] (MSC service thread 1-1) CDI: InjectionTime::processInjectionTarget: at=public@SessionScoped @Named class org.jboss.weld.examples.login.Login
00:31:45,097 INFO  [stdout] (MSC service thread 1-1) CDI: InjectionTime::processInjectionTarget: at=public@Default @Named @RequestScoped class org.jboss.weld.examples.login.Credentials
00:31:45,107 INFO  [stdout] (MSC service thread 1-1) CDI: InjectionTime::processInjectionTarget: at=public@Named @RequestScoped class org.jboss.weld.examples.login.ManagedBeanUserManager
00:31:45,112 INFO  [stdout] (MSC service thread 1-1) CDI: InjectionTime::processInjectionTarget: at=public@Entity class org.jboss.weld.examples.login.User
00:31:45,118 INFO  [stdout] (MSC service thread 1-1) CDI: InjectionTime::processInjectionTarget: at=public class org.tanoseam.examples.MyApplicationContext
00:31:51,725 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: Begin inject: at=public@SessionScoped @Named class org.jboss.weld.examples.login.Login
00:31:51,731 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: End inject: injectionTime=5 ms
00:31:51,736 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: Begin inject: at=public@Default @Named @RequestScoped class org.jboss.weld.examples.login.Credentials
00:31:51,737 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: End inject: injectionTime=0 ms
00:31:53,691 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: Begin inject: at=public@Named @RequestScoped class org.jboss.weld.examples.login.ManagedBeanUserManager
00:31:53,696 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: Begin inject: at=public class org.jboss.weld.examples.login.Resources
00:31:53,696 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: End inject: injectionTime=0 ms
00:31:53,706 INFO  [stdout] (http--127.0.0.1-8080-3) CDI: End inject: injectionTime=15 ms

前回と今回のブログで、コンテキストとインジェクションのそれぞれのおおまかな動きが理解できたと思います。でも、シーケンスの細部を見ると、Producer::produce()のパラメーターとして渡されていたCreationalContextがどのような働きをしているかがわかりません(このCreationalContextはContext::get()のパラメーターとしても登場していました)。次回は、インスタンスの生成時に使われるCreationalContextについて書くつもりです。

広告

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中

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