CLOVER🍀

That was when it all began.

CDIのBeanManagerを使う(Scalaで)

以前書いたエントリの、リベンジ版です。

元ネタは、こちら。

CDIのBeanManagerを使う
http://d.hatena.ne.jp/Kazuhira/20140208/1391845469

CDIで管理されているBeanを取得する際に、@InjectアノテーションではなくてBeanManagerを直接使った場合について書いています。

このブログでは、Java EE関連のコードをScalaで書いていることが比較的多いですが、上記エントリは当時うまくScalaで書けず、最終的にJavaで妥協して書いたというものになっています。

が、なんかScalaで書けそうな気がしたので再チャレンジすることに。

何にハマっていたかというと、BeanManager#resolveの呼び出しのコンパイルエラー解決ができなかったという話。

こんなコードを書いて、

    val initialContext = new InitialContext
    val bm: BeanManager =
      initialContext.lookup("java:comp/BeanManager").asInstanceOf[BeanManager]

    val beans: java.util.Set[Bean[_]] = bm.getBeans(classOf[/* Beanクラス */])
    val bean: Bean[_] = bm.resolve(beans)

こんなコンパイルエラーを見ていました。

> compile
[info] Compiling 1 Scala source to /xxxxx/target/scala-2.10/classes...
[error] /xxxxx/src/main/scala/org/littlewings/javaee6/jaxrs/JndiLookupAsScalaResource.scala:22: no type parameters for method resolve: (x$1: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]])javax.enterprise.inject.spi.Bean[_ <: X] exist so that it can be applied to arguments (java.util.Set[javax.enterprise.inject.spi.Bean[_]])
[error]  --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: ?X]]
[error]     val bean: Bean[_] = bm.resolve(beans)
[error]                            ^
[error] /xxxx/src/main/scala/org/littlewings/javaee6/jaxrs/JndiLookupAsScalaResource.scala:22: type mismatch;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]]
[error] Note: javax.enterprise.inject.spi.Bean[_] >: javax.enterprise.inject.spi.Bean[_ <: X], but Java-defined trait Set is invariant in type E.
[error] You may wish to investigate a wildcard type such as `_ >: javax.enterprise.inject.spi.Bean[_ <: X]`. (SLS 3.2.10)
[error]     val bean: Bean[_] = bm.resolve(beans)
[error]                                    ^
[error] two errors found
[error] (compile:compile) Compilation failed

SLS 3.2.10ってなんですかー。

で、これを解消しましょう、と。

あと、前回はJava EE6でやっていましたが、今回はEE7(WildFly)になるのと、少しコードを見直すことにしました。

準備

依存関係の定義と、xsbt-web-pluginの設定。
build.sbt

name := "bean-manager-lookup"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.2"

organization := "org.littlewings"

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

incOptions := incOptions.value.withNameHashing(true)

jetty()

artifactName := { (version: ScalaVersion, module: ModuleID, artifact: Artifact) =>
  //artifact.name + "." + artifact.extension
  "javaee7-web." + artifact.extension
}

val jettyVersion = "9.2.3.v20140905"

libraryDependencies ++= Seq(
  "org.eclipse.jetty" % "jetty-webapp" % jettyVersion % "container",
  "org.eclipse.jetty" % "jetty-plus"   % jettyVersion % "container",
  "javax" % "javaee-web-api" % "7.0" % "provided"
)

project/plugins.sbt

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "1.0.0-M6")

CDI管理対象のBeanクラス

CDI管理対象として、こんなクラスを用意。
src/main/scala/org/littlewings/javaee7/service/CalcService.scala

package org.littlewings.javaee7.service

import javax.enterprise.context.RequestScoped

@RequestScoped
class CalcService {
  def add(left: Int, right: Int): Int =
    left + right

  def multiply(left: Int, right: Int): Int =
    left * right
}

src/main/scala/org/littlewings/javaee7/service/DateService.scala

package org.littlewings.javaee7.service

import java.time.LocalDateTime

import javax.enterprise.context.ApplicationScoped

@ApplicationScoped
class DateService {
  private[this] val thisTime: LocalDateTime = LocalDateTime.now

  def time: LocalDateTime =
    thisTime
}

せっかくJava 8になったので、Time APIを使いました。

BeanManagerを使うクラス

で、上記で作成した管理BeanをBeanManagerを使用して取得するクラスを定義します。

前回はJAX-RSリソースクラスの中に含めましたが、今回は切り出しました。結果はこちら。
src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala

package org.littlewings.javaee7.service

import java.lang.annotation.Annotation

import javax.enterprise.inject.spi.Bean
import javax.enterprise.inject.spi.BeanManager
import javax.naming.InitialContext

object ServiceLookup {
  private[this] val ic: InitialContext = new InitialContext

  private[this] lazy val beanManager: BeanManager =
    ic.lookup("java:comp/BeanManager").asInstanceOf[BeanManager]

  def resolve[A](clazz: Class[A], qualifiers: Annotation*): A = {
    val beans = beanManager.getBeans(clazz, qualifiers: _*)
    val bean =
      beanManager
        .resolve[A](beans.asInstanceOf[java.util.Set[Bean[_ <: A]]])

    beanManager
      .getReference(bean, clazz, beanManager.createCreationalContext(bean))
      .asInstanceOf[A]
  }
}

上記コードなら、なんとかコンパイル可能になりました。

JAX-RS関係のクラスの定義

あとは、JAX-RSの有効化とこれらを使うリソースクラスを定義。
src/main/scala/org/littlewings/javaee7/rest/JaxrsApplication.scala

package org.littlewings.javaee7.rest

import javax.ws.rs.ApplicationPath
import javax.ws.rs.core.Application

@ApplicationPath("rest")
class JaxrsApplication extends Application

src/main/scala/org/littlewings/javaee7/rest/ServiceLookupResource.scala

package org.littlewings.javaee7.rest

import java.time.format.DateTimeFormatter

import javax.ws.rs.{GET, Path, Produces, QueryParam}
import javax.ws.rs.core.MediaType

import org.littlewings.javaee7.service.{CalcService, DateService, ServiceLookup}

@Path("service-lookup")
class ServiceLookupResource {
  @GET
  @Path("calc")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def calc(@QueryParam("p1") p1: Int, @QueryParam("p2") p2: Int): String = {
    val calcService = ServiceLookup.resolve(classOf[CalcService])
    calcService.add(p1, p2) + System.lineSeparator
  }

  @GET
  @Path("date")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def date: String = {
    val dateService = ServiceLookup.resolve(classOf[DateService])
    dateService
      .time
      .format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")) +
        System.lineSeparator
  }
}

実行

あとは、WARファイルにパッケージングしてWildFlyにデプロイして動作確認。

CalcServiceを使う方。

$ curl -i 'http://localhost:8080/javaee7-web/rest/service-lookup/calc?p1=5&p2=3'
HTTP/1.1 200 OK
Connection: keep-alive
X-Powered-By: Undertow/1
Server: WildFly/8
Content-Type: text/plain
Content-Length: 2
Date: Sun, 14 Sep 2014 12:21:09 GMT

8

DateServiceを使う方。
*DateServiceは、@ApplicationScopedです

## 1回目
$ curl -i 'http://localhost:8080/javaee7-web/rest/servic
e-lookup/date'
HTTP/1.1 200 OK
Connection: keep-alive
X-Powered-By: Undertow/1
Server: WildFly/8
Content-Type: text/plain
Content-Length: 20
Date: Sun, 14 Sep 2014 12:21:26 GMT

2014/09/14 21:04:58

## 2回目
$ curl -i 'http://localhost:8080/javaee7-web/rest/service-lookup/date'
HTTP/1.1 200 OK
Connection: keep-alive
X-Powered-By: Undertow/1
Server: WildFly/8
Content-Type: text/plain
Content-Length: 20
Date: Sun, 14 Sep 2014 12:21:26 GMT

2014/09/14 21:04:58

OKそうですね!

何が問題だったか

最終的にBeanManager#getBeans、resolveを呼び出す箇所はこうなったわけですが…

    val beans = beanManager.getBeans(clazz, qualifiers: _*)
    val bean =
      beanManager
        .resolve[A](beans.asInstanceOf[java.util.Set[Bean[_ <: A]]])

ここで、BeanManagerのgetBeans、resolveのシグニチャを見てみます。

Set<Bean<?>> getBeans(Type beanType,
                    Annotation... qualifiers)

<X> Bean<? extends X> resolve(Set<Bean<? extends X>> beans)

getBeansの方は、ワイルドカードが適用されたBeanでSetが型パラメータが適用されています。

対して、resolveの方は上限境界がXとなるSet<Bean<? extends X>>を期待している、そういうことになりますね。

で、先ほどのresolveの箇所をいくつか変えてみると…

    val bean =
      beanManager
        .resolve(beans)

コンパイルエラー。

[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:24: no type parameters for method resolve: (x$1: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]])javax.enterprise.inject.spi.Bean[_ <: X] exist so that it can be applied to arguments (java.util.Set[javax.enterprise.inject.spi.Bean[_]])
[error]  --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: ?X]]
[error]         .resolve(beans)
[error]          ^
[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:24: type mismatch;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]]
[error] Note: javax.enterprise.inject.spi.Bean[_] >: javax.enterprise.inject.spi.Bean[_ <: X], but Java-defined trait Set is invariant in type E.
[error] You may wish to investigate a wildcard type such as `_ >: javax.enterprise.inject.spi.Bean[_ <: X]`. (SLS 3.2.10)
[error]         .resolve(beans)
[error]                  ^
[error] two errors found

ワイルドカードでキャストしてみます。

    val bean =
      beanManager
        .resolve(beans.asInstanceOf[java.util.Set[Bean[_]]])

前回は、事実上ここで諦めたわけです。

[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:24: no type parameters for method resolve: (x$1: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]])javax.enterprise.inject.spi.Bean[_ <: X] exist so that it can be applied to arguments (java.util.Set[javax.enterprise.inject.spi.Bean[_]])
[error]  --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: ?X]]
[error]         .resolve(beans.asInstanceOf[java.util.Set[Bean[_]]])
[error]          ^
[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:24: type mismatch;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]]
[error] Note: javax.enterprise.inject.spi.Bean[_] >: javax.enterprise.inject.spi.Bean[_ <: X], but Java-defined trait Set is invariant in type E.
[error] You may wish to investigate a wildcard type such as `_ >: javax.enterprise.inject.spi.Bean[_ <: X]`. (SLS 3.2.10)
[error]         .resolve(beans.asInstanceOf[java.util.Set[Bean[_]]])
[error]                                    ^
[error] two errors found

上限境界入れてキャストしても、やっぱりうまくいきません。

    val bean =
      beanManager
        .resolve(beans.asInstanceOf[java.util.Set[Bean[_ <: A]]])

ここまでは、前回試したのかな…?

[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:24: no type parameters for method resolve: (x$1: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]])javax.enterprise.inject.spi.Bean[_ <: X] exist so that it can be applied to arguments (java.util.Set[javax.enterprise.inject.spi.Bean[_ <: A]])
[error]  --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_ <: A]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: ?X]]
[error]         .resolve(beans.asInstanceOf[java.util.Set[Bean[_ <: A]]])
[error]          ^
[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:24: type mismatch;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_ <: A]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]]
[error]         .resolve(beans.asInstanceOf[java.util.Set[Bean[_ <: A]]])
[error]                                    ^
[error] two errors found

で、最終的に上記状態に、さらに明示的に型パラメータを与えたのが今回のコードです。

    val bean =
      beanManager
        .resolve[A](beans.asInstanceOf[java.util.Set[Bean[_ <: A]]])

上記までの要素のいずれが欠けても、このコードはコンパイルが通りません。

例えば、こういうのはコンパイルが通りません。

    val beans = beanManager.getBeans(clazz, qualifiers: _*).asInstanceOf[java.util.Set[Bean[_]]]
    val bean =
      beanManager
        .resolve[A](beans)

結果。

[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:19: type mismatch;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: A]]
[error] Note: javax.enterprise.inject.spi.Bean[_] >: javax.enterprise.inject.spi.Bean[_ <: A], but Java-defined trait Set is invariant in type E.
[error] You may wish to investigate a wildcard type such as `_ >: javax.enterprise.inject.spi.Bean[_ <: A]`. (SLS 3.2.10)
[error]         .resolve[A](beans)
[error]                     ^
[error] one error found

こういうのもダメでしたよ。

    val bean =
      beanManager
        .resolve[A](beans.asInstanceOf[java.util.Set[Bean[_]]])

なんとなく、BeanManager#resolveのシグニチャをもっと注意して見ていれば、解決できてたような気もします。

なんですけど、Javaってこういうコードでコンパイル通るんですもんね。
*前回のコード

        Set<Bean<?>> beans = bm.getBeans(DateService.class);
        Bean<?> bean = bm.resolve(beans);

Scalaだと、同じコード書いても

    val beans: java.util.Set[Bean[_]] = beanManager.getBeans(classOf[DateService])
    val bean: Bean[_] =  beanManager.resolve(beans)

コンパイルエラーですからね。

[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:28: no type parameters for method resolve: (x$1: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]])javax.enterprise.inject.spi.Bean[_ <: X] exist so that it can be applied to arguments (java.util.Set[javax.enterprise.inject.spi.Bean[_]])
[error]  --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: ?X]]
[error]     val bean: Bean[_] =  beanManager.resolve(beans)
[error]                                      ^
[error] /xxxxx/src/main/scala/org/littlewings/javaee7/service/ServiceLookup.scala:28: type mismatch;
[error]  found   : java.util.Set[javax.enterprise.inject.spi.Bean[_]]
[error]  required: java.util.Set[javax.enterprise.inject.spi.Bean[_ <: X]]
[error] Note: javax.enterprise.inject.spi.Bean[_] >: javax.enterprise.inject.spi.Bean[_ <: X], but Java-defined trait Set is invariant in type E.
[error] You may wish to investigate a wildcard type such as `_ >: javax.enterprise.inject.spi.Bean[_ <: X]`. (SLS 3.2.10)
[error]     val bean: Bean[_] =  beanManager.resolve(beans)
[error]                                              ^
[error] two errors found

そりゃあ、???になるというか…。今考えてみると、なんでこれJava側はコンパイル通るんだろうという感覚の方が…。Xはどこから取ってきたんでしょう、Xは。

まあ、解決できたから、良しとしましょうか。

今回作成したコードは、こちらに置いてあります。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/bean-manager-lookup