以前書いたエントリの、リベンジ版です。
元ネタは、こちら。
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 }
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