5.2. 将应用程序迁移到 AutoProtoSchemaBuilder 注解

之前的 Data Grid 版本使用 ProtoStream API 中的 MessageMarshaller 接口来配置 marshalling。

MessageMarshaller API 和 ProtoSchemaBuilder 注解都从 Data Grid 8.1.1 中弃用,它对应于 ProtoStream 4.3.4。

使用 MessageMarshaller 接口涉及:

  • 手动创建 Protobuf 模式。
  • ProtoSchemaBuilder 注释添加到 Java 类,然后生成 Protobuf 模式。

但是,配置 ProtoStream marshalling 的这些技术不作为 AutoProtoSchemaBuilder 注释(可从 Data Grid 81 开始获得)。只需在您的 Java 类中添加 AutoProtoSchemaBuilder 注释,并生成包含 Protobuf 模式和相关 marshallers 的 SerializationContextInitializer 实现。

红帽建议您开始使用 AutoProtoSchemaBuilder 注解从 ProtoStream marshaller 获取最佳结果。

以下代码示例演示了如何将应用从 MessageMarshaller API 迁移到 AutoProtoSchemaBuilder 注释。

5.2.1. 基本 MessageMarshaller 实现

这个示例包含使用非默认类型的一些字段。text 字段有不同的顺序,固定32 字段与生成的 Protobuf 模式类型冲突,因为代码生成器默认使用 int 类型。

SimpleEntry.java

public class SimpleEntry {

  private String description;
  private Collection<String> text;
  private int intDefault;
  private Integer fixed32;

  // public Getter, Setter, equals and HashCode methods omitted for brevity
}

SimpleEntryMarshaller.java

import org.infinispan.protostream.MessageMarshaller;

public class SimpleEntryMarshaller implements MessageMarshaller<SimpleEntry> {

  @Override
  public void writeTo(ProtoStreamWriter writer, SimpleEntry testEntry) throws IOException {
    writer.writeString("description", testEntry.getDescription());
    writer.writeInt("intDefault", testEntry.getIntDefault());
    writer.writeInt("fix32", testEntry.getFixed32());
    writer.writeCollection("text", testEntry.getText(), String.class);
  }

  @Override
  public SimpleEntry readFrom(MessageMarshaller.ProtoStreamReader reader) throws IOException {
    SimpleEntry x = new SimpleEntry();

    x.setDescription(reader.readString("description"));
    x.setIntDefault(reader.readInt("intDefault"));
    x.setFixed32(reader.readInt("fix32"));
    x.setText(reader.readCollection("text", new LinkedList<String>(), String.class));

    return x;
  }
}

生成的 Protobuf 模式

syntax = "proto2";

package example;

message SimpleEntry {
   required string description = 1;
   optional int32 intDefault = 2;
   optional fixed32 fix32 = 3;
   repeated string text = 4;
}

迁移到 AutoProtoSchemaBuilder 注解

SimpleEntry.java

import org.infinispan.protostream.annotations.ProtoField;
import org.infinispan.protostream.descriptors.Type;

public class SimpleEntry {

  private String description;
  private Collection<String> text;
  private int intDefault;
  private Integer fixed32;

  @ProtoField(number = 1)
  public String getDescription() {...}

  @ProtoField(number = 4, collectionImplementation = LinkedList.class)
  public Collection<String> getText() {...}

  @ProtoField(number = 2, defaultValue = "0")
  public int getIntDefault() {...}

  @ProtoField(number = 3, type = Type.FIXED32)
  public Integer getFixed32() {...}

  // public Getter, Setter, equals and HashCode methods and convenient constructors omitted for brevity
}

SimpleEntryInitializer.java

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

@AutoProtoSchemaBuilder(includeClasses = { SimpleEntry.class }, schemaFileName = "simple.proto", schemaFilePath = "proto", schemaPackageName = "example")
public interface SimpleEntryInitializer extends GeneratedSchema {
}

重要观察
  • 字段 2 定义为 int,之前版本中的 ProtoStream marshaller 没有检查。
  • 因为 Java int 字段不可为空,因此 ProtoStream 处理器将失败。
    必须 需要 Java int 字段,或使用 defaultValue 进行初始化。

    从 Java 应用程序的角度来看,int 字段使用 "0" 初始化,因此您可以使用 defaultValue,而不影响任何 put 操作会设置它。如果始终存在数据视角,则更改 为必需 不是问题,但可能会导致不同的客户端出现问题。

  • 为了兼容性,字段 3 必须明确设置为 Type.FIXED32
  • 对于生成的 Protobuf 模式,必须以正确的顺序设置 文本集合
重要

Protobuf 模式中的 文本集合 顺序必须与迁移之前和之后相同。同样,您必须在迁移过程中设置 fixed32 类型。

如果没有,客户端应用程序可能会抛出以下异常,且无法启动:

Exception ( ISPN004034: Unable to unmarshall bytes )

在其他情况下,您可能会观察缓存的数据不完整或不准确。

5.2.2. 带有自定义类型的 MessageMarshaller 实现

本节提供了 MessageMarshaller 实现迁移示例,其中包含 ProtoStream 没有原生处理的字段。

以下示例使用 BigInteger 类,但适用于任何类,即使是数据网格适配器或自定义类。

注意

BigInteger 类不可变,因此没有参数结构。

CustomTypeEntry.java

import java.math.BigInteger;

public class CustomTypeEntry {

  final String description;
  final BigInteger bigInt;

  // public Getter, Setter, equals and HashCode methods and convenient constructors omitted for brevity
}

CustomTypeEntryMarshaller.java

import org.infinispan.protostream.MessageMarshaller;

public class CustomTypeEntryMarshaller implements MessageMarshaller<CustomTypeEntry> {

  @Override
  public void writeTo(ProtoStreamWriter writer, CustomTypeEntry testEntry) throws IOException {
    writer.writeString("description", testEntry.description);
    writer.writeString("bigInt", testEntry.bigInt.toString());
  }

  @Override
  public CustomTypeEntry readFrom(MessageMarshaller.ProtoStreamReader reader) throws IOException {
    final String desc = reader.readString("description");
    final BigInteger bInt = new BigInteger(reader.readString("bigInt"));

    return new CustomTypeEntry(desc, bInt);
  }
}

CustomTypeEntry.proto

syntax = "proto2";

package example;

message CustomTypeEntry {
   required string description = 1;
   required string bigInt = 2;
}

使用适配器类迁移的代码

您可以使用 ProtoAdapter 注解以生成 Protobuf 模式的方式 marshall CustomType 类,它与您使用 MessageMarshaller 实现创建的 Protobuf 模式兼容。

使用这个方法,您可以:

  • 不得在 CustomTypeEntry 类中添加注解。
  • 创建一个 CustomTypeEntryAdapter 类,它使用 @ProtoAdapter 注释来控制如何生成 Protobuf 模式和 marshaller。
  • 包含 CustomTypeEntryAdapter 类,并包含 @AutoProtoSchemaBuilder 注释。

    注意

    由于 AutoProtoSchemaBuilder 注释不引用 CustomTypeEntry 类,因此该类中包含的任何注解都会被忽略。

以下示例显示了包含 CustomTypeEntry 类的 ProtoStream 注解的 CustomTypeEntryAdapter 类:

CustomTypeEntryAdapter.java

import java.math.BigInteger;

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

@ProtoAdapter(CustomTypeEntry.class)
public class CustomTypeEntryAdapter {

  @ProtoFactory
  public CustomTypeEntry create(String description, String bigInt) {
    return new CustomTypeEntry(description, new BigInteger(bigInt));
  }

  @ProtoField(number = 1, required = true)
  public String getDescription(CustomTypeEntry t) {
    return t.description;
  }

  @ProtoField(number = 2, required = true)
  public String getBigInt(CustomTypeEntry t) {
    return t.bigInt.toString();
  }
}

以下示例显示了带有 AutoProtoSchemaBuilder 注解的 SerializationContextInitializer,该注解引用 CustomTypeEntryAdapter 类:

CustomTypeEntryInitializer.java

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

@AutoProtoSchemaBuilder(includeClasses = { CustomTypeEntryAdapter.class },
   schemaFileName = "custom.proto",
   schemaFilePath = "proto",
   schemaPackageName = "example")
public interface CustomTypeAdapterInitializer extends GeneratedSchema { }

在没有适配器类的情况下迁移的代码

您可以直接将 ProtoStream 注解添加到 CustomTypeEntry 类中,而不是创建适配器类。

重要

在本例中,生成的 Protobuf 模式与通过 MessageMarshaller 接口添加的缓存中的数据不兼容,因为 BigInteger 是一个单独的消息。即使 adapter 字段写入相同的 String,也无法处理数据。

以下示例显示了直接包含 ProtoStream 注解的 CustomTypeEntry 类:

CustomTypeEntry.java

import java.math.BigInteger;

public class CustomTypeEntry {

  @ProtoField(number = 1)
  final String description;
  @ProtoField(number = 2)
  final BigInteger bigInt;

  @ProtoFactory
  public CustomTypeEntry(String description, BigInteger bigInt) {
    this.description = description;
    this.bigInt = bigInt;
  }

  // public Getter, Setter, equals and HashCode methods and convenient constructors omitted for brevity
}

以下示例显示了带有 AutoProtoSchemaBuilder 注解的 SerializationContextInitializer,该注解引用 CustomTypeEntryBigIntegerAdapter 类:

CustomTypeEntryInitializer.java

import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.types.java.math.BigIntegerAdapter;

@AutoProtoSchemaBuilder(includeClasses = { CustomTypeEntry.class,
   BigIntegerAdapter.class },
   schemaFileName = "customtype.proto",
   schemaFilePath = "proto",
   schemaPackageName = "example")
public interface CustomTypeInitializer extends GeneratedSchema { }

当您从前面的 SerializationContextInitializer 实现中生成 Protobuf 模式时,它会生成以下 Protobuf 模式:

CustomTypeEntry.proto

syntax = "proto2";

package example;

message BigInteger {
   optional bytes bytes = 1;
}

message CustomTypeEntry {
   optional string description = 1;
   optional BigInteger bigInt = 2;
}