Please use atleast protostuff-1.0.4 or later if you have raw byte array fields (byte[],List<byte[]>,etc
). See Issue 90 for details.
The protostuff-runtime module allows your existing pojos to be serialized to different formats.
For people who prefer not to have their messages code-generated from proto files, this fits your bill.
The preliminary modules needed: * protostuff-api * protostuff-collectionschema * protostuff-runtime
The advantages of using proto files is that you have explicit control of fields and their corresponding numbers (which is useful for schema evolution, e.g forward-backward compatibility).
With this module, the field's number is ordered according to their declaration in the pojo (top to bottom).
Note that the order is not guaranteed on some (non-sun) vms (especially dalvik).
Sun jdk6 or higher is recommended for guaranteed ordering (link).
As of 1.0.5, @Tag
annotations can be used on fields to have explicit control of the field numbers
```
// all or nothing.
// Either you annotate all fields or you don't annotate at all (applies to the relevant class only).
// To exclude certain fields, use java's transient keyword
public final class Bar
{
@Tag(8)
int baz;
// alias is available since 1.0.7 // useful for json/xml/yaml where you can override the field names @Tag(value = 15, alias = "f") double foo; }
// with this approach, versioning with inheritance is now fully supported. // you simply reserve x-y (range) numbers for the fields of the parent class. // internally it will be detected when you make mistakes tagging with the same number. ```
Note that if you have non-static inner classes and want to use @Tag annotations, mark that class as static instead. See Issue 146 for details.
Without @Tag
annotations, forward-backward compatibility is still supported via "append-only" schema evolution.
* To add new fields, append the field in the declaration
* To remove existing fields, annotate with @Deprecated
* To exclude fields from being serialized, use the java keyword: transient
Here's an example: ``` public final class Entity { int id;
String name;
@Deprecated
String alias;
long timestamp;
}
```
Schema evolution scenario: * v1: 3 initial fields (id=1, name=2, alias=3) * v2: Added a new field (timestamp=4). * v3: Removed the "alias" field.
With v3, the field mapping would be (id=1, name=2, timestamp=4). When we encounter the alias field, it is ignored by the deserializer.
The field mapping is still intact despite schema evolution ... w/c makes it forward-backward compatible to different versions.
3 possible types of Schema
Unlike a static hand-written/code-generated schema, there are 3 possible types of schema that can be used at runtime. Below are the types ordered according to their efficiency and performance at runtime.
Static Schema
- used when the declared field is a concrete type.
- compact since no extra metadata included on serialization
E.g ``` public enum SortOrder { ASCENDING, DESCENDING; }
public final class Bar { Entity entity; // the example above List scalarList; // any scalar type List bytesList; // byte arrays are treated as scalar fields (use >= 1.0.4) List entityList; Map bytesMapWithScalarKeys; Map entityMapWithScalarKeys; Map entityMapWithEnumKeys; Map entityMapWithPojoKeys; Map entityMap; } ```
DerivativeSchema
- used when the declared field is an abstract class.
- Use at least version 1.0.5. (previous versions allowed interfaces but was changed because enums and most scalars do implement interfaces)
- less compact since the type metadata is written (field number: 127) on serialization.
E.g ``` public abstract class Instrument { // ... }
public final class BassGuitar extends Instrument { // ... }
public final class Piano extends Instrument { // ... }
// DerivativeSchema will be used on the fields below public final class Baz { Instrument instrument; List instrumentList; Map instrumentMapWithScalarKeys; Map instrumentMapWithEnumKeys; Map instrumentMapWithPojoKeys; Map instrumentMap; }
- used when the declared field is an abstract class.
``` * IMPORTANT * If your object heirarchy involves a concrete class subclassing another concrete class (not using abstract classes), set:
-Dprotostuff.runtime.morph_non_final_pojos=true
* With that property set,DerivativeSchema
will be used on non-final pojos (concrete types) similar to abstract classes. For example: ``` class Base { int id = 1; } class Child extends Base { int status = 2; } class Pojo { Base b = new Child(); }
// If you serialize Pojo, Child's "status" field will not be
// serialized if the system property is not set.
// With that in mind, all pojos that aren't marked final will
// have an overhead of extra type metadata on serialization.
// To ensure that no extra type metadata be will written, mark
// your pojos final when you know there are no subclasses.
```
ObjectSchema
(dynamic)- used when the type of the declared fields:
- are
java.lang.Object
- are interfaces
- are arrays
- don't have generics
- are too complex
- are
- all necessary metadata is included on serialization to be able to deserialize the message correctly.
E.g ```
public final class Dynamic { Object entity;
Object[] objectArray; int[] primitiveArray; Integer[] boxedArray; Entity[] entityArray; IEntity[] ientityArray; List noGenericsList; List<?> uselessGenericsList; List<Object> objectList; List<long[]> withArrayList; Map noGenericsMap; Map<?,?> uselessGenericsMap; Map<String,Object> withObjectMap; Map<?,SortOrder> dynamicKeyMap; Map<Entity,?> dynamicValueMap; Map<Integer[],int[]> withArrayMap; // and complex types List<List<String>> aListWithAList; Map<String,List<SortOrder>> complexMap; Map<Set<Entity>,Long> anotherComplexMap;
}
- used when the type of the declared fields:
```
Updating fields
With the information above, be sure that you update your fields carefully.
For example ... don't add/remove generics when you already have existing data because the deserialization will fail.
For scalar fields:
* int
can be updated to long
(and vice versa)
* compatible with all suported formats
* String
can be updated to byte[]
/ByteString
(and vice versa)
* not compatible with text formats (e.g json/xml/yaml)
``` class Example { int i; long l; Integer i2; Long l2; String s; byte[] b; ByteString bs; }
```
Performance guidelines
As much as possible, use the concrete type when declaring a field.
For polymorhic datasets, prefer abstract classes vs interfaces.
* Use ExplicitIdStrategy
to write the type metadata as int (ser/deser will be faster and the serialized size will be smaller).
* Register your concrete classes at startup via ExplicitIdStrategy.Registry
.
- For objects not known ahead of time, use
IncrementalIdStrategy
- You can activate it using the system property:
-Dprotostuff.runtime.id_strategy_factory=com.dyuproject.protostuff.runtime.IncrementalIdStrategy$Factory
You can also use these strategies independently. E.g: ``` final IncrementalIdStrategy strategy = new IncrementalIdStrategy(....);
// use its registry if you want to pre-register classes.// Then when your app needs a schema, use it. RuntimeSchema.getSchema(clazz, strategy); ```
Usage
Note that on deser, if your object does not have a default constructor, you can always use schema.newMessage() to instantiate (internally similar to how the default java-serialization instantiates)
``` Foo foo = new Foo("foo", 1, 3.5);
// this is lazily created and cached by RuntimeSchema // so its safe to call RuntimeSchema.getSchema(Foo.class) over and over // The getSchema method is also thread-safe Schema schema = RuntimeSchema.getSchema(Foo.class); LinkedBuffer buffer = getApplicationBuffer();
/* -------- protostuff -------- (requires protostuff-core module) */ // ser try { byte[] protostuff = ProtostuffIOUtil.toByteArray(foo, schema, buffer); } finally { buffer.clear(); } // deser Foo f = schema.newMessage(); ProtostuffIOUtil.mergeFrom(protostuff, f, schema);
/* -------- protobuf -------- (requires protostuff-core module) */ // ser try { byte[] protobuf = ProtobufIOUtil.toByteArray(foo, schema, buffer); } finally { buffer.clear(); } // deser Foo f = schema.newMessage(); ProtobufIOUtil.mergeFrom(protobuf, f, schema);
/* -------- json -------- (requires protostuff-json module)*/ // ser boolean numeric = true; byte[] json = JsonIOUtil.toByteArray(foo, schema, numeric, buffer);
// deser Foo f = schema.newMessage(); JsonIOUtil.mergeFrom(json, f, schema, numeric);
/* -------- xml -------- (requires protostuff-xml module)*/ // ser byte[] xml = XmlIOUtil.toByteArray(foo, schema);
// deser Foo f = schema.newMessage(); XmlIOUtil.mergeFrom(xml, f, schema);
/* -------- yaml -------- (requires protostuff-yaml module)*/ // ser try { byte[] yaml = YamlIOUtil.toByteArray(foo, schema, buffer); } finally { buffer.clear(); }
```
Reading/Writing from/to streams
``` Foo foo = new Foo("foo", 1, 3.5);
Schema schema = RuntimeSchema.getSchema(Foo.class); LinkedBuffer buffer = getApplicationBuffer();
/* -------- protostuff -------- (requires protostuff-core module)*/ // ser try { ProtostuffIOUtil.writeTo(outputStream, foo, buffer); } finally { buffer.clear(); } // deser Foo f = schema.newMessage(); ProtostuffIOUtil.mergeFrom(inputStream, f, schema, buffer);
/* --------protobuf -------- (requires protostuff-core module)*/ // ser try { ProtobufIOUtil.writeTo(outputStream, foo, buffer); } finally { buffer.clear(); } // deser Foo f = schema.newMessage(); ProtobufIOUtil.mergeFrom(inputStream, f, schema, buffer);
/* -------- json -------- (requires protostuff-json module)*/ // ser boolean numeric = false; JsonIOUtil.writeTo(outputStream, foo, schema, numeric, buffer);
// deser Foo f = schema.newMessage(); JsonIOUtil.mergeFrom(inputStream, f, schema, numeric, buffer);
/* -------- xml -------- (requires protostuff-xml module)*/ // ser XmlIOUtil.writeTo(outputStream, foo, schema);
// deser Foo f = schema.newMessage(); XmlIOUtil.mergeFrom(inputStream, f, schema);
/* -------- yaml -------- (requires protostuff-yaml module)*/ // ser try { YamlIOUtil.writeTo(outputStream, foo, buffer); } finally { buffer.clear(); }
```