時々ブログや本で目にする、CDIのInjectionPointを使ってそこから取れる情報を見てみました。
よくLoggerを使った例を見かけるので、自分もそれに習って気になるところを試してみます。
InjectionPointを使うと、依存関係を注入する場所のメタデータにアクセスできるようになるようです。
準備
まずは、ビルドの定義。
build.sbt
name := "cdi-injectionpoint" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.6" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") updateOptions := updateOptions.value.withCachedResolution(true) libraryDependencies ++= Seq( "org.jboss.weld.se" % "weld-se" % "2.2.12.Final", "org.jboss.logging" % "jboss-logging" % "3.3.0.Final", "org.slf4j" % "slf4j-api" % "1.7.12", "ch.qos.logback" % "logback-classic" % "1.1.3", "org.scalatest" %% "scalatest" % "2.2.5" % "test" )
Weld SEでやります。また、LoggerはJBoss Logging(裏はSLF4J+Logback)とします。
CDI有効化のため、beans.xmlを用意。
src/main/resources/META-INF/beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="annotated"> </beans>
あとは、Logbackの設定を。
src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="STDOUT"/> </root> </configuration>
単純な例から
それでは、コードを書いていきます。まずは、LoggerのProducerを作成。
src/main/scala/org/littlewings/javaee7/cdi/LoggerProducer.scala
package org.littlewings.javaee7.cdi import javax.enterprise.context.Dependent import javax.enterprise.inject.Produces import javax.enterprise.inject.spi.InjectionPoint import org.jboss.logging.Logger @Dependent class LoggerProducer { @Produces def createLogger(injectionPoint: InjectionPoint): Logger = { val logger = Logger.getLogger(injectionPoint.getMember.getDeclaringClass) logger.infof("annotated = %s", injectionPoint.getAnnotated) logger.infof("annotated.annotations = %s", injectionPoint.getAnnotated.getAnnotations) logger.infof("annotated.baseType = %s", injectionPoint.getAnnotated.getBaseType) logger.infof("annotated.typeClosure = %s", injectionPoint.getAnnotated.getTypeClosure) logger.infof("bean = %s", injectionPoint.getBean) logger.infof("bean.beanClass = %s", injectionPoint.getBean.getBeanClass) logger.infof("bean.injectionPoints = %s", injectionPoint.getBean.getInjectionPoints) logger.infof("bean.name = %s", injectionPoint.getBean.getName) logger.infof("bean.qualifier = %s", injectionPoint.getBean.getQualifiers) logger.infof("bean.scope = %s", injectionPoint.getBean.getScope) logger.infof("bean.stereotypes = %s", injectionPoint.getBean.getStereotypes) logger.infof("bean.types = %s", injectionPoint.getBean.getTypes) logger.infof("member = %s", injectionPoint.getMember) logger.infof("member.declaringClass = %s", injectionPoint.getMember.getDeclaringClass) logger.infof("member.modifiers = %s", injectionPoint.getMember.getModifiers) logger.infof("member.name = %s", injectionPoint.getMember.getName) logger.infof("qualifier = %s", injectionPoint.getQualifiers) logger.infof("type = %s", injectionPoint.getType) logger.infof("delegate = %b", injectionPoint.isDelegate) logger.infof("transient = %b", injectionPoint.isTransient) logger } }
せっかくなので、アクセス時にいろいろログ出力するようにしてみました。
で、このLoggerを@InjectするCDI管理Bean。
src/main/scala/org/littlewings/javaee7/cdi/CalcService.scala
package org.littlewings.javaee7.cdi import javax.enterprise.context.{ApplicationScoped, Dependent} import javax.inject.Inject import org.jboss.logging.Logger trait CalcService { def add(a: Int, b: Int): Int } @Dependent class SimpleCalcService extends CalcService { @Inject private var logger: Logger = _ override def add(a: Int, b: Int): Int = { val result = a + b logger.infof("%d + %d = %d", a, b, result) result } }
インジェクションしたLoggerは、一応使うようにしています。
また、後のために、インターフェース(トレイト)を定義しています。
では、テストコードで動作確認。
src/test/scala/org/littlewings/javaee7/cdi/InjectionPointSpec.scala
package org.littlewings.javaee7.cdi import javax.enterprise.inject.spi.CDI import javax.enterprise.util.AnnotationLiteral import org.jboss.logging.Logger import org.jboss.weld.environment.se.Weld import org.scalatest.FunSpec import org.scalatest.Matchers._ class InjectionPointSpec extends FunSpec { describe("InjectionPoint Spec") { it("trace simple") { withWeld { val calcService = CDI.current.select(classOf[CalcService]).get calcService.add(2, 3) should be(5) } } } protected def withWeld(f: => Unit): Unit = { val weld = new Weld try { weld.initialize() f } finally { weld.shutdown() } } }
結果は、このように。Producerが出力したログを貼り付けています。
annotated = [BackedAnnotatedField] @Inject private org.littlewings.javaee7.cdi.SimpleCalcService.logger annotated.annotations = [@javax.inject.Inject()] annotated.baseType = class org.jboss.logging.Logger annotated.typeClosure = [interface org.jboss.logging.BasicLogger, class java.lang.Object, class org.jboss.logging.Logger, interface java.io.Serializable] bean = Managed Bean [class org.littlewings.javaee7.cdi.SimpleCalcService] with qualifiers [@Any @Default] bean.beanClass = class org.littlewings.javaee7.cdi.SimpleCalcService bean.injectionPoints = [[BackedAnnotatedField] @Inject private org.littlewings.javaee7.cdi.SimpleCalcService.logger] bean.name = null bean.qualifier = [@javax.enterprise.inject.Any(), @javax.enterprise.inject.Default()] bean.scope = interface javax.enterprise.context.Dependent bean.stereotypes = [] bean.types = [interface org.littlewings.javaee7.cdi.CalcService, class org.littlewings.javaee7.cdi.SimpleCalcService, class java.lang.Object] member = private org.jboss.logging.Logger org.littlewings.javaee7.cdi.SimpleCalcService.logger member.declaringClass = class org.littlewings.javaee7.cdi.SimpleCalcService member.modifiers = 2 member.name = logger qualifier = [@javax.enterprise.inject.Default()] type = class org.jboss.logging.Logger delegate = false transient = false
各項目の意味は…出力結果を見ようという感じですが…ちょこっと書いておきます。
- InjectionPoint#getAnnotated … インジェクションされる場所のアノテーションに関する情報が取得可能
- InjectionPoint#getBean … インジェクションされるjavax.enterprise.inject.spi.Beanについての情報が取得可能
- InjectionPoint#getMember … インジェクションされるFieldやMethodなどが取得可能
- InjectionPoint#getType および getQualifier … インジェクションされる型情報および@Qualifierが取得可能
- InjectionPoint#isDelegate … Decoratorが使われているかどうか
- InjectionPoint#isTransient … インジェクションされる場所が、transientかどうか
Loggerで使うなら、InjectionPoint#getMember#getDeclaringClassとかでしょうか。
@Qualifierおよび@Stereotypeを使う
続いて、@Qualifierやjavax.enterprise.inject.spi.Beanから取得できる@Stereotypeがあることから、これらについても試してみます。
アノテーションを定義。@Qualifier。
src/main/java/org/littlewings/javaee7/cdi/ServiceQualifier.java
package org.littlewings.javaee7.cdi; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PACKAGE, ElementType.TYPE}) public @interface ServiceQualifier { }
@Stereotype。
src/main/java/org/littlewings/javaee7/cdi/MyStereotype.java
package org.littlewings.javaee7.cdi; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.enterprise.inject.Alternative; import javax.enterprise.inject.Stereotype; @Inherited @Stereotype @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyStereotype { }
Loggerに付与するQualifier。
src/main/java/org/littlewings/javaee7/cdi/LoggerQualifier.java
package org.littlewings.javaee7.cdi; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PACKAGE, ElementType.TYPE}) public @interface LoggerQualifier { }
Producer。
@LoggerQualifier @Produces def createLoggerWithQualifier(injectionPoint: InjectionPoint): Logger = { val logger = Logger.getLogger(injectionPoint.getMember.getDeclaringClass) logger.infof("annotated = %s", injectionPoint.getAnnotated) logger.infof("annotated.annotations = %s", injectionPoint.getAnnotated.getAnnotations) logger.infof("annotated.baseType = %s", injectionPoint.getAnnotated.getBaseType) logger.infof("annotated.typeClosure = %s", injectionPoint.getAnnotated.getTypeClosure) logger.infof("bean = %s", injectionPoint.getBean) logger.infof("bean.beanClass = %s", injectionPoint.getBean.getBeanClass) logger.infof("bean.injectionPoints = %s", injectionPoint.getBean.getInjectionPoints) logger.infof("bean.name = %s", injectionPoint.getBean.getName) logger.infof("bean.qualifier = %s", injectionPoint.getBean.getQualifiers) logger.infof("bean.scope = %s", injectionPoint.getBean.getScope) logger.infof("bean.stereotypes = %s", injectionPoint.getBean.getStereotypes) logger.infof("bean.types = %s", injectionPoint.getBean.getTypes) logger.infof("member = %s", injectionPoint.getMember) logger.infof("member.declaringClass = %s", injectionPoint.getMember.getDeclaringClass) logger.infof("member.modifiers = %s", injectionPoint.getMember.getModifiers) logger.infof("member.name = %s", injectionPoint.getMember.getName) logger.infof("qualifier = %s", injectionPoint.getQualifiers) logger.infof("type = %s", injectionPoint.getType) logger.infof("delegate = %b", injectionPoint.isDelegate) logger.infof("transient = %b", injectionPoint.isTransient) logger }
CDI管理Bean。
@MyStereotype @ServiceQualifier @ApplicationScoped class WithQualifierStereotypeCalcService extends CalcService { @LoggerQualifier @Inject private var logger: Logger = _ override def add(a: Int, b: Int): Int = { val result = a + b logger.infof("%d + %d = %d", a, b, result) result } }
テストコード。
it("with Qualifier, Stereotypes") { withWeld { val calcService = CDI.current.select(classOf[CalcService], new AnnotationLiteral[ServiceQualifier] {}).get calcService.add(2, 3) should be(5) } }
結果。
annotated = [BackedAnnotatedField] @LoggerQualifier @Inject private org.littlewings.javaee7.cdi.WithQualifierStereotypeCalcService.logger annotated.annotations = [@org.littlewings.javaee7.cdi.LoggerQualifier(), @javax.inject.Inject()] annotated.baseType = class org.jboss.logging.Logger annotated.typeClosure = [interface org.jboss.logging.BasicLogger, class org.jboss.logging.Logger, class java.lang.Object, interface java.io.Serializable] bean = Managed Bean [class org.littlewings.javaee7.cdi.WithQualifierStereotypeCalcService] with qualifiers [@ServiceQualifier @Any] bean.beanClass = class org.littlewings.javaee7.cdi.WithQualifierStereotypeCalcService bean.injectionPoints = [[BackedAnnotatedField] @LoggerQualifier @Inject private org.littlewings.javaee7.cdi.WithQualifierStereotypeCalcService.logger] bean.name = null bean.qualifier = [@org.littlewings.javaee7.cdi.ServiceQualifier(), @javax.enterprise.inject.Any()] bean.scope = interface javax.enterprise.context.ApplicationScoped bean.stereotypes = [interface org.littlewings.javaee7.cdi.MyStereotype] bean.types = [interface org.littlewings.javaee7.cdi.CalcService, class java.lang.Object, class org.littlewings.javaee7.cdi.WithQualifierStereotypeCalcService] member = private org.jboss.logging.Logger org.littlewings.javaee7.cdi.WithQualifierStereotypeCalcService.logger member.declaringClass = class org.littlewings.javaee7.cdi.WithQualifierStereotypeCalcService member.modifiers = 2 member.name = logger qualifier = [@org.littlewings.javaee7.cdi.LoggerQualifier()] type = class org.jboss.logging.Logger delegate = false transient = false
@Qualifierや@Stereotypeが入りました。
手動でルックアップしたらどうなる?
最後、確認してみようということで、@Injectを使わずに手動でCDI管理Beanを取得したらどうなるのか試してみました。
@Qualifierを追加。
src/main/java/org/littlewings/javaee7/cdi/ManualLoggerQualifier.java
package org.littlewings.javaee7.cdi; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PACKAGE, ElementType.TYPE}) public @interface ManualLoggerQualifier { }
Producer。
@ManualLoggerQualifier @Produces def createLoggerWithQualifierFromManual(injectionPoint: InjectionPoint): Logger = { val logger = if (injectionPoint.getMember != null) Logger.getLogger(injectionPoint.getMember.getDeclaringClass) else Logger.getLogger(getClass) logger.infof("annotated = %s", injectionPoint.getAnnotated) if (injectionPoint.getAnnotated != null) { logger.infof("annotated.annotations = %s", injectionPoint.getAnnotated.getAnnotations) logger.infof("annotated.baseType = %s", injectionPoint.getAnnotated.getBaseType) logger.infof("annotated.typeClosure = %s", injectionPoint.getAnnotated.getTypeClosure) } logger.infof("bean = %s", injectionPoint.getBean) if (injectionPoint.getBean != null) { logger.infof("bean.beanClass = %s", injectionPoint.getBean.getBeanClass) logger.infof("bean.injectionPoints = %s", injectionPoint.getBean.getInjectionPoints) logger.infof("bean.name = %s", injectionPoint.getBean.getName) logger.infof("bean.qualifier = %s", injectionPoint.getBean.getQualifiers) logger.infof("bean.scope = %s", injectionPoint.getBean.getScope) logger.infof("bean.stereotypes = %s", injectionPoint.getBean.getStereotypes) logger.infof("bean.types = %s", injectionPoint.getBean.getTypes) logger.infof("member = %s", injectionPoint.getMember) } if (injectionPoint.getMember != null) { logger.infof("member.declaringClass = %s", injectionPoint.getMember.getDeclaringClass) logger.infof("member.modifiers = %s", injectionPoint.getMember.getModifiers) logger.infof("member.name = %s", injectionPoint.getMember.getName) } logger.infof("qualifier = %s", injectionPoint.getQualifiers) logger.infof("type = %s", injectionPoint.getType) logger.infof("delegate = %b", injectionPoint.isDelegate) logger.infof("transient = %b", injectionPoint.isTransient) logger }
このLoggerを直接取得するようにして、動作させてみます。
it("manual lookup") { withWeld { val logger = CDI.current.select(classOf[Logger], new AnnotationLiteral[ManualLoggerQualifier] {}).get logger.infof("get Logger by CDI.") } }
結果ですが、Producerのコードにいろいろifでnullを判定していることからわかるように、けっこうな項目がnullになります。
annotated = null bean = null qualifier = [@org.littlewings.javaee7.cdi.ManualLoggerQualifier()] type = class org.jboss.logging.Logger delegate = false transient = false
まあ、そうですよね、と。このあたりはJSR-346にも書いてあります。
終わり
今回は、InjectionPointを確認してみました。どういう情報が取れるのか確認してみたかったのと、コードを書いていて手動でCDI管理Beanを取得した場合だとどうなるのかな?とか気になったりしましたが、だいたい知りたかったことは確認できた感じです。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/cdi-injectionpoint