CLOVER🍀

That was when it all began.

InfinispanのSpring Session Support(Embedded)を試す

Infinispan 9から、Spring用のモジュールにSpring Sessionのサポートが入りました。

Externalizing session using Spring Session

Embedded Mode、Client/Server Mode両方で使うことができます。

今回は、Embedded Modeで使ってみましょう。

また、Spring Boot向けのモジュールもリリースされているので、こちらも合わせて使ってみたいと思います。

Using Infinispan with Spring Boot

GitHub - infinispan/infinispan-spring-boot: Infinispan Spring Boot

Infinispan: Spring Boot Starters

準備

作成したpom.xmlは、こんな感じ。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>embedded-spring-session</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>

        <scala.version>2.12.2</scala.version>
        <infinispan.version>9.0.3.Final</infinispan.version>
        <infinispan-spring-boot.version>1.0.0.Final</infinispan-spring-boot.version>
        <spring-boot.version>1.5.4.RELEASE</spring-boot.version>
        <spring-session.version>1.3.1.RELEASE</spring-session.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${infinispan.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter</artifactId>
            <version>${infinispan-spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring4-embedded</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>${spring-session.version}</version>
        </dependency>

        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>${scala-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <args>
                        <arg>-Xlint</arg>
                        <arg>-unchecked</arg>
                        <arg>-deprecation</arg>
                        <arg>-feature</arg>
                    </args>
                    <recompileMode>incremental</recompileMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

主要なライブラリのバージョンは、BOMで指定することにしました。

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${infinispan.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

BOMを使う場合は、Spring BootのものよりもInfinispanのものを先に書く必要があります。
でないと、Spring BootがInfinispan 8.2系を引っ張ってきてしまいます。

なお、今回のInfinispanは9.0.3.Finalを使用します。

あとは、InfinispanのSpring Boot StarterモジュールとSpring 4向けのEmbeddedのモジュール、

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter</artifactId>
            <version>${infinispan-spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring4-embedded</artifactId>
        </dependency>

Spring BootのWebモジュールとSpring Sessionを足しておきます。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>${spring-session.version}</version>
        </dependency>

これで、pom.xmlはおしまいです。

サンプルコード

それでは、Spring Sessionを使ったサンプルコードを書いていきます。今回は、セッション内にカウンター的なものを保存するようなものを
書いてみましょう。

Mainクラス。
src/main/scala/org/littlewings/infinispan/spring/App.scala

package org.littlewings.infinispan.spring

import org.infinispan.spring.session.configuration.EnableInfinispanEmbeddedHttpSession
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@EnableInfinispanEmbeddedHttpSession
@SpringBootApplication
class App

object App {
  def main(args: Array[String]): Unit = {
    SpringApplication.run(classOf[App], args: _*)
  }
}

ポイントは、@EnableInfinispanEmbeddedHttpSessionアノテーションを付与しておくことです。@EnableInfinispanEmbeddedHttpSessionでは、
Spring Sessionで使うInfinispanのCache名とセッションの有効期限を設定することができます。

デフォルトでは、Cacheの名前が「sessions」で、有効期限が30分です。
https://github.com/infinispan/infinispan/blob/9.0.3.Final/spring/spring4/spring4-embedded/src/main/java/org/infinispan/spring/session/configuration/EnableInfinispanEmbeddedHttpSession.java

今回は、デフォルトのまま使用します。

あとは、RestControllerとセッション間で共有するBeanを作成します。
src/main/scala/org/littlewings/infinispan/spring/CounterController.scala

package org.littlewings.infinispan.spring

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.{GetMapping, RestController}
import org.springframework.web.context.annotation.SessionScope

import scala.collection.JavaConverters._

@RestController
class CounterController(counter: Counter) {
  @GetMapping(Array("counter/access"))
  def access: java.util.Map[String, AnyRef] = {
    counter.increment()
    Map[String, AnyRef](
      "value" -> Integer.valueOf(counter.value),
      "time" -> counter.time.format(DateTimeFormatter.ISO_DATE_TIME)
    ).asJava
  }
}

@SessionScope
@Component
@SerialVersionUID(1L)
class Counter extends Serializable {
  var value: Int = 0

  val time: LocalDateTime = LocalDateTime.now

  def increment(): Unit = value += 1
}

まあ、レスポンスはMapにしてるんですけど…。

設定ファイルも用意しましょう。Infinispanの設定ファイルは、このように準備。
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:9.0 http://www.infinispan.org/schemas/infinispan-config-9.0.xsd"
        xmlns="urn:infinispan:config:9.0">
    <jgroups>
        <stack-file name="udp" path="default-configs/default-jgroups-udp.xml"/>
    </jgroups>

    <cache-container>
        <transport cluster="test-cluster" stack="udp"/>

        <distributed-cache name="sessions"/>
    </cache-container>
</infinispan>

Spring Session用のCacheは、Distributed Cacheとしました。

この設定ファイルは、infinispan-spring-boot-starterを使用しているので、application.propertiesで指定することができます。
src/main/resources/application.properties

infinispan.embedded.configXml=infinispan.xml

これで、準備はおしまいです。

確認

では、パッケージングしてアプリケーションを起動してみましょう。

$ mvn package

Nodeは、3つとします。

## Node 1
$ java -jar target/embedded-spring-session-0.0.1-SNAPSHOT.jar

## Node 2
$ java -jar target/embedded-spring-session-0.0.1-SNAPSHOT.jar --server.port=8090

## Node 3
$ java -jar target/embedded-spring-session-0.0.1-SNAPSHOT.jar --server.port=8100

クラスタが構成されます。

2017-06-24 18:23:17.668  INFO 20276 --- [xxxxx-50068] o.i.r.t.jgroups.JGroupsTransport         : ISPN000094: Received new cluster view for channel test-cluster: [xxxxx-21343|2] (3) [xxxxx-21343, xxxxx-50068, xxxxx-23324]

curlで各Nodeにアクセスして確認します。

## Node 1
$ curl -c cookie.txt -b cookie.txt -i http://localhost:8080/counter/access
HTTP/1.1 200 
Set-Cookie: SESSION=a39ab7b7-0bac-4d8f-8d4e-c43d8d944bb7; Path=/; HttpOnly
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 24 Jun 2017 09:25:38 GMT

{"value":1,"time":"2017-06-24T18:25:37.962"}

## Node 2
$ curl -c cookie.txt -b cookie.txt -i http://localhost:8090/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 24 Jun 2017 09:25:44 GMT

{"value":2,"time":"2017-06-24T18:25:37.962"}

## Node 3
$ curl -c cookie.txt -b cookie.txt -i http://localhost:8100/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 24 Jun 2017 09:26:33 GMT

{"value":3,"time":"2017-06-24T18:25:37.962"}

## Node 1
$ curl -c cookie.txt -b cookie.txt -i http://localhost:8080/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 24 Jun 2017 09:26:44 GMT

{"value":4,"time":"2017-06-24T18:25:37.962"}

## Node 2
$ curl -c cookie.txt -b cookie.txt -i http://localhost:8090/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 24 Jun 2017 09:26:57 GMT

{"value":5,"time":"2017-06-24T18:25:37.962"}

## Node 3
$ curl -c cookie.txt -b cookie.txt -i http://localhost:8100/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 24 Jun 2017 09:27:05 GMT

{"value":6,"time":"2017-06-24T18:25:37.962"}

Bean作成時に生成された時間も変わっていませんし、インクリメントした値も共有されてそうなのでOKですね。

まとめ

InfinispanのSpring Session向けのモジュールを、Embedded ModeでかつSpring Boot向けのStarterと一緒に試してみました。

Infinispan以外を使う時と同じように、簡単に使える感じなのでいいですね。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/embedded-spring-session