Infinispanには、Springとの連携(Spring Cache Provider)がけっこう前からあります。Infinispan 5.0.0.Finalの時点では、すでにあったみたいです。
とはいえ、しばらくEmbedded Modeのみだったのですが、8.1.0.FinalでClient/Server(Hot Rod Client)での連携が追加され、最近Infinispan側のSpring連携も進んでいるようなので、ちょっと試してみることにしました。
9.0.0.Finalでは、Spring Sessionの実装も入りそうな雰囲気です。
で、今回作成するコードの実行にはSpring Bootは使いますが、Spring Boot側で提供しているInfinispanに対するautoconfigureについては今回はおいておくものとします。
※Spring IO Platformに入っているInfinispanのバージョンは、ちょっと古め…
あくまで、Infinispan側が提供するSpring Cacheの機能に対する内容が、今回の焦点です。
Embedded Mode/Hot Rod Client両方とも扱います。あと、コードはScalaで書きますが(テストはScalaTest)そのあたりを含めたMavenの設定は書くと長くなるので、気になる方はGitHubにソースコードを置いているのでそちらを参照いただければと思います。
使用するバージョンは、Infinispanについては8.2.5.Final、Spring Bootについては1.4.2.RELEASEを使用します。
Embedded Mode
まずは、Embedded Modeから。ドキュメントは、こちらです。
Using Infinispan as a Spring Cache provider
見てると、なんか古い気がしますが…。
準備
Embedded ModeのInfinispanをSpring Cache Providerとして使うには、「infinispan-spring4-embedded」を使用します。
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-spring4-embedded</artifactId> <version>8.2.5.Final</version> </dependency>
その他、ScalaやSpring Boot、テスト関係を含めた依存関係。
<dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-spring4-embedded</artifactId> <version>8.2.5.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.scalatest</groupId> <artifactId>scalatest_${scala.major.version}</artifactId> <version>3.0.1</version> <scope>test</scope> </dependency>
Cacheを適用するクラスと起動用のクラス
Spring Cacheを適用するクラスは、単純なもので用意。
src/main/scala/org/littlewings/infinispan/spring/CalcService.scala
package org.littlewings.infinispan.spring import java.util.concurrent.TimeUnit import org.springframework.cache.annotation.{CacheConfig, CacheEvict, Cacheable} import org.springframework.stereotype.Service @Service @CacheConfig(cacheNames = Array("calcCache")) class CalcService { @Cacheable def add(a: Int, b: Int): Int = { TimeUnit.SECONDS.sleep(3L) a + b } @CacheEvict def evict(a: Int, b: Int): Unit = () }
あと、@SpringBootApplicationアノテーションと、Cacheを有効にするように@EnableCachingアノテーションを付与したクラスを作成します。
src/main/scala/org/littlewings/infinispan/spring/App.scala
package org.littlewings.infinispan.spring import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.cache.annotation.EnableCaching @SpringBootApplication @EnableCaching class App
CacheManagerの作成
@Beanとして、SpringのCacheManagerを作成します。Embedded Modeの場合は、Infinispan側でSpringEmbeddedCacheManagerというクラスが提供されているので、こちらを使用します。
src/main/scala/org/littlewings/infinispan/spring/Config.scala
package org.littlewings.infinispan.spring import java.util.concurrent.TimeUnit import org.infinispan.configuration.cache.ConfigurationBuilder import org.infinispan.manager.DefaultCacheManager import org.infinispan.spring.provider.SpringEmbeddedCacheManager import org.springframework.cache.CacheManager import org.springframework.context.annotation.{Bean, Configuration} @Configuration class Config { @Bean def cacheManager: CacheManager = { val cacheConfiguration = new ConfigurationBuilder().expiration.lifespan(5L, TimeUnit.SECONDS).build val nativeCacheManager = new DefaultCacheManager nativeCacheManager.defineConfiguration("calcCache", cacheConfiguration) new SpringEmbeddedCacheManager(nativeCacheManager) } }
SpringEmbeddedCacheManagerに対して、InfinispanのEmbeddedCacheManager(DefaultCacheManager)を渡すようにしてあげればOKです。
Cacheの設定は、Local Cacheでexpireを5秒としました。
SpringEmbeddedCacheManagerFactoryBean、ContainerEmbeddedCacheManagerFactoryBeanといったクラスもありますが、今回は省略。
確認(テストコード)
それでは、テストコードを書いて確認してみます。
作成したのは、こちら。
rc/test/scala/org/littlewings/infinispan/spring/SpringEmbeddedCacheTest.scala
package org.littlewings.infinispan.spring import java.util.concurrent.TimeUnit import org.junit.runner.RunWith import org.junit.{Before, Test} import org.scalatest.Matchers import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.cache.CacheManager import org.springframework.test.context.junit4.SpringRunner @RunWith(classOf[SpringRunner]) @SpringBootTest(classes = Array(classOf[App])) class SpringEmbeddedCacheTest extends Matchers { @Autowired var calcService: CalcService = _ @Autowired var cacheManager: CacheManager = _ @Before def setUp(): Unit = cacheManager.getCache("calcCache").clear() protected def sw(fun: => Unit): Long = { val startTime = System.nanoTime fun TimeUnit.SECONDS.convert(System.nanoTime - startTime, TimeUnit.NANOSECONDS) } @Test def embeddedCacheSimpleTest(): Unit = { sw { calcService.add(1, 3) should be(4) } should be >= 3L sw { calcService.add(1, 3) should be(4) } should be < 1L TimeUnit.SECONDS.sleep(5L) sw { calcService.add(1, 3) should be(4) } should be >= 3L } @Test def embeddedCacheEvictTest(): Unit = { sw { calcService.add(1, 3) should be(4) } should be >= 3L calcService.evict(1, 3) sw { calcService.add(1, 3) should be(4) } should be >= 3L } }
それぞれ、Cacheに登録してexpireを待った後にアクセスしたり、Cacheエントリを削除したりするテストです。こんな感じで。
Embedded Mode向けに作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/embedded-spring-cache
Remote(Hot Rod Client)
続いて、Hot Rod ClientでのSpring Cache Provider。
こちらは、ドキュメントに記載のない機能です…。
準備
Hot Rod ClientのInfinispanをSpring Cache Providerとして使うには、「infinispan-spring4-remote」を使用します。
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-spring4-remote</artifactId> <version>8.2.5.Final</version> <exclusions> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> </exclusion> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </exclusion> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> </exclusion> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-client-hotrod</artifactId> <version>8.2.5.Final</version> </dependency> <dependency> <groupId>net.jcip</groupId> <artifactId>jcip-annotations</artifactId> <version>1.0</version> <scope>provided</scope> </dependency>
jcip-annotationsが入っているのは、Scalaの都合上です。
infinispan-spring-remote4の場合は、Hot Rod Clientへの依存関係を別途追加する必要があります。また、Spring Bootで実行するにあたり、Log4j2のSLF4J向けのブリッジがSpring BootデフォルトのSLF4Jと衝突するのでexclusionしてあります。
その他に使用したライブラリは、こちら。
<dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-server-hotrod</artifactId> <version>8.2.5.Final</version> <scope>test</scope> <exclusions> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> </exclusion> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </exclusion> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> </exclusion> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.scalatest</groupId> <artifactId>scalatest_${scala.major.version}</artifactId> <version>3.0.1</version> <scope>test</scope> </dependency>
UnitTest内でInfinspan Hot Rod Serverを使いますが、こちらにもLog4j2が含まれているため、除外。
というか、Log4j2がスコープruntimeでHot Rodに入っているのは、どうしてなんでしょう…?
Cacheを適用するクラスと起動用のクラス
ここは、Embedded Modeと同じ。
src/main/scala/org/littlewings/infinispan/spring/CalcService.scala
package org.littlewings.infinispan.spring import java.util.concurrent.TimeUnit import org.springframework.cache.annotation.{CacheConfig, CacheEvict, Cacheable} import org.springframework.stereotype.Service @Service @CacheConfig(cacheNames = Array("calcCache")) class CalcService { @Cacheable def add(a: Int, b: Int): Int = { TimeUnit.SECONDS.sleep(3L) a + b } @CacheEvict def evict(a: Int, b: Int): Unit = () }
src/main/scala/org/littlewings/infinispan/spring/App.scala
package org.littlewings.infinispan.spring import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.cache.annotation.EnableCaching @SpringBootApplication @EnableCaching class App
CacheManagerの作成
@Beanとして、SpringのCacheManagerを作成します。Remote(Hot Rod Client)の場合は、Infinispan側でSpringRemoteCacheManagerというクラスが提供されているので、こちらを使用します。
src/main/scala/org/littlewings/infinispan/spring/Config.scala
package org.littlewings.infinispan.spring import org.infinispan.client.hotrod.RemoteCacheManager import org.infinispan.client.hotrod.configuration.ConfigurationBuilder import org.infinispan.spring.provider.SpringRemoteCacheManager import org.springframework.cache.CacheManager import org.springframework.context.annotation.{Bean, Configuration} @Configuration class Config { @Bean def cacheManager: CacheManager = { val nativeCacheManager = new RemoteCacheManager(new ConfigurationBuilder().addServer.host("localhost").port(11222).build) new SpringRemoteCacheManager(nativeCacheManager) } }
SpringRemoteCacheManagerには、RemoteCacheManagerを渡せばOKです。
Cacheの設定は、Hot Rodの場合はサーバー側ですね。
こちらにも、SpringRemoteCacheManagerFactoryBeanやContainerRemoteCacheManagerFactoryBeanといったクラスがあったりします。
確認(テストコード)
それでは、テストコードを書いて確認してみます。
Hot Rodの場合はサーバーが必要になるので、UnitTest内でHot Rod Serverを起動/停止するようなコードを書いて実行します。
src/test/scala/org/littlewings/infinispan/spring/SpringRemoteCacheTest.scala
package org.littlewings.infinispan.spring import java.util.concurrent.TimeUnit import org.infinispan.commons.equivalence.AnyServerEquivalence import org.infinispan.configuration.cache.ConfigurationBuilder import org.infinispan.manager.DefaultCacheManager import org.infinispan.server.hotrod.HotRodServer import org.infinispan.server.hotrod.configuration.HotRodServerConfigurationBuilder import org.junit.runner.RunWith import org.junit.{AfterClass, Before, BeforeClass, Test} import org.scalatest.Matchers import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.cache.CacheManager import org.springframework.test.context.junit4.SpringRunner object SpringRemoteCacheTest { var hotRodServer: HotRodServer = _ @BeforeClass def setUpClass(): Unit = { val embeddedCacheManager = new DefaultCacheManager embeddedCacheManager .defineConfiguration( "calcCache", new ConfigurationBuilder() .dataContainer() .keyEquivalence(new AnyServerEquivalence) .valueEquivalence(new AnyServerEquivalence) .expiration .lifespan(5L, TimeUnit.SECONDS) .build ) val hotRodServerHost = "localhost" val hotRodServerPort = 11222 hotRodServer = new HotRodServer hotRodServer.start( new HotRodServerConfigurationBuilder() .host(hotRodServerHost) .port(hotRodServerPort) .build, embeddedCacheManager ) } @AfterClass def tearDownClass(): Unit = hotRodServer.stop } @RunWith(classOf[SpringRunner]) @SpringBootTest(classes = Array(classOf[App])) class SpringRemoteCacheTest extends Matchers { @Autowired var calcService: CalcService = _ @Autowired var cacheManager: CacheManager = _ @Before def setUp(): Unit = cacheManager.getCache("calcCache").clear() protected def sw(fun: => Unit): Long = { val startTime = System.nanoTime fun TimeUnit.SECONDS.convert(System.nanoTime - startTime, TimeUnit.NANOSECONDS) } @Test def remoteCacheSimpleTest(): Unit = { sw { calcService.add(1, 3) should be(4) } should be >= 3L sw { calcService.add(1, 3) should be(4) } should be < 1L TimeUnit.SECONDS.sleep(5L) sw { calcService.add(1, 3) should be(4) } should be >= 3L } @Test def remoteCacheEvictTest(): Unit = { sw { calcService.add(1, 3) should be(4) } should be >= 3L calcService.evict(1, 3) sw { calcService.add(1, 3) should be(4) } should be >= 3L } }
テスト自体は、Embedded Modeと同じです。
最初に、Hot Rod Serverの起動/停止の実装をしてあります。Cacheの設定も、ここでしています。
object SpringRemoteCacheTest { var hotRodServer: HotRodServer = _ @BeforeClass def setUpClass(): Unit = { val embeddedCacheManager = new DefaultCacheManager embeddedCacheManager .defineConfiguration( "calcCache", new ConfigurationBuilder() .dataContainer() .keyEquivalence(new AnyServerEquivalence) .valueEquivalence(new AnyServerEquivalence) .expiration .lifespan(5L, TimeUnit.SECONDS) .build ) val hotRodServerHost = "localhost" val hotRodServerPort = 11222 hotRodServer = new HotRodServer hotRodServer.start( new HotRodServerConfigurationBuilder() .host(hotRodServerHost) .port(hotRodServerPort) .build, embeddedCacheManager ) } @AfterClass def tearDownClass(): Unit = hotRodServer.stop }
結果は、expireの設定がEmbedded Modeの時と同じなので、ほぼ同じテストがパスします、と。
Remote(Hot Rod Client)向けに作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-spring-cache
まとめ
Spring Cache Providerとしての、Infinispanの機能を試してみました。双方の使い方がわかっていれば、わりかし簡単に使えるのではないかなぁと思います。
Infinispan 9.0.0.FinalではSpring Sessionの実装が入りそうなので(Embedded Mode/Remoteともに)、リリースされたらそちらも試してみましょう。