とあるネタからの、スピンオフというか延長線上の話で。
Embedded Cacheのみですが、InfinispanのCDIサポートを試してみました。
CDI Support
http://infinispan.org/docs/6.0.x/user_guide/user_guide.html#_cdi_support
最近Java EE 6を始めたばかりですが、InfinispanとEEの統合ネタを扱うのは初めてですね。アプリケーションサーバには、JBoss AS 7.1.1を使用します。
準備
相変わらず、sbt+xsbt-web-pluginでいきます。
build.sbt
name := "infinispan-cdi" version := "0.0.1-SNAPSHOT" scalaVersion := "2.10.3" organization := "littlewings" seq(webSettings :_*) artifactName := { (version: ScalaVersion, module: ModuleID, artifact: Artifact) => //artifact.name + "." + artifact.extension "javaee6-web." + artifact.extension } resolvers += "Public JBoss Group" at "http://repository.jboss.org/nexus/content/groups/public-jboss" libraryDependencies ++= Seq( "org.eclipse.jetty" % "jetty-webapp" % "9.1.0.v20131115" % "container", "javax" % "javaee-web-api" % "6.0" % "provided", "org.infinispan" % "infinispan-core" % "6.0.0.Final" excludeAll( ExclusionRule(organization = "org.jgroups", name = "jgroups"), ExclusionRule(organization = "org.jboss.marshalling", name = "jboss-marshalling-river"), ExclusionRule(organization = "org.jboss.marshalling", name = "jboss-marshalling"), ExclusionRule(organization = "org.jboss.logging", name = "jboss-logging"), ExclusionRule(organization = "org.jboss.spec.javax.transaction", name = "jboss-transaction-api_1.1_spec") ), "org.infinispan" % "infinispan-cdi" % "6.0.0.Final", "org.jgroups" % "jgroups" % "3.4.1.Final", "org.jboss.spec.javax.transaction" % "jboss-transaction-api_1.1_spec" % "1.0.1.Final", "org.jboss.marshalling" % "jboss-marshalling-river" % "1.3.18.GA", "org.jboss.marshalling" % "jboss-marshalling" % "1.3.18.GA", "org.jboss.logging" % "jboss-logging" % "3.1.2.GA", "net.jcip" % "jcip-annotations" % "1.0" )
CDI用のモジュールを追加しています。
"org.infinispan" % "infinispan-cdi" % "6.0.0.Final",
*自分でEmbeddedCacheManagerとかCacheをきっちりProduces/Disposesする場合は、いらないかも…?
project/plugins.sbt
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.5.0")
今回から、xsbt-web-pluginが0.5.0になりました!
Java EE 6でCDIを使用するので、空ファイルでもbeans.xmlを作成します。
src/main/webapp/WEB-INF/beans.xml
インジェクションしてみる
以下の内容を参考に。
Inject an embedded cache
http://infinispan.org/docs/6.0.x/user_guide/user_guide.html#_inject_an_embedded_cache
確認には、JAX-RSを使うことにします。
src/main/scala/javaee6/web/rest/CacheApplication.scala
package javaee6.web.rest import javax.ws.rs.ApplicationPath import javax.ws.rs.core.Application @ApplicationPath("/") class CacheApplication extends Application { }
InfinispanのCacheをインジェクションする、Resourceクラス。
src/main/scala/javaee6/web/rest/CalcResource.scala
package javaee6.web.rest import javax.inject.Inject import javax.ws.rs.{GET, Path, QueryParam} import org.infinispan.Cache @Path("calc") class CalcResource { @Inject private var cache: Cache[String, Int] = _ @GET @Path("add") def add(@QueryParam("p1") p1: Int, @QueryParam("p2") p2: Int): String = { val key = s"$p1:$p2" cache.containsKey(key) match { case true => s"Result By Cache[${cache.getName}] => ${cache.get(key)}" case false => Thread.sleep(3 * 1000L) val value = p1 + p2 cache.put(key, value) s"Result By Real => $value" } } }
addメソッドでQueryStringからパラメータを受け取り、Cacheにすでに登録されていればその値を使用し、そうでなければ3秒後にCacheに結果を格納しつつレスポンスを返すクラスです。
つまり、初めて計算する値は、3秒かかるわけですね。
では、これをパッケージングして
> packageWar
デプロイ。
$ cp /path/to/javaee6-web.war $JBOSS_HOME/standalone/deployments/
確認してみます。
$ time curl "http://localhost:8080/javaee6-web/calc/add?p1=2&p2=3" Result By Real => 5 real 0m4.422s user 0m0.024s sys 0m0.020s
1回目は、当然時間がかかります。
$ time curl "http://localhost:8080/javaee6-web/calc/add?p1=2&p2=3" Result By Cache[___defaultcache] => 5 real 0m0.045s user 0m0.008s sys 0m0.024s
2度目は、Cacheにエントリがあるので早くなりましたね。なお、Cacheの名前も合わせて返却するようにしています。今回は、デフォルトのCacheが使用されています。
もちろん、パラメータを変えたりすると、また時間がかかるようになります。
$ time curl "http://localhost:8080/javaee6-web/calc/add?p1=2&p2=5" Result By Real => 7 real 0m3.049s user 0m0.016s sys 0m0.016s
ところで、このデフォルトの状態だと再デプロイすると、JMXに重複登録されてしまうためだと思いますが、コケてしまいました…。
ISPN000034: There's already an cache manager instance registered under 'org.infinispan' JMX domain. If you want to allow multiple instances configured with same JMX domain enable 'allowDuplicateDomains' attribute in 'globalJmxStatistics' config element ISPN000034: There's already an cache manager instance registered under 'org.infinispan' JMX domain. If you want to allow multiple instances configured with same JMX domain enable 'allowDuplicateDomains' attribute in 'globalJmxStatistics' config element MSC00004: Failure during stop of service jboss.deployment.unit."javaee6-web.war".WeldService: org.infinispan.jmx.JmxDomainConflictException: Domain already registered org.infinispan when trying to register: type=CacheManager,name="DefaultCacheManager"
これ以降の手順では、EmbeddedCacheManagerを自分で作成してシャットダウンするコードを追加したからだと思いますが、この事象は発生しなくなりました。
EmbeddedCacheManagerを自分で登録する
ドキュメントでは、Configurationをカスタマイズする手順が主に載っていますが、自分は設定ファイルを書く方が好みなので、EmbeddedCacheManagerを触る方を中心に見ていきたいと思います。
Override the default embedded cache manager and configuration
http://infinispan.org/docs/6.0.x/user_guide/user_guide.html#_override_the_default_embedded_cache_manager_and_configuration
EmbeddedCacheManagerの、ProducesとDisposesに対応したクラスを用意。
src/main/scala/javaee6/web/cache/EmbeddedCacheManagerProvider.scala
package javaee6.web.cache import javax.enterprise.context.ApplicationScoped import javax.enterprise.inject.{Disposes, Produces} import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager} class EmbeddedCacheManagerProvider { @Produces @ApplicationScoped def getDefaultEmbeddedCacheManager: EmbeddedCacheManager = new DefaultCacheManager("infinispan.xml") private def stopEmbeddedCacheManager(@Disposes cacheManager: EmbeddedCacheManager): Unit = cacheManager.stop() }
スコープは、ApplicationScopedとしましょう。
設定ファイルも用意しました。
src/main/resources/infinispan.xml
<?xml version="1.0" encoding="UTF-8"?> <infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:infinispan:config:6.0 http://www.infinispan.org/schemas/infinispan-config-6.0.xsd" xmlns="urn:infinispan:config:6.0"> <global> <globalJmxStatistics enabled="true" jmxDomain="org.infinispan" cacheManagerName="DefaultCacheManager" /> <shutdown hookBehavior="REGISTER"/> </global> <default> <expiration lifespan="5000" /> </default> </infinispan>
デフォルト設定として、5秒後にエントリの有効期限が切れるようにしています。
で、パッケージングしてデプロイすると1度アクセスしたエントリは5秒間は高速に動作5秒後には再度時間がかかるようになります。
$ time curl "http://localhost:8080/javaee6-web/calc/add?p1=2&p2=3"
デフォルトのCacheではなく、特定の名前のCacheをインジェクションする
ここまでは、デフォルトのCacheをインジェクションしてきました。最後に、特定の名前のCacheをインジェクションするようにしてみます。
Infinispanのドキュメントには、特定の名前のCacheに対するConfigurationを行ってインジェクションするものはあるのですが、EmbeddedCacheManagerを使うものはなかったので、どうするのかな…?と思っていたのですが。
冷静に考えると、できそうな感じがしたので試してみたら、やっぱりできました。
今回インジェクションする対象のCacheの名前は、「calcCache」とします。では、まずアノテーションを書きましょう。
アノテーションを書く…?ここですぐに、ScalaでJavaで認識できるアノテーションが書けないことに気付き、パタと手が止まりました。
ここまできて、Javaを書けと…?
いやまあ、書きますよ。というわけで書いたのは、こんなアノテーション。
src/main/java/javaee6/web/cache/CalcCache.java
package javaee6.web.cache; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CalcCache { }
ポイントは、@Qualifierアノテーションが付いているところですね。
CacheのProviderとして、こういうクラスを用意。
src/main/scala/javaee6/web/cache/EmbeddedCacheProvider.scala
package javaee6.web.cache import javax.enterprise.context.ApplicationScoped import javax.enterprise.inject.{Disposes, Produces} import javax.inject.Inject import org.infinispan.Cache import org.infinispan.manager.EmbeddedCacheManager class EmbeddedCacheProvider { @Inject private var cacheManager: EmbeddedCacheManager = _ @CalcCache @Produces @ApplicationScoped def getCalcCache: Cache[String, Int] = cacheManager.getCache("calcCache") private def stopEmbeddedCacheManager(@CalcCache @Disposes cache: Cache[String, Int]): Unit = cache.stop }
ここで、先ほど作成した@CalcCacheアノテーションを使用し、生成と破棄を扱っています。
設定ファイルもわかりやすいようにデフォルトはそのままにし、有効期限が5秒なのは「calcCache」のみとしました。
src/main/resources/infinispan.xml
<?xml version="1.0" encoding="UTF-8"?> <infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:infinispan:config:6.0 http://www.infinispan.org/schemas/infinispan-config-6.0.xsd" xmlns="urn:infinispan:config:6.0"> <global> <globalJmxStatistics enabled="true" jmxDomain="org.infinispan" cacheManagerName="DefaultCacheManager" /> <shutdown hookBehavior="REGISTER"/> </global> <default /> <namedCache name="calcCache"> <expiration lifespan="5000" /> </namedCache> </infinispan>
最後に、インジェクションするCacheに@CalcCacheを付与します。
@Path("calc") class CalcResource { @CalcCache @Inject private var cache: Cache[String, Int] = _
これで、パッケージングしてデプロイ。
確認すると
$ time curl "http://localhost:8080/javaee6-web/calc/add?p1=2&p2=3" Result By Cache[calcCache] => 5 real 0m0.055s user 0m0.024s sys 0m0.024s
Cacheの名前が、「calcCache」になっていることが確認できます。もちろん、5秒後には有効期限切れするところも設定できました。