今まで@Injectとか@Qualifierとかを使ったインジェクションは少し試してきましたが、手動でBeanを取得するようなやり方はやってこなかったので、今回試してみることにしました。
Seasar2でいうとS2Containerを直接使う、みたいな感じですね。
CDIだと、BeanManagerというものを使うみたいです。
サンプルを作るにあたり、このあたりを参考にさせていただきました。
BeanManagerでCDIコンテナにアクセスする
http://tanoseam.wordpress.com/2011/11/03/beanmanager/
[Java] BeanManager: Obtain Contextual Reference to Beans (TOTD #215)
http://orablogs-jp.blogspot.jp/2013/07/beanmanager-obtain-contextual-reference.html
BeanManagerからManagedBeanを取得する
http://inya-bauer.blogspot.jp/2011/01/beanmanagermanagedbean.html
準備
とりあえず、依存関係などの準備をしましょう。
今回は、Aqruillianを使わずに素直にWARをアプリケーションサーバにデプロイすることにします。
build.sbt
name := "bean-manager-lookup" version := "0.0.1-SNAPSHOT" scalaVersion := "2.10.3" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation") seq(webSettings: _*) artifactName := { (version: ScalaVersion, module: ModuleID, artifact: Artifact) => //artifact.name + "." + artifact.extension "javaee6-web." + artifact.extension } libraryDependencies ++= Seq( "org.eclipse.jetty" % "jetty-webapp" % "9.1.0.v20131115" % "container", "org.eclipse.jetty" % "jetty-plus" % "9.1.0.v20131115" % "container", "javax" % "javaee-web-api" % "6.0" % "provided" )
xsbt-web-pluginも使用します。
project/plugins.sbt
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.6.0")
Java EE 6でやっているので、CDIを有効化するために、以下のファイルを中身は空で配置。
src/main/webapp/WEB-INF/beans.xml
では、コードを書いていってみましょう。
Bean
今回、Beanとして以下の2つを用意しました。
src/main/scala/org/littlewings/javaee6/service/CalcService.scala
package org.littlewings.javaee6.service class CalcService { def add(left: Int, right: Int): Int = left + right def multiply(left: Int, right: Int): Int = left * right }
EJBではありません。
src/main/scala/org/littlewings/javaee6/service/DateService.scala
package org.littlewings.javaee6.service import java.util.Date import javax.enterprise.context.ApplicationScoped @ApplicationScoped class DateService { private[this] val thisTime: Date = new Date def time: Date = thisTime }
こちらは、@ApplicationScopedアノテーションを付与しました。
これらを、CDIのBeanManagerから取得します。
呼び出し元
WARとしてデプロイするので、リクエストを受け付ける役割はJAX-RSでやることに。Beanの取得も、ここでやります。
まずは有効化。
src/main/scala/org/littlewings/javaee6/jaxrs/JaxrsApplication.scala
package org.littlewings.javaee6.jaxrs import javax.ws.rs.ApplicationPath import javax.ws.rs.core.Application @ApplicationPath("/rest") class JaxrsApplication extends Application
パスは「/rest」で。
BeanManagerを取得するには、JNDIルックアップするか、BeanManager自体を@InjectアノテーションでDIすればよいみたいです。
まずは、JNDIルックアップの方から。
src/main/java/org/littlewings/javaee6/jaxrs/JndiLookupResource.java
package org.littlewings.javaee6.jaxrs; import java.util.Set; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.littlewings.javaee6.service.CalcService; import org.littlewings.javaee6.service.DateService; @Path("jndi") public class JndiLookupResource { @GET @Path("calc") @Produces(MediaType.TEXT_PLAIN) public String calc(@QueryParam("p1") int p1, @QueryParam("p2") int p2) throws NamingException { InitialContext ic = new InitialContext(); BeanManager bm = (BeanManager) ic.lookup("java:comp/BeanManager"); Set<Bean<?>> beans = bm.getBeans(CalcService.class); Bean<?> bean = bm.resolve(beans); CalcService calcService = (CalcService) bm.getReference(bean, CalcService.class, bm.createCreationalContext(bean)); return Integer.toString(calcService.add(p1, p2)) + System.lineSeparator(); } @GET @Path("date") @Produces(MediaType.TEXT_PLAIN) public String date() throws NamingException { InitialContext ic = new InitialContext(); BeanManager bm = (BeanManager) ic.lookup("java:comp/BeanManager"); Set<Bean<?>> beans = bm.getBeans(DateService.class); Bean<?> bean = bm.resolve(beans); DateService dateService = (DateService) bm.getReference(bean, DateService.class, bm.createCreationalContext(bean)); return dateService.time().toString() + System.lineSeparator(); } }
なぜか、ソースコードがJavaになりました。まあ、いろいろあったんですよ…。
BeanManagerを取得したら、BeanManager#getBeansでBean(このBeanは、javax.enterprise.inject.spi.Beanインターフェース)のSetを取得し、BeanManager#resolveで特定、BeanManager#getReferenceでBean(同じくjavax.enterprise...)のインスタンスを取得できるようです。
BeanManager#getBeansには、Qualifierを指定して絞り込めるみたい?また、文字列でBean名を指定できるバージョンもあります。
続いて、@Injectアノテーションで注入するバージョン。
src/main/java/org/littlewings/javaee6/jaxrs/InjectResource.java
package org.littlewings.javaee6.jaxrs; import java.util.Set; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.littlewings.javaee6.service.CalcService; import org.littlewings.javaee6.service.DateService; @Path("inject") public class InjectResource { @Inject private BeanManager bm; @GET @Path("calc") @Produces(MediaType.TEXT_PLAIN) public String calc(@QueryParam("p1") int p1, @QueryParam("p2") int p2) { Set<Bean<?>> beans = bm.getBeans(CalcService.class); Bean<?> bean = bm.resolve(beans); CalcService calcService = (CalcService) bm.getReference(bean, CalcService.class, bm.createCreationalContext(bean)); return Integer.toString(calcService.add(p1, p2)) + System.lineSeparator(); } @GET @Path("date") @Produces(MediaType.TEXT_PLAIN) public String date() { Set<Bean<?>> beans = bm.getBeans(DateService.class); Bean<?> bean = bm.resolve(beans); DateService dateService = (DateService) bm.getReference(bean, DateService.class, bm.createCreationalContext(bean)); return dateService.time().toString() + System.lineSeparator(); } }
これを、JBoss AS 7.1.1にデプロイ。コンテキスト名は、「javaee6-web」とします。
まずは、CalcServiceクラスを使用。
# JNDIルックアップ版 $ curl "http://localhost:8080/javaee6-web/rest/jndi/calc?p1=3&p2=6" 9 # @Inject版 $ curl "http://localhost:8080/javaee6-web/rest/inject/calc?p1=3&p2=6" 9
続いて、DateServiceクラスを使用。こちらは、@ApplicationScopedでしたね。
# JNDIルックアップ版 $ curl "http://localhost:8080/javaee6-web/rest/jndi/date" Sat Feb 08 16:09:30 JST 2014 # @Inject版 $ curl "http://localhost:8080/javaee6-web/rest/inject/date" Sat Feb 08 16:09:30 JST 2014
ちゃんと、同じインスタンスを見てくれてそうですね。
とりあえず、BeanManagerを使ってBeanを取得することができました。
今回書いたサンプルは、こちらにアップしています。
https://github.com/kazuhira-r/javaee6-scala-examples/tree/master/bean-manager-lookup
余談
今回、BeanManagerを使った部分は、最初はScalaではなくJavaで書いていました。が、BeanManager#getBeansの部分から先がうまくいかず、結局断念。
一応、こんなことを書いていました。
src/main/scala/org/littlewings/javaee6/jaxrs/JndiLookupAsScalaResource.scala
package org.littlewings.javaee6.jaxrs import javax.enterprise.inject.spi.{Bean, BeanManager} import javax.naming.InitialContext import javax.ws.rs.{GET, Path, Produces, QueryParam} import javax.ws.rs.core.MediaType import org.littlewings.javaee6.service.{CalcService, DateService} @Path("jndi-as-scala") class JndiLookupResourceAsScala { @GET @Path("calc") @Produces(Array(MediaType.TEXT_PLAIN)) def calc(@QueryParam("p1") p1: Int, @QueryParam("p2") p2: Int): String = { val initialContext = new InitialContext val bm: BeanManager = initialContext.lookup("java:comp/BeanManager").asInstanceOf[BeanManager] // このコードは、コンパイルできない val beans: java.util.Set[Bean[_]] = bm.getBeans(classOf[CalcService]) val bean: Bean[_] = bm.resolve(beans) val calcService = bm .getReference(bean, classOf[CalcService], bm.createCreationalContext(bean)) .asInstanceOf[CalcService] calcService.add(p1, p2).toString } }
これをコンパイルすると、こんなことになります。
> 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)なんでしょうなぁと思いつつ、今回は放棄しましたが…(面倒になって、ちゃんと読んでない)。
More Scala typing issues
http://stackoverflow.com/questions/6141701/more-scala-typing-issues
Problem with bounded type parameterised case class and default args in Scala
http://stackoverflow.com/questions/6135663/problem-with-bounded-type-parameterised-case-class-and-default-args-in-scala
Scala, can't implement generic java method
http://stackoverflow.com/questions/11723211/scala-cant-implement-generic-java-method