ProcessAnnotatedTypeイベント:アノテーションの上書き

AnnotatedTypeはCDIにおいてクラスまたはインタフェースのメタデータを管理するためのものです。AnnotatedTypeからアノテーション、コンストラクタ、フィールド、メソッドの情報を得ることができます(下図参照)。CDI拡張モジュールはProcessAnnotatedTypeイベントによってこのAnnotatedTypeを受け取り、そのラッパークラスを作成してCDIコンテナに登録することで、メタデータを上書きできます。


具体例を見ていきましょう。AnnotatedTypeの上書きをする拡張モジュールの例としてWeld Documentation:16.7. Wrapping an AnnotatedTypeがあります。これは、AnnotatedTypeのアノテーションが@Nameを含む場合にvalue()の内容を上書きするというものです。

もともとの@Namedの定義は次のようになっています。

package javax.inject;

@javax.inject.Qualifier
@java.lang.annotation.Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface Named {

 java.lang.String value() default "";
}

@NamedはBeanに名前をつけるものです。UserManagerというBeanクラスがあったとして、このクラスに@Named(value=”userManager”)または@Name(“userManager”)のように指定すると、指定されたクラスのインスタンスは#{userManasger}のようにEL式から参照することが可能になります。

クラスに対して行われる@Namedのvalueの宣言はJavaソースコード上で定義されるものですが、このvalueの値をCDI拡張モジュールの中で書き換えることができます。アノテーションはインタフェースなのでデフォルト値を上書きすることはできません。しかし、CDIコンテナはAnnotatedTypeを使ってクラスのメタデータを管理していますから、これを書き換えることによってCDIコンテナが使うメタデータを書き換えることができるのです。AnnoatedTypeではアノテーションも管理されているので、@Namedのアノテーションを置き換えることによってアノテーションを書き換えることができるのです。

以下は、Weld Documentationのサンプルを若干修正したコードです(修正部分はコメントで示します)。この拡張モジュールではAnnotatedType::getAnnotation()において、取り出したアノテーションが@Nameであれば(前回のブログで紹介した)AnnotationLiteralを使ってオリジナルのアノテーションを上書きします。@Namedのvalue()メソッドを上書きしている点に注目してください。

package org.tanoseam.examples;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.inject.Named;
import javax.enterprise.event.Observes;
import javax.enterprise.util.AnnotationLiteral;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

public class QualifiedName implements Extension {

    public  void processAnnotatedType(@Observes ProcessAnnotatedType event) {

        final AnnotatedType at = event.getAnnotatedType();

        log("ProcessAnnotatedType: AnnotatedType" + at);

        AnnotatedType wrapped = new AnnotatedType() {
            Set annotations = new HashSet();

            @Override
            public Set> getConstructors() {
                return at.getConstructors();
            }

            @Override
            public Set> getFields() {
                return at.getFields();
            }

            @Override
            public Class getJavaClass() {
                return at.getJavaClass();
            }

            @Override
            public Set> getMethods() {
              return at.getMethods();
            }

            @Override
            public  X getAnnotation(final Class annType) {
              if (Named.class.equals(annType)) {
                  class NamedLiteral extends AnnotationLiteral
                      implements Named {

                      // value()が繰り返し呼ばれるのでキャッシュする
                      String qualifiedName;

                      @Override
                      public String value() {
                          if (qualifiedName == null) {
                               // getClass()ではなく、getJavaClass()を使うように修正
                               Package pkg = at.getJavaClass().getPackage();
                               String unqualifiedName = at.getAnnotation(
                                  Named.class).value();

                              // valueの値が設定されていない場合の対応
                              if (unqualifiedName.isEmpty()) {
                                  String className =
                                        at.getJavaClass().getSimpleName();
                                  unqualifiedName =
                                        StringUtils.uncapitalize(className);
                              }
                              qualifiedName = pkg.getAnnotation(
                                     Named.class).value()
                                     + '.' + unqualifiedName;
                              log("\t unqualifeidName=" + unqualifiedName);
                              log("\t qualifeidName=" + qualifiedName);
                         }
                         return qualifiedName;
                     }
                  }
                 return (X) new NamedLiteral();
                } else {
                   return at.getAnnotation(annType);
                }
            }
            // getAnnotationへの対応
            @Override
            public Set getAnnotations() {
               if (annotations.isEmpty()) {
                   for (Annotation a : at.getAnnotations()) {
                       annotations.add(getAnnotation(a.annotationType()));
                   }
                }
                return annotations;
             }

            @Override
            public Type getBaseType() {
                return at.getBaseType();
            }

            @Override
            public Set getTypeClosure() {
               return at.getTypeClosure();
            }

            @Override
            public boolean isAnnotationPresent(
               Class annType) {
                   return at.isAnnotationPresent(annType);
            }
         };
         event.setAnnotatedType(wrapped);
    }

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

この拡張モジュールではvalue()の定義を上書きして、package-info.javaに@Nameがついていた場合には”パッケージ名”.”Beanインスタンス名”のようにして参照できるように変更しています。

この拡張モジュールを動作させる準備として、あらかじめパッケージに対して@Nameで名前を宣言しておく必要があります。今回はWeld ディストリビューションのexamples/jsf/loginアプリケーションのアーカイブに上のQualifedName拡張モジュールを含ませてみます。パッケージ名を定義するパッケージとして以下のようなsrc/main/java/org/jboss/weld/examples/login/package-info.javaを定義します。

@Named("tanoseam")
package org.jboss.weld.examples.login;

import javax.inject.Named;

以上でorg.jboss.weld.examples.loginパッケージ配下のクラスで@Namedがついているもの(例えばUserManager)は、EL式としては#{tanoseam.userManger}で参照できるようになります。

JBoss AS 7.1CR1b と Weld 1.1.4 を使って実行した結果を以下に示します。

16:07:52,081 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic@Entity class org.jboss.weld.examples.login.User
16:07:52,085 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic@Named @RequestScoped @Default class org.jboss.weld.examples.login.Credentials
16:07:52,089 INFO [stdout] (MSC service thread 1-7) CDI: unqualifeidName=credentials
16:07:52,089 INFO [stdout] (MSC service thread 1-7) CDI: qualifeidName=tanoseam.credentials
16:07:52,091 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypeabstract interface@Named class org.jboss.weld.examples.login.package-info
16:07:52,091 INFO [stdout] (MSC service thread 1-7) CDI: unqualifeidName=tanoseam
16:07:52,091 INFO [stdout] (MSC service thread 1-7) CDI: qualifeidName=tanoseam.tanoseam
16:07:52,092 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic abstract interface class org.jboss.weld.examples.login.UserManager
16:07:52,095 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.jboss.weld.examples.login.Resources
16:07:52,099 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic@Named @Alternative @RequestScoped @Stateful class org.jboss.weld.examples.login.EJBUserManager
16:07:52,099 INFO [stdout] (MSC service thread 1-7) CDI:     unqualifeidName=userManager
16:07:52,100 INFO [stdout] (MSC service thread 1-7) CDI:     qualifeidName=tanoseam.userManager
16:07:52,104 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic@Named @RequestScoped class org.jboss.weld.examples.login.ManagedBeanUserManager
16:07:52,104 INFO [stdout] (MSC service thread 1-7) CDI:     unqualifeidName=userManager
16:07:52,104 INFO [stdout] (MSC service thread 1-7) CDI:     qualifeidName=tanoseam.userManager
16:07:52,108 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic@Named @SessionScoped class org.jboss.weld.examples.login.Login
16:07:52,109 INFO [stdout] (MSC service thread 1-7) CDI:     unqualifeidName=login
16:07:52,109 INFO [stdout] (MSC service thread 1-7) CDI:     qualifeidName=tanoseam.login
16:07:52,138 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.CollectBeanInfo$1
16:07:52,141 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.QualifiedName$1$1NamedLiteral
16:07:52,147 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.BeanRegistration$1
16:07:52,151 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.MyExtension
16:07:52,152 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.ContextRegistration
16:07:52,153 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.QualifiedName
16:07:52,154 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.BeanRegistration$1$1
16:07:52,156 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.BeanRegistration$1$2
16:07:52,158 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.InjectionTime
16:07:52,159 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.CollectBeanInfo
16:07:52,161 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.MyExtension$1
16:07:52,165 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.ContainerLifecycleEvents
16:07:52,168 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.LifecycleLogger
16:07:52,170 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.QualifiedName$1
16:07:52,173 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.LifecycleLogger$1
16:07:52,177 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.MyApplicationContext
16:07:52,180 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedType class org.tanoseam.examples.InjectionTime$1
16:07:52,181 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.examples.BeanRegistration
16:07:52,190 INFO [stdout] (MSC service thread 1-7) CDI: ProcessAnnotatedType: AnnotatedTypepublic class org.tanoseam.beans.MyBean

EJBUserManagerとManagedBeanUserManagerにおいてqualifeidName=tanoseam.userManagerと出力されているところを見てください。Loginサンプルでは、UserManagerインタフェースの実装としてEJBUserManagerとManagedBeanUserManagerの2つがそれぞれ@Named(“userManager”)と宣言されています。EJBUserManagerの方には@Alternativeが宣言されているので、インジェクション時にはManagedBeanUserManagerの方が選択されます。

広告

アノテーションを指定したBeanの検索方法

前回のブログでBeanManagerを紹介しましたが、AnnotationLiteralの使い方の説明が足りなかったと思いますので補足します。

AnnotationLiteralを使うとアノテーションを「もの」として扱える

BeanManagerの使用例として取り上げたBeanManager:: getBeans(Type beanType, Annotation… qualifiers) はメソッド引数として限定子(Qualifier)アノテーションを要求しています。しかし、使用例ではこのメソッド引数としてAnnotationLiteralのインスタンスが指定されていました。

Set<Bean<?>> beans = beanManager.getBeans(Object.class, new AnnotationLiteral<Any>(){});

AnnotationはもともとインタフェースというのがJavaでの定義なので、それをメソッド引数として渡すためにはAnnotationインタフェースを実装した何らかのクラスが必要になります。クラスAnnotationLiteral<T extends Annotation>はCDIによって導入されたものでAnnotationインタフェースの実装をワンライナー(1行プログラム)として書けるようにしたものです。上の例では、アノテーションAnyを引数として渡すためにAnnotationLiteral<Any>のインスタンスを作成しています。

このように、APIを使って実行時にAnnotationを指定するというシーンでAnnotationLiteralが使われます。JavaのアノテーションはそもそもはJavaコード上に@Anyのようにしてメタデータを付加するのに使われるものです。しかし、今回のようにAPIを使ってアノテーションを指定して検索をするような場合にはアノテーションを「もの」として扱える方が便利です。

Instanceを使うとコンテキストからインスタンスを取得できる

AnnotationLiteralを使った別の例を紹介します。CDIにはAPIを使ってコンテキストにバインドしたインスタンスを取得するIntanceというクラスが提供されています。これはCDI拡張モジュール開発者のための機能というよりは、むしろアプリケーション開発向けに提供されている一般的な機能です(詳しくはWeld参照文書を見てください)。

@Inject Instance<PaymentProcessor> paymentProcessorSource;

実際にインスタンスを取得するにはInstanceに対してget()を実行します。get()を実行することで、CDIコンテナは指定されたクラスPaymentProcessorのBeanインスタンスを特定し、その結果を返します。つまり、アプリケーションがインジェクションのタイミングをget()によって制御できるようになります。

PaymentProcessor p = paymentProcessorSource.get();

しかし、コンテキスト上にPaymentProcessortという型の複数のBeanの候補が存在した場合(例えばPaymentProcessorのサブクラスのBeanが複数存在した場合)はCDIコンテナはBeanを1つに特定することができず、結果として例外がスローされます。そこで、次の例のように@Asynchronousという限定子を指定することでユニークがBeanが特定されるようにします。

@Inject @Asynchronous Instance<PaymentProcessor> paymentProcessorSource;

こうすることで例外は避けられるようになりましたが、このようにInstanceの宣言時に限定子を追加していくやり方では、(実行時にContexualなインスタンスを取得するという)Instanceを使う甲斐がありません。Instanceには実行時に検索条件として限定子を指定するselect() というメソッドが提供されていますので、そこでAnnotatlinLiteralを使います。

PaymentProcessor p = paymentProcessorSource
 .select(new AnnotationLiteral<Asynchronous>() {});

BeanManagerとInstanceの例からCDIでBeanの検索をするときにAnnotationLiteralが便利に使えることがわかりました。次回は、CDI拡張モジュールの作り方の話に戻って、AnnotationLiteralを使ってAnnotationの挙動を変更する方法について書くつもりです。

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