Codec

Version 4.2 of Spring Integration introduced the Codec abstraction. Codecs encode and decode objects to and from byte[]. They offer an alternative to Java serialization. One advantage is that, typically, objects need not implement Serializable. We provide one implementation that uses Kryo for serialization, but you can provide your own implementation for use in any of the following components:

  • EncodingPayloadTransformer

  • DecodingTransformer

  • CodecMessageConverter

EncodingPayloadTransformer

This transformer encodes the payload to a byte[] by using the codec. It does not affect message headers.

See the Javadoc for more information.

DecodingTransformer

This transformer decodes a byte[] by using the codec. It needs to be configured with the Class to which the object should be decoded (or an expression that resolves to a Class). If the resulting object is a Message<?>, inbound headers are not retained.

See the Javadoc for more information.

CodecMessageConverter

Certain endpoints (such as TCP and Redis) have no concept of message headers. They support the use of a MessageConverter, and the CodecMessageConverter can be used to convert a message to or from a byte[] for transmission.

See the Javadoc for more information.

Kryo

Currently, this is the only implementation of Codec, and it provides two kinds of Codec:

  • PojoCodec: Used in the transformers

  • MessageCodec: Used in the CodecMessageConverter

The framework provides several custom serializers:

  • FileSerializer

  • MessageHeadersSerializer

  • MutableMessageHeadersSerializer

The first can be used with the PojoCodec by initializing it with the FileKryoRegistrar. The second and third are used with the MessageCodec, which is initialized with the MessageKryoRegistrar.

Customizing Kryo

By default, Kryo delegates unknown Java types to its FieldSerializer. Kryo also registers default serializers for each primitive type, along with String, Collection, and Map. FieldSerializer uses reflection to navigate the object graph. A more efficient approach is to implement a custom serializer that is aware of the object’s structure and can directly serialize selected primitive fields. The following example shows such a serializer:

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());
    }
}

The Serializer interface exposes Kryo, Input, and Output, which provide complete control over which fields are included and other internal settings, as described in the Kryo documentation.

When registering your custom serializer, you need a registration ID. The registration IDs are arbitrary. However, in our case, the IDs must be explicitly defined, because each Kryo instance across the distributed application must use the same IDs. Kryo recommends small positive integers and reserves a few ids (value < 10). Spring Integration currently defaults to using 40, 41, and 42 (for the file and message header serializers mentioned earlier). We recommend you start at 60, to allow for expansion in the framework. You can override these framework defaults by configuring the registrars mentioned earlier.
Using a Custom Kryo Serializer

If you need custom serialization, see the Kryo documentation, because you need to use the native API to do the customization. For an example, see the MessageCodec implementation.

Implementing KryoSerializable

If you have write access to the domain object source code, you can implement KryoSerializable as described here. In this case, the class provides the serialization methods itself and no further configuration is required. However, benchmarks have shown this is not quite as efficient as registering a custom serializer explicitly. The following example shows a custom Kryo serializer:

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();
    }
}

You can also use this technique to wrap a serialization library other than Kryo.

Using the @DefaultSerializer Annotation

Kryo also provides a @DefaultSerializer annotation, as described here.

@DefaultSerializer(SomeClassSerializer.class)
public class SomeClass {
       // ...
}

If you have write access to the domain object, this may be a simpler way to specify a custom serializer. Note that this does not register the class with an ID, which may make the technique unhelpful for certain situations.