#chiroito ’s blog

Java を中心とした趣味の技術について

Infinispan Hot RodのDistributed-cacheで自作のエンティティを使う

インメモリデータグリッドを使用するにあたり分散キャッシュを使って自作のデータ型となるエンティティを使うことは避けられないでしょう。今回はHot Rodを使ったDistributed-cacheでどうやって自作のエンティティを使うかを紹介します。

オブジェクトを読み書きするためにはオブジェクトのシリアライズ/デシリアライズが必要になります。 Infinispan を分散キャッシュとして使うにはネットワーク通信が必要ですので、シリアライズ/デシリアライズが必要となります。サーバ内ではオブジェクトはシリアライズされた状態で格納されています。そのため、サーバ側でデシリアライズする必要が無ければクライアント側だけにシリアライズ/デシリアライズの情報やクラスがあれば動きます。シリアライズするにはマーシャラが必要ですが、Infinispan では自作のエンティティに設定をするだけでマーシャラを自動生成してくれます。

全体の流れ

自作のエンティティを使うには以下の手順が必要です。

  1. 依存関係の追加
  2. 自作のエンティティを作成
  3. コンテキストイニシャライザの設定
  4. ソースコードを自動生成
  5. Hot Rodクライアントの設定
  6. 実行

依存関係の追加

Infinispanでシリアライズするにはprotostream-processorが必要です。依存関係を追加するためpom.xmlに以下の設定を追加します。

pom.xml

        <dependency>
            <groupId>org.infinispan.protostream</groupId>
            <artifactId>protostream-processor</artifactId>
            <version>4.3.2.Final</version>
        </dependency>

自作のエンティティを作成

依存関係を追加することで、APIが使えるようになりました。このAPIを使用して自作のエンティティとそれをシリアライズする設定を記述します。この設定を書くだけでシリアライズ/デシリアライズが自動的に行われます。

自作のエンティティには次の2つの設定が必要です。

  • シリアライズするフィールドを指定
  • オブジェクトを作るファクトリメソッドを指定

シリアライズするフィールドを指定するには@ProtoFieldアノテーションを使用し、シリアライズする順番と必須フィールドかどうかの指定が必ず必要となり、必要に応じてデフォルト値を指定します。フィールドにプリミティブ型を使用する場合はデフォルト値の指定は必須です。 シリアライズする順番はnumber属性、必須フィールドかどうかはrequired属性、デフォルト値はdefaultValue属性を使用します。

オブジェクトを作るファクトリメソッドを指定するには@ProtoFactoryアノテーションでstaticメソッドを指定します。 static メソッドではないメソッドに@ProtoFactoryアノテーションを指定するとビルド時に以下の様なエラーが発生します。

Error:(79,5) java: org.infinispan.protostream.annotations.ProtoSchemaBuilderException: @ProtoFactory annotated method must be static: public chiroito.sample.CustomEntity CustomEntity.create(java.lang.String,int)

自作のエンティティとなるCustomEntity.javaは以下のとおりです。

import org.infinispan.protostream.annotations.ProtoFactory;
import org.infinispan.protostream.annotations.ProtoField;

public class CustomEntity {

    @ProtoField(number = 1, required = true)
    String name;

    @ProtoField(number = 2, required = true, defaultValue = "0")
    int num;

    @ProtoFactory
    public static CustomEntity create(String name, int num) {
        return new CustomEntity(name, num);
    }

    public CustomEntity(String name, int num) {
        this.name = name;
        this.num = num;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "CustomEntity{" +
                "name='" + name + '\'' +
                ", num=" + num +
                '}';
    }
}

コンテキストイニシャライザの設定

ソースコードを自動生成するための設定のためだけのインターフェースです。マーシャラを作成して欲しいクラス、スキーマの情報などを指定します。@AutoProtoSchemaBuilderアノテーションとSerializationContextInitializerインターフェースの組み合わせで設定します。SerializationContextInitializerインターフェースのサブインターフェースを作り、@AutoProtoSchemaBuilderアノテーションのincludeClasses属性でマーシャラを作成して欲しいクラスを指定するだけと言う理解で良いでしょう。

詳しくはこちらをご覧ください。

protostream/AutoProtoSchemaBuilder.java at master · infinispan/protostream · GitHub

import org.infinispan.protostream.SerializationContextInitializer;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;

@AutoProtoSchemaBuilder(
        includeClasses = {
                CustomEntity.class
        },
        schemaFileName = "library.proto",
        schemaFilePath = "proto/",
        schemaPackageName = "customEntity")
interface CustomInitializer extends SerializationContextInitializer {
}

ソースコードを自動生成

ここまでそろった後にビルドをすることで、いくつかのソースコードが自動生成されます。ビルドはmvn packageします。自動生成されたコードはtarget/generated-sources/annotations以下に格納され、コンパイルされてtarget/classes.classファイルが生成されます。また、この.classファイルはjarファイルにも含まれます。作成されない場合はビルド時に他のプロセッサが邪魔をしていないか確認してください。

今回の例では以下の 5 つのファイルが生成されました。

  • target/generated-sources/annotations/chiroito/sample/CustomEntity$___Marshaller_6fdc67bee880914a83a620b354601cbfaee4ffec71b6318bc7625017a75101f1.java
  • target/generated-sources/annotations/chiroito/sample/CustomInitializerImpl.java
  • target/classes/chiroito/sample/CustomEntity$___Marshaller_6fdc67bee880914a83a620b354601cbfaee4ffec71b6318bc7625017a75101f1.class
  • target/classes/chiroito/sample/CustomInitializerImpl.class
  • target/classes/proto/library.proto

Hot Rodクライアントの設定

最後に設定ファイルに自動生成されたクラスを指定して終わりです。infinispan.client.hotrod.context-initializersに自動生成されたクラス名を指定します。

infinispan.client.hotrod.server_list=192.168.1.1:11222
infinispan.client.hotrod.context-initializers=chiroito.sample.CustomInitializerImpl

もしくはコードで自動生成されたクラスのインスタンスを与えます。ConfigurationBuilderクラスのaddContextInitializerメソッドで指定します。

ConfigurationBuilder cb = new ConfigurationBuilder();
cb.addContextInitializer(new CustomInitializerImpl());

RemoteCacheManager manager = new RemoteCacheManager(cb.build());

実行

それでは実行してみましょう。今回は自作のエンティティをputしてからgetしてみます。 今回はクライアントでのみシリアライズ/デシリアライズするため、サーバにjarファイルを送ったり設定を書いたりは不要です。

public static void main(String[] args) throws Exception{
    RemoteCacheManager manager = new RemoteCacheManager();

    RemoteCache<String, CustomEntity> c = manager.getCache("mycache");
    c.put("key1", new CustomEntity("りんご", 3));
    System.out.println(c.get("key1"));
    manager.close();
}

以下の様にエンティティの中身が出力されれば成功です。

3 23, 2020 9:00:23 午後 org.infinispan.client.hotrod.RemoteCacheManager actualStart
INFO: ISPN004021: Infinispan version: Infinispan 'Turia' 10.1.3.Final
3 23, 2020 9:00:23 午後 org.infinispan.client.hotrod.impl.protocol.Codec20 readNewTopologyAndHash
INFO: ISPN004006: Server sent new topology view (id=1, age=0) containing 1 addresses: [192.168.1.1:11222]
CustomEntity{name='りんご', num=3}