AfterBeanDiscoveryイベント:Beanの登録

今回はBeanの登録処理について書きます。

Beanとは

BeanはCDI仕様の中で中心的なコンセプトです。Beanのインスタンスはコンテキスト上でライフサイクルが管理され、インジェクションがサポートされます。普通のクラスもBeanとして扱われますので、Managed BeanやEJBを含むほとんどすべてのクラスはBeanとして扱われます。

CDIコンテナ内部ではBean<T>というインタフェースによってBeanを管理します。Beanインタフェースは図1のような階層から構成されます。BeanのサブインタフェースとしてInterceptor<T>やDecorator<T>もありますので、それらもBeanとして扱われます。Beanインタフェースの詳細については、Javadocを参照してください。

図1. Bean<T>の階層
Beanの登録

CDI拡張モジュールとしてBeanを提供するのは簡単です。単にMETA-INF/beans.xmlを含むJARを作ればよいからです。しかし、CDIに関係ない既存のJARに含まれるクラスをCDIのBeanとしてコンテナに登録する必要がある場合にはちょっと工夫が必要になります。

CDI拡張モジュールSPIでは、BeforeBeanDiscoveryイベントとAfterBeanDiscoveryイベントの間にアプリケーションアーカイブに含まれるBeanのスキャン(Bean Discovery Process)が行われます。BeforeBeanDiscoveryイベントのハンドラでは、スキャンの前に実装されるべき処理を、AfterBeanDiscoveryイベントのハンドラでは、スキャンの完了後に実装されるべき処理を記述します。

アプリケーションアーカイブに含まれないようなクラスをBeanとして登録するには、AfterBeanDiscovery::addBean()メソッドを使います。このメソッドを使ってBeanインタフェースの無名クラスを定義し、そのインスタンスをAfterBeanDiscoveryイベントに追加します。

次のコードはMyBeanというクラスのインスタンスをBeanとしてCDIコンテナに登録するための例です。MyBean.classはCDI拡張モジュールとは別の既存のJARによって提供されることを想定しています。このJARにはMETA-INF/beans.xmlが含まれていないため、AfterBeanDiscovery::addBean()メソッドを使って拡張モジュール内で明示的に登録しています。

package org.tanoseam.examples;

import org.tanoseam.beans.MyBean;
import javax.annotation.*;
import javax.enterprise.context.*;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.*;
import javax.enterprise.inject.spi.*;
// (略)

public class BeanRegistration implements Extension
{

public BeanRegistration() {
 log("BeanRegistration constructor");
 }

public void afterBeanDiscovery(@Observes AfterBeanDiscovery event,
 BeanManager bm) {
 log("BeanRegistration::AfterBeanDiscovery");

AnnotatedType<MyBean> at = bm.createAnnotatedType(MyBean.class);
 final InjectionTarget<MyBean> it = bm.createInjectionTarget(at);

Bean<MyBean> bean = new Bean<MyBean>() {
 @Override
 public Class<?> getBeanClass() {
 return MyBean.class;
 }

@Override
 public Set<InjectionPoint> getInjectionPoints() {
 return it.getInjectionPoints();
 }

@Override
 public String getName() {
 return "myBean";
 }

@Override
 public Set<Annotation> getQualifiers() {
 Set<Annotation> qualifiers = new HashSet<Annotation>();
 qualifiers.add( new AnnotationLiteral<Default>() {} );
 qualifiers.add( new AnnotationLiteral<Any>() {} );
 return qualifiers;
 }

@Override
 public Class<? extends Annotation> getScope() {
 return SessionScoped.class;
 }

@Override
 public Set<Class<? extends Annotation>> getStereotypes() {
 return Collections.emptySet();
 }

@Override
 public Set<Type> getTypes() {
 Set<Type> types = new HashSet<Type>();
 types.add(MyBean.class);
 types.add(Object.class);
 return types;
 }

@Override
 public boolean isAlternative() {
 return false;
 }

@Override
 public boolean isNullable() {
 return false;
 }

@Override
 public MyBean create(CreationalContext<MyBean> ctx) {
 log("Bean<MyBean>::create");
 MyBean instance = it.produce(ctx);
 it.inject(instance, ctx);
 it.postConstruct(instance);
 return instance;
 }

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

event.addBean(bean);
 }

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

登録対象となるクラスMyBeanの定義は次のようになります。

package org.tanoseam.beans;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class MyBean {

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

@PostConstruct
 public void initialize() {
 log("MyBean::initialize");
 }

@PreDestroy
 public void destroy() {
 log("MyBean::destroy");
 }

public String toString() {
 return "MyBean String";
 }

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

}

このMyBeanが実際にインジェクトできることを確認するため、weldのexamples/jsf/loginプロジェクトのクラスManagedBeanUserManager内でMyBeanのインジェクションを宣言し、getUsers()メソッドの先頭でbeanのtoString()を呼び出すようにしています(インジェクションだけではproxyが作成されるだけなので、MyBean::toString()を呼び出しています)。

@Inject
 private MyBean bean;

// (略)
 public List<User> getUsers() throws Exception
 {

System.out.println("bean=" + bean);

weld-login.warのWEB-INF/ilbには以下のJARを含めるようにします。

  • tanoseam-beans-0.1.jar: このJARはMyBeanを提供します。
  • tanoseam-examples-0.1.jar: このJARはBeanRegistrationを提供します。

以上にように修正したweld-login.warをJBoss AS7にデプロイし、http://localhost:8080/weld-login/を開くと次のような画面が表示されます。

この画面のView usersのリンクを実行すると次のようなログが出力されます。

22:07:57,466 INFO  [stdout] (MSC service thread 1-4) CDI: BeanRegistration::AfterBeanDiscovery
...
22:08:07,646 INFO  [stdout] (http--127.0.0.1-8080-1) CDI: MyBean::constructor
22:08:07,666 INFO  [stdout] (http--127.0.0.1-8080-1) CDI: Bean<MyBean>::create
22:08:07,666 INFO  [stdout] (http--127.0.0.1-8080-1) CDI: MyBean::constructor
22:08:07,667 INFO  [stdout] (http--127.0.0.1-8080-1) CDI: MyBean::initialize
22:08:07,667 INFO  [stdout] (http--127.0.0.1-8080-1) bean=MyBean String
23:23:04,993 INFO  [stdout] (ContainerBackgroundProcessor[StandardEngine[jboss.web]]) CDI: BeanBean<MyBean>::destroy
23:23:04,994 INFO  [stdout] (ContainerBackgroundProcessor[StandardEngine[jboss.web]]) CDI: MyBean::destroy

それではソースコードと比較しながらログの内容を確認しましょう。まず、Beanのcreate()とdestroy()の呼び出しのタイミングを確認してみてください。さらに、MyBean::constructorが2回呼び出されているのも確認できるかと思います。これはこのFAQに書かれているようにCDIコンテナがBeanのインジェクションにproxyを使っていることが原因です。このことからもわかるようにBeanのコンストラクタでは初期化処理を記述しないようにしてください。

広告

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中

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