Codec
Spring Integration 4.2 版本引入了 Codec 抽象。Codec 用于将对象编码为 byte[] 以及从 byte[] 解码为对象。它们为 Java 序列化提供了一种替代方案。其中一个优势是,通常对象无需实现 Serializable。我们提供了一个使用 Kryo 进行序列化的实现,但您也可以提供自己的实现,用于以下任意组件:
-
EncodingPayloadTransformer -
DecodingTransformer -
CodecMessageConverter
EncodingPayloadTransformer
该转换器使用编解码器将有效载荷编码为 byte[]。它不会影响消息头。
更多信息请参阅 Javadoc。
DecodingTransformer
该转换器通过使用编解码器对 byte[] 进行解码。需要配置解码目标对象的 Class(或解析为 Class 的表达式)。如果解码结果对象为 Message<?>,则不会保留入站消息头。
更多信息请参阅 Javadoc。
CodecMessageConverter
某些端点(如TCP和Redis)不具备消息头的概念。它们支持使用MessageConverter,而CodecMessageConverter可用于将消息转换为byte[]以进行传输,或从byte[]转换回消息。
更多信息请参阅 Javadoc。
Kryo
目前,这是 Codec 的唯一实现,它提供了三种类型的 Codec:
-
PojoCodec: 用于转换器 -
MessageCodec: 用于CodecMessageConverter -
CompositeCodec: 用于转换器
该框架提供了多种自定义序列化器:
-
FileSerializer -
MessageHeadersSerializer -
MutableMessageHeadersSerializer
第一种可以与 PojoCodec 一起使用,通过使用 FileKryoRegistrar 进行初始化。第二种和第三种则与 MessageCodec 一起使用,后者通过 MessageKryoRegistrar 进行初始化。
CompositeCodec
CompositeCodec 是一个将多个编解码器组合成单一编解码器的组件,它将编码和解码操作委托给适当的类型特定编解码器。该实现将对象类型与其对应的编解码器关联起来,同时为未注册的类型提供默认的备用编解码器。
一个示例实现如下所示:
void encodeDecodeSample() {
Codec codec = getFullyQualifiedCodec();
//Encode and Decode a Dog Object
Dog dog = new Dog("Wolfy", 3, "woofwoof");
dog = codec.decode(
codec.encode(dog),
Dog.class);
System.out.println(dog);
//Encode and Decode a Cat Object
Cat cat = new Cat("Kitty", 2, 8);
cat = codec.decode(
codec.encode(cat),
Cat.class);
System.out.println(cat);
//Use the default code if the type being decoded and encoded is not Cat or dog.
Animal animal = new Animal("Badger", 5);
Animal animalOut = codec.decode(
codec.encode(animal),
Animal.class);
System.out.println(animalOut);
}
/**
* Create and return a {@link CompositeCodec} that associates {@code Dog} and {@code Cat}
* classes with their respective {@link PojoCodec} instances, while providing a default
* codec for {@code Animal} types.
* <p>
* @return a fully qualified {@link CompositeCodec} for {@code Dog}, {@code Cat},
* and fallback for {@code Animal}
*/
static Codec getFullyQualifiedCodec() {
Map<Class<?>, Codec> codecs = new HashMap<Class<?>, Codec>();
codecs.put(Dog.class, new PojoCodec(new KryoClassListRegistrar(Dog.class)));
codecs.put(Cat.class, new PojoCodec(new KryoClassListRegistrar(Cat.class)));
return new CompositeCodec(codecs, new PojoCodec(
new KryoClassListRegistrar(Animal.class)));
}
// Records that will be encoded and decoded in this sample
record Dog(String name, int age, String tag) {}
record Cat(String name, int age, int lives) {}
record Animal(String name, int age){}
在某些情况下,单一类型的对象可能返回多个编解码器。此时会抛出 IllegalStateException 异常。
该类使用 ClassUtils.findClosestMatch 为给定对象类型选择合适的编解码器。当多个编解码器匹配一个对象类型时,ClassUtils.findClosestMatch 提供了 failOnTie 选项。如果 failOnTie 为 false,它将返回任意一个匹配的编解码器。如果 failOnTie 为 true 且多个编解码器匹配,它将抛出 IllegalStateException。CompositeCodec 将 failOnTie 设置为 true,因此如果多个编解码器匹配,将抛出 IllegalStateException。
自定义 Kryo
默认情况下,Kryo 会将未知的 Java 类型委托给其 FieldSerializer。Kryo 还会为每种基本类型以及 String、Collection 和 Map 注册默认的序列化器。FieldSerializer 使用反射来遍历对象图。一种更高效的方法是实现一个自定义序列化器,该序列化器了解对象的结构,并可以直接序列化选定的基本字段。以下示例展示了这样一个序列化器:
public class AddressSerializer extends Serializer<Address> {
@Override
public void write(Kryo kryo, Output output, Address address) {
output.writeString(address.getStreet());
output.writeString(address.getCity());
output.writeString(address.getCountry());
}
@Override
public Address read(Kryo kryo, Input input, Class<Address> type) {
return new Address(input.readString(), input.readString(), input.readString());
}
}
Serializer 接口公开了 Kryo、Input 和 Output,从而允许完全控制哪些字段被包含以及其他内部设置,具体内容请参阅 Kryo 文档。
注册自定义序列化器时,你需要一个注册ID。注册ID可以是任意的。然而,在我们的场景中,ID必须被明确定义,因为分布式应用程序中的每个Kryo实例都必须使用相同的ID。Kryo推荐使用小的正整数,并保留了一些ID(值 < 10)。Spring Integration 目前默认使用 40、41 和 42(用于前面提到的文件和消息头序列化器)。我们建议你从 60 开始,以便为框架的扩展留出空间。你可以通过配置前面提到的注册器来覆盖这些框架默认值。
使用自定义 Kryo 序列化器
如果你需要自定义序列化,请参阅 Kryo 文档,因为你需要使用原生 API 进行自定义。例如,可以参考 org.springframework.integration.codec.kryo.MessageCodec 实现。
实现 KryoSerializable
如果你对领域对象的源代码拥有write访问权限,可以按照此处所述实现KryoSerializable接口。在这种情况下,类自身提供序列化方法,无需额外配置。但基准测试表明,这种方式不如显式注册自定义序列化器高效。以下示例展示了一个自定义 Kryo 序列化器:
public class Address implements KryoSerializable {
@Override
public void write(Kryo kryo, Output output) {
output.writeString(this.street);
output.writeString(this.city);
output.writeString(this.country);
}
@Override
public void read(Kryo kryo, Input input) {
this.street = input.readString();
this.city = input.readString();
this.country = input.readString();
}
}
你也可以使用这种技术来包装除 Kryo 之外的其他序列化库。
使用 @DefaultSerializer 注解
Kryo 还提供了 @DefaultSerializer 注解,具体描述请参见此处。
@DefaultSerializer(SomeClassSerializer.class)
public class SomeClass {
// ...
}
如果你对领域对象拥有 write 访问权限,这可能是指定自定义序列化器的一种更简单的方法。请注意,这不会为类注册 ID,在某些情况下可能使该技术无法发挥作用。