Chapter 9. The Externalizable API
9.1. The Externalizable API
An Externalizer
is a class that can:
- Marshall a given object type to a byte array.
- Unmarshall the contents of a byte array into an instance of the object type.
Externalizers are used by Red Hat JBoss Data Grid and allow users to specify how their object types are serialized. The marshalling infrastructure used in Red Hat JBoss Data Grid builds upon JBoss Marshalling and provides efficient payload delivery and allows the stream to be cached. The stream caching allows data to be accessed multiple times, whereas normally a stream can only be read once.
The Externalizable interface uses and extends serialization. This interface is used to control serialization and deserialization in Red Hat JBoss Data Grid.
9.2. Customize Externalizers
As a default in Red Hat JBoss Data Grid, all objects used in a distributed or replicated cache must be serializable. The default Java serialization mechanism can result in network and performance inefficiency. Additional concerns include serialization versioning and backwards compatibility.
For enhanced throughput, performance or to enforce specific object compatibility, use a customized externalizer. Customized externalizers for Red Hat JBoss Data Grid can be used in one of two ways:
- Use an Externalizable Interface.
- Use an advanced externalizer.
9.3. Annotating Objects for Marshalling Using @SerializeWith
Objects can be marshalled by providing an Externalizer implementation for the type that needs to be marshalled or unmarshalled, then annotating the marshalled type class with @SerializeWith
indicating the Externalizer class to use.
Using the @SerializeWith Annotation
import org.infinispan.commons.marshall.Externalizer; import org.infinispan.commons.marshall.SerializeWith; @SerializeWith(Person.PersonExternalizer.class) public class Person { final String name; final int age; public Person(String name, int age) { this.name = name; this.age = age; } public static class PersonExternalizer implements Externalizer<Person> { @Override public void writeObject(ObjectOutput output, Person person) throws IOException { output.writeObject(person.name); output.writeInt(person.age); } @Override public Person readObject(ObjectInput input) throws IOException, ClassNotFoundException { return new Person((String) input.readObject(), input.readInt()); } } }
In the provided example, the object has been defined as marshallable due to the @SerializeWith
annotation. JBoss Marshalling will therefore marshall the object using the Externalizer class passed.
This method of defining externalizers is user friendly, however it has the following disadvantages:
- The payload sizes generated using this method are not the most efficient. This is due to some constraints in the model, such as support for different versions of the same class, or the need to marshall the Externalizer class.
-
This model requires the marshalled class to be annotated with
@SerializeWith
, however an Externalizer may need to be provided for a class for which source code is not available, or for any other constraints, it cannot be modified. - Annotations used in this model may be limiting for framework developers or service providers that attempt to abstract lower level details, such as the marshalling layer, away from the user.
Advanced Externalizers are available for users affected by these disadvantages.
To make Externalizer implementations easier to code and more typesafe, define type <t> as the type of object that is being marshalled or unmarshalled.
9.4. Using an Advanced Externalizer
9.4.1. Using an Advanced Externalizer
Using a customized advanced externalizer helps optimize performance in Red Hat JBoss Data Grid.
-
Define and implement the
readObject()
andwriteObject()
methods. - Link externalizers with marshaller classes.
- Register the advanced externalizer.
9.4.2. Implement the Methods
To use advanced externalizers, define and implement the readObject()
and writeObject()
methods. The following is a sample definition:
Define and Implement the Methods
import org.infinispan.commons.marshall.AdvancedExternalizer; public class Person { final String name; final int age; public Person(String name, int age) { this.name = name; this.age = age; } public static class PersonExternalizer implements AdvancedExternalizer<Person> { @Override public void writeObject(ObjectOutput output, Person person) throws IOException { output.writeObject(person.name); output.writeInt(person.age); } @Override public Person readObject(ObjectInput input) throws IOException, ClassNotFoundException { return new Person((String) input.readObject(), input.readInt()); } @Override public Set<Class<? extends Person>> getTypeClasses() { return Util.<Class<? extends Person>>asSet(Person.class); } @Override public Integer getId() { return 2345; } } }
This method does not require annotated user classes. As a result, this method is valid for classes where the source code is not available or cannot be modified.
9.4.3. Link Externalizers with Marshaller Classes
Use an implementation of getTypeClasses()
to discover the classes that this externalizer can marshall and to link the readObject()
and writeObject()
classes.
The following is a sample implementation:
import org.infinispan.util.Util; <!-- Additional configuration information here --> @Override public Set<Class<? extends ReplicableCommand>> getTypeClasses() { return Util.asSet(LockControlCommand.class, GetKeyValueCommand.class, ClusteredGetCommand.class, MultipleRpcCommand.class, SingleRpcCommand.class, CommitCommand.class, PrepareCommand.class, RollbackCommand.class, ClearCommand.class, EvictCommand.class, InvalidateCommand.class, InvalidateL1Command.class, PutKeyValueCommand.class, PutMapCommand.class, RemoveCommand.class, ReplaceCommand.class); }
In the provided sample, the ReplicableCommandExternalizer
indicates that it can externalize several command types. This sample marshalls all commands that extend the ReplicableCommand
interface but the framework only supports class equality comparison so it is not possible to indicate that the classes marshalled are all children of a particular class or interface.
In some cases, the class to be externalized is private and therefore the class instance is not accessible. In such a situation, look up the class with the provided fully qualified class name and pass it back. An example of this is as follows:
@Override public Set<Class<? extends List>> getTypeClasses() { return Util.<Class<? extends List>>asSet( Util.<List>loadClass("java.util.Collections$SingletonList", null)); }
9.4.4. Register the Advanced Externalizer (Programmatically)
After the advanced externalizer is set up, register it for use with Red Hat JBoss Data Grid. This registration is done programmatically as follows:
Registering the Advanced Externalizer Programmatically
GlobalConfigurationBuilder builder = ... builder.serialization() .addAdvancedExternalizer(new Person.PersonExternalizer());
Enter the desired information for the GlobalConfigurationBuilder in the first line.
9.4.5. Register Multiple Externalizers
Alternatively, register multiple advanced externalizers because GlobalConfiguration.addExternalizer()
accepts varargs
. Before registering the new externalizers, ensure that their IDs are already defined using the @Marshalls
annotation.
Registering Multiple Externalizers
builder.serialization() .addAdvancedExternalizer(new Person.PersonExternalizer(), new Address.AddressExternalizer());
9.5. Custom Externalizer ID Values
9.5.1. Custom Externalizer ID Values
Advanced externalizers can be assigned custom IDs if desired. Some ID ranges are reserved for other modules or frameworks and must be avoided:
ID Range | Reserved For |
---|---|
1000-1099 | The Infinispan Tree Module |
1100-1199 | Red Hat JBoss Data Grid Server modules |
1200-1299 | Hibernate Infinispan Second Level Cache |
1300-1399 | JBoss Data Grid Lucene Directory |
1400-1499 | Hibernate OGM |
1500-1599 | Hibernate Search |
1600-1699 | Infinispan Query Module |
1700-1799 | Infinispan Remote Query Module |
1800-1849 | JBoss Data Grid Scripting Module |
1850-1899 | JBoss Data Grid Server Event Logger Module |
1900-1999 | JBoss Data Grid Remote Store |
9.5.2. Customize the Externalizer ID (Programmatically)
Use the following configuration to programmatically assign a specific ID to the externalizer:
Assign an ID to the Externalizer
GlobalConfiguration globalConfiguration = new GlobalConfigurationBuilder() .serialization() .addAdvancedExternalizer($ID, new Person.PersonExternalizer()) .build();
Replace the $ID
with the desired ID.