AfterBeanDiscoveryイベント:コンテキストの追加

CDI(Contexts and Dependency Injection)仕様の主要なテーマは、文字通り、(1) コンテキストと(2) 依存性注入になります。今回は、前者のカスタムコンテキストを追加する方法について書きます。

CDI仕様では、Request, Conversation, Session, Applicationといったコンテキストが提供されていますが、これに独自のカスタムコンテキストを追加するには、AfterBeanDiscoveryイベントのaddScopeメソッドを使います。このメソッドの引数としてカスタムコンテキストのインスタンスを登録します。

次のCDI拡張モジュールContextRegistrationは、MyApplicationContextというカスタムコンテキストをCDIコンテナに登録する例です。

package org.tanoseam.examples;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;

public class ContextRegistration implements Extension {

public void afterBeanDiscovery(@Observes AfterBeanDiscovery event,
 BeanManager bm) {
 log("ContextRegistration::AfterBeanDiscovery");
 event.addContext(new MyApplicationContext());
 }

void log(String messages) {
 System.out.println("CDI: " + messages);
 }
}

MyApplicationContextは@MyApplicationScopedというスコープに対応するコンテキストです。CDIのコンテキストはContextインタフェースを実装する必要があります。

このコンテキストの機能としてはこのスコープで生成されたインスタンスを保持するだけの単純な実装になっています。

注意:このサンプルはCDIの挙動を確認するためにログ出力をするシンプルな実装です。本格的な拡張モジュールを開発するときには、コンテキストの用途に応じて排他制御などを十分に考慮してください。

package org.tanoseam.examples;

import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import java.lang.annotation.Annotation;
import java.util.concurrent.ConcurrentHashMap;

public class MyApplicationContext implements Context {
 ConcurrentHashMap<contextual<?>, Object> instanceMap = new ConcurrentHashMap<contextual<?>, Object>();

@Override
 public Class getScope() {
 log("MyApplicationContext::getScope");
 return MyApplicationScoped.class;
 }

@Override
 public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) {
 log("MyApplicationContext::get(Contextual<T>, CreationalContext<T>)");
 Object instance = instanceMap.get(contextual);
 if (instance != null) {
 log("\tGet from Context: instance=" + instance);
 }
 else {
 if (creationalContext != null) {
 instance = contextual.create(creationalContext);
 }
 log("\tPut to Context: instance=" + instance);
 instanceMap.put(contextual,instance);
 }
 return (T) instance;
 }

@Override
 public <T> T get(Contextual contextual) {
 log("MyApplicationContext::get(Contextual<T>)");
 Object instance = instanceMap.get(contextual);
 log("\tGet from Context: instance=" + instance);
 return (T) instance;
 }

@Override
 public boolean isActive() {
 log("MyApplicationContext::isActive");
 return true;
 }

void log(String messages) {
 System.out.println("CDI: " + messages);
 }
}

@MyApplicationScopedの定義は次のようになります。

package org.tanoseam.examples;

import javax.enterprise.context.NormalScope;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@NormalScope(passivating = false)
public @interface MyApplicationScoped {
}

@MyApplicationScopedの動作を確認するため、前回のブログで作成したMyBeanにこのスコープアノテーションを設定します。こうしておけば、MyBeanのインジェクションが発生したときに、生成されたMyBeanのインスタンスはMyApplicationContextにバインドされるはずです。

package org.tanoseam.beans;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.tanoseam.examples.MyApplicationScoped;

@MyApplicationScoped
public class MyBean {

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

// ...

CDI拡張モジュールtanoseam-examples-0.1.jarとしては、前回のBeanRegistrationではなく、今回のContextRegistrationを起動するように、あらかじめjavax.enterprise.inject.spi.Extensionファイルの内容をorg.tanoseam.examples.ContextRegistrationとなるように修正しておきます(CDI拡張モジュールの構造についてはここを参考にしてください)。

ここまで準備できたところでContextRegistrationモジュールの動作確認です。前回と同様weld-login.warをJBoss AS7にデプロイし、http://localhost:8080/weld-login/を開いてから、View Usersリンクをクリックします。

17:01:26,365 INFO  [stdout] (MSC service thread 1-1) CDI: ContextRegistration::AfterBeanDiscovery
17:01:26,365 INFO  [stdout] (MSC service thread 1-1) CDI: MyApplicationContext::getScope
17:01:46,126 INFO  [stdout] (http--127.0.0.1-8080-2) CDI: MyBean::constructor
17:01:46,157 INFO  [stdout] (http--127.0.0.1-8080-2) CDI: MyApplicationContext::isActive
17:01:46,158 INFO  [stdout] (http--127.0.0.1-8080-2) CDI: MyApplicationContext::get(Contextual<T>)
17:01:46,158 INFO  [stdout] (http--127.0.0.1-8080-2) CDI:       Get from Context: instance=null17:01:46,159 INFO  [stdout] (http--127.0.0.1-8080-2) CDI: MyApplicationContext::get(Contextual<T>, CreationalContext<T>)
17:01:46,159 INFO  [stdout] (http--127.0.0.1-8080-2) CDI: MyBean::constructor
17:01:46,160 INFO  [stdout] (http--127.0.0.1-8080-2) CDI: MyBean::initialize
17:01:46,160 INFO  [stdout] (http--127.0.0.1-8080-2) CDI:       Put to Context: instance=MyBean String
17:01:46,161 INFO  [stdout] (http--127.0.0.1-8080-2) *************** bean=MyBean String

このログを見ると、MBeanのインスタンスが生成されてMyApplicationContextにバインド(つまりPutされるまで)にコンテナとカスタムコンテキストの間で一定のインタラクションがあることがわかります。

CDIコンテナは、コンテキストのisActiveを呼んでから、get(Contextual<T>)を呼び出しています。そして、get(Contextual<T>,CreationContext<T>)の中でMyBeanの生成とコンテキストへのバインドが行われています。

このようなCDIコンテナ、コンテキスト、Beanの間の呼び出し関係はJavaDocやCDI仕様書からはわかりにくい部分です。次回は、Contextインタフェースの各メソッドの意味を整理し、CDIコンテナとコンテキスト間のインタラクションについて引き続き調べていきます。

広告

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中

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