CDIの適用条件と順番について、今ひとつわかっていないところがあったので確認してみます。
※というか、JSR-318?
準備
ビルド定義は、以下の通りでScala+sbt。
build.sbt
name := "cdi-interceptor-ordering" 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.scalatest" %% "scalatest" % "2.2.5" % "test" )
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>
Interceptor用のアノテーションを作成する
順番を確認するということなので、Interceptorで使用するアノテーションを2つ用意します。
付与すると、「★」をコンソールに出力するためのアノテーション(Interceptorの定義は後述)。
src/main/java/org/littlewings/javaee7/cdi/PrintStar.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.interceptor.InterceptorBinding; @InterceptorBinding @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PrintStar { }
付与すると、「◯」をコンソールに出力するためのアノテーション(Interceptorの定義は後述)。
src/main/java/org/littlewings/javaee7/cdi/PrintCircle.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.interceptor.InterceptorBinding; @InterceptorBinding @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PrintCircle { }
Interceptorの作成
続いて、作成したアノテーションに対するInterceptorを作成します。
@PrintStarアノテーションに対するInterceptor。
src/main/scala/org/littlewings/javaee7/cdi/PrintStarInterceptor.scala
package org.littlewings.javaee7.cdi import javax.annotation.Priority import javax.enterprise.context.Dependent import javax.interceptor.{InvocationContext, AroundInvoke, Interceptor} @Interceptor @PrintStar @Dependent @SerialVersionUID(1L) class PrintStarInterceptor extends Serializable { @AroundInvoke def invoke(ic: InvocationContext): Any = { println("★★★★★ start.") try { ic.proceed() } finally { println("★★★★★ end.") } } }
@PrintCircleに対するInterceptor。
src/main/scala/org/littlewings/javaee7/cdi/PrintCircleInterceptor.scala
package org.littlewings.javaee7.cdi import javax.annotation.Priority import javax.enterprise.context.Dependent import javax.interceptor.{InvocationContext, AroundInvoke, Interceptor} @Interceptor @PrintStar @Dependent @SerialVersionUID(1L) class PrintCircleInterceptor { @AroundInvoke def invoke(ic: InvocationContext): Any = { println("◯◯◯◯◯ start.") try { ic.proceed() } finally { println("◯◯◯◯◯ end.") } } }
Interceptorを適用するCDI管理Beanを作成して、動かしてみる
ここまでに作成した、Interceptorを適用するためのCDI管理Beanを作成します。
src/main/scala/org/littlewings/javaee7/cdi/MessageService.scala
package org.littlewings.javaee7.cdi import javax.enterprise.context.ApplicationScoped @ApplicationScoped class MessageService { @PrintStar @PrintCircle def print(): Unit = println("Hello World") }
あとは、これらを動かすコードを書いて確認してみます。
src/test/scala/org/littlewings/javaee7/cdi/InterceptorOrderingSpec.scala
package org.littlewings.javaee7.cdi import javax.enterprise.inject.spi.CDI import javax.interceptor.Interceptor import org.jboss.weld.environment.se.Weld import org.scalatest.FunSpec import org.scalatest.Matchers._ class InterceptorOrderingSpec extends FunSpec { describe("Interceptor ordering Spec") { it("apply interceptor") { withWeld { val messageService = CDI.current.select(classOf[MessageService]).get messageService.print() } } } private def withWeld(f: => Unit): Unit = { val weld = new Weld try { weld.initialize() f } finally { weld.shutdown() } } }
この状態でプログラムを実行すると、以下のような出力になります。
Hello World
まだIntereptorは適用されていません。
Interceptorを有効化と順番の制御
Interceptorを有効にするには、以下のどちらかの方法を取ります。
また、この時の設定でInterceptorの適用順も制御することができます。
@Priorityアノテーションを付与する
まずは、Interceptorに@Priorityアノテーションを付与してみます。
@Interceptor @Priority(Interceptor.Priority.APPLICATION) @PrintStar @Dependent @SerialVersionUID(1L) class PrintStarInterceptor extends Serializable {
こちらも。
@Interceptor @Priority(Interceptor.Priority.APPLICATION) @PrintStar @Dependent @SerialVersionUID(1L) class PrintCircleInterceptor {
Priorityは、この時点では両方ともAPPLICATIONとしました。
プログラムを実行すると、結果が以下のように変化します。
◯◯◯◯◯ start. ★★★★★ start. Hello World ★★★★★ end. ◯◯◯◯◯ end.
Interceptorが適用されました。
ここで、Priorityの値をいじってみます。プラス1。
@Interceptor @Priority(Interceptor.Priority.APPLICATION + 1) @PrintStar @Dependent @SerialVersionUID(1L) class PrintStarInterceptor extends Serializable {
こちらは、プラス10。
@Interceptor @Priority(Interceptor.Priority.APPLICATION + 10) @PrintStar @Dependent @SerialVersionUID(1L) class PrintCircleInterceptor {
実行結果。
★★★★★ start. ◯◯◯◯◯ start. Hello World ◯◯◯◯◯ end. ★★★★★ end.
適用順が変わりました。
値が同じになった時は…よくわかりませんが、@Priorityに指定する値が小さい方が先に適用されるようです。
なお、javax.interceptor.Interceptor.Priorityに定義されている値についてですが
http://docs.oracle.com/javaee/7/api/javax/interceptor/Interceptor.Priority.html
以下のようになっています。
定数名 | 値 |
---|---|
PLATFORM_BEFORE | 0 |
LIBRARY_BEFORE | 1000 |
APPLICATION | 2000 |
LIBRARY_AFTER | 3000 |
PLATFORM_AFTER | 4000 |
Java EEで用意されているInterceptor…例えば、javax.transaction.Transactionに対応するInterceptorついては、PLATFORM_BEFORE+200でなければならないようです。
The Transactional interceptors must have a priority of Interceptor.Priority.PLATFORM_BEFORE+200.
http://docs.oracle.com/javaee/7/api/javax/transaction/Transactional.html
蛇足ですが、@Priorityに設定する値を入れ替えると、順番も当然変わります。
@Interceptor @Priority(Interceptor.Priority.APPLICATION + 10) @PrintStar @Dependent @SerialVersionUID(1L) class PrintStarInterceptor extends Serializable {
こちらは、+1。
@Interceptor @Priority(Interceptor.Priority.APPLICATION + 1) @PrintStar @Dependent @SerialVersionUID(1L) class PrintCircleInterceptor {
結果。
◯◯◯◯◯ start. ★★★★★ start. Hello World ★★★★★ end. ◯◯◯◯◯ end.
まあ、元に戻っただけ、とも。
beans.xmlで定義する
続いて、beans.xmlで定義してみます。
@Interceptor // @Priority(Interceptor.Priority.APPLICATION + 1) @PrintStar @Dependent @SerialVersionUID(1L) class PrintStarInterceptor extends Serializable {
こちらも。
@Interceptor // @Priority(Interceptor.Priority.APPLICATION + 10) @PrintStar @Dependent @SerialVersionUID(1L) class PrintCircleInterceptor {
Interceptorが無効になりました。
Hello World
では、beans.xmlにInterceptorを定義してみます。
<interceptors> <class>org.littlewings.javaee7.cdi.PrintStarInterceptor</class> <class>org.littlewings.javaee7.cdi.PrintCircleInterceptor</class> </interceptors>
結果。
★★★★★ start. ◯◯◯◯◯ start. Hello World ◯◯◯◯◯ end. ★★★★★ end.
定義順を変えてみます。
<interceptors> <class>org.littlewings.javaee7.cdi.PrintCircleInterceptor</class> <class>org.littlewings.javaee7.cdi.PrintStarInterceptor</class> </interceptors>
結果。
◯◯◯◯◯ start. ★★★★★ start. Hello World ★★★★★ end. ◯◯◯◯◯ end.
というわけで、XMLで並べた順番ですね。
@Priorityとbeans.xmlを一緒に使った場合は?
beans.xmlに
<interceptors> <class>org.littlewings.javaee7.cdi.PrintCircleInterceptor</class> <class>org.littlewings.javaee7.cdi.PrintStarInterceptor</class> </interceptors>
と書いた状態で、@Priorityも付与してみます。
@Interceptor @Priority(Interceptor.Priority.APPLICATION + 1) @PrintStar @Dependent @SerialVersionUID(1L) class PrintStarInterceptor extends Serializable {
こちらも。
@Interceptor @Priority(Interceptor.Priority.APPLICATION + 10) @PrintStar @Dependent @SerialVersionUID(1L) class PrintCircleInterceptor {
beans.xmlやCircle → Star、@PriorityはStar → Circleの順番です。
結果。
★★★★★ start. ◯◯◯◯◯ start. Hello World ◯◯◯◯◯ end. ★★★★★ end.
@Priorityの順番が優先されているようですね。まあ、混ぜて使うことはあんまりしないと思いますが…。
とりあえず、適用方法と適用順はわかった気がします。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/cdi-interceptor-ordering