CLOVER🍀

That was when it all began.

CDIのInterceptorの適用順を制御する

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で定義してみます。

まず、@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 {

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