CLOVER🍀

That was when it all began.

Java SE環境でCDIを使う(Weld SE)

CDIの参照実装のWeldですが、Java SE環境でも使えるんですね。どこかで聞いたことがあった気がしますが、忘れてました…。

Weld
http://weld.cdi-spec.org/

そんなに情報はないですが、オフィシャルのドキュメントと

18.4. Java SE
http://docs.jboss.org/weld/reference/latest/en-US/html_single/#d0e5666

こちらを参考にさせていただきました。

JavaSE 環境でCDI(Weld)を使う
http://qiita.com/opengl-8080/items/2f03ab496e871cf32f54

準備

依存関係の定義。
build.sbt

name := "weld-se-example"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.3"

organization := "org.littlewings"

scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked")

libraryDependencies ++= Seq(
  "org.jboss.weld.se" % "weld-se" % "2.1.2.Final",
  "org.scalatest" %% "scalatest" % "2.0" % "test"
)

オフィシャルサイトでは、2.2.0.Alpha1が最新だよと書いていましたが、アルファ版なので今回は2.1.2.Finalを使うようにしました。ScalaTestはテストコード用ですね。

BeanとCDIの有効化

Beanとしては、こんなのを用意。
src/main/scala/org/littlewings/weldse/example/CalcService.scala

package org.littlewings.weldse.example

class CalcService {
  def plus(left: Int, right: Int): Int =
    left + right
}

src/main/scala/org/littlewings/weldse/example/DateService.scala

package org.littlewings.weldse.example

import java.util.Date

import javax.enterprise.context.ApplicationScoped

@ApplicationScoped
class DateService {
  private[this] val thisTime: Date = new Date

  def time: Date = thisTime
}

こちらは、@ApplicationScopedアノテーション付き。

src/main/scala/org/littlewings/weldse/example/FacadeService.scala

package org.littlewings.weldse.example

import javax.inject.Inject

class FacadeService {
  @Inject
  var calcService: CalcService = _

  @Inject
  var dateService: DateService = _
}

こちらは、@Injectで他のBeanを注入するパターン。

あと、中身は空でよいのでCDI有効化のため、以下のファイルを作成します。

src/main/resources/META-INF/beans.xml 

では、動かしてみましょう。

動作確認

動作確認は、ScalaTestを使った単体テストコードで行います。

以下のような雛形を用意。
src/test/scala/org/littlewings/weldse/example/WeldSeSpec.scala

package org.littlewings.weldse.example

import javax.enterprise.inject.Instance

import org.jboss.weld.environment.se.{Weld, WeldContainer}

import org.scalatest.FunSpec
import org.scalatest.Matchers._

class WeldExample extends FunSpec {
  describe("weld-se spec") {
    // ここに、テストコードを書く!!
  }
}

まずは、単純な使い方。

    it("simple use") {
      val weld: Weld = new Weld
      val container: WeldContainer = weld.initialize()

      val instance: Instance[AnyRef] = container.instance
      val calcService = instance.select(classOf[CalcService]).get

      calcService.plus(1, 3) should be (4)

      weld.shutdown()
    }

Weldクラスをnewして、その後WeldContainer、Instanceと取得。Instance#selectからgetすることで、目的のBeanのインスタンスを取得することができます。Instance#select時にはQualifierを渡せそうな感じですね。

最後に、Weldをシャットダウンします。

で、続けて他のテストを。

取得したCalcServiceのインスタンスが、それぞれ別のインスタンスであることの確認。

    it("normal") {
      val weld: Weld = new Weld
      val container: WeldContainer = weld.initialize()

      val instance: Instance[AnyRef] = container.instance
      val calcService1 = instance.select(classOf[CalcService]).get
      val calcService2 = instance.select(classOf[CalcService]).get

      calcService1 eq calcService2 should be (false)

      weld.shutdown()
    }

@ApplicationScopedアノテーションを付与したDateServiceについては、同じインスタンスが取れることの確認。

    it("application-scoped") {
      val weld: Weld = new Weld
      val container: WeldContainer = weld.initialize()

      val dateService1 = container.instance.select(classOf[DateService]).get
      val dateService2 = container.instance.select(classOf[DateService]).get

      dateService1 should be theSameInstanceAs dateService2

      dateService1.time should be (dateService2.time)

      weld.shutdown()
    }

@Injectアノテーションによる、依存関係の解決ができていることの確認。

    it("@Inject resolved") {
      val weld: Weld = new Weld
      val container: WeldContainer = weld.initialize()

      val instance: Instance[AnyRef] = container.instance

      val facadeService = instance.select(classOf[FacadeService]).get
      val dateService = instance.select(classOf[DateService]).get

      facadeService.dateService should be theSameInstanceAs dateService

      facadeService.dateService.time should be (dateService.time)
      facadeService.calcService.plus(1, 5) should be (6)

      weld.shutdown()
    }

これで、Java SE環境でもCDIが使えそうですね。

唯一ハマった点は、最初beans.xmlをsrc/main/resources/META-INFではなくて、src/test/resources/META-INFに作ってしまってBeanが見つけられなかったことくらいですね。単にクラスパスが通ってればいい、というわけではないことがちょっと分かりました。

今回書いたサンプルは、こちらにアップしています。

https://github.com/kazuhira-r/javaee6-scala-examples/tree/master/weld-se-example