Spearal is a compact binary format for exchanging arbitrary complex data between various endpoints such as Java EE, JavaScript / HTML, Android and iOS applications.
Spearal-Java is the common codebase used in all Spearal / Java specific extensions.
The library is available in the Maven central repository, so with Maven or Gradle you simply have to reference the dependency on the library in your build system. Note that Javassist is an optional (but necessary to handle partial objects) dependency of the library:
<dependency>
<groupId>org.spearal</groupId>
<artifactId>spearal-java</artifactId>
<version>${spearal.version}</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.2-GA</version>
</dependency>
compile 'org.spearal:spearal-java:${spearal.version}'
compile 'org.javassist:javassist:3.18.2-GA'
In any other case, just download the Spearal-Java jar from github and add it to your classpath.
First, you need to create a SpearalFactory
that contains the central configuration and internal state of the library:
SpearalFactory factory = new DefaultSpearalFactory();
Encoding data is then a matter of creating a new encoder and call the writeAny
method:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SpearalEncoder encoder = factory.newEncoder(baos);
encoder.writeAny(obj);
Decoding is achieved the same way with readAny
:
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
SpearalDecoder decoder = factory.newEncoder(bais);
Object copy = decoder.readAny();
Note that if you know the expected type of the decoded object, you can use the typed version of readAny
:
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
SpearalDecoder decoder = factory.newEncoder(bais);
Invoice copy = decoder.readAny(Invoice.class);
Spearal lets you encode only a subset of the properties of an object. Let’s say you have bean defined by this class:
class Person implements Serializable {
private String firstName;
private String lastName;
private List<String> phones;
// getters / setters...
}
If you are retrieving a collection of Persons and just need their first and last names, it is useless to serialize their collections of phones.
Spearal lets you encode just the firstName
and lastName
properties with a property filter:
SpearalFactory factory = new DefaultSpearalFactory();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SpearalEncoder encoder = factory.newEncoder(baos);
// Only firstName and lastName:
encoder.getPropertyFilter().add(Person.class, "firstName", "lastName");
encoder.writeAny(obj);
When decoding the result, you will get a Javassist proxy for each Person, that will throw a UndefinedPropertyException
if you try to access the phones collection:
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
SpearalDecoder decoder = factory.newEncoder(bais);
Person copy = decoder.readAny(Person.class);
System.out.println(copy.getFirstName());
System.out.println(copy.getLastName());
// This line throws a UndefinedPropertyException:
System.out.println(copy.getPhones());
Various elements can be configured in the SpearalFactory
. All configurable elements implement the interface Configurable
and can be configured
with:
spearalFactory.configure(new ConfigurableElement());
The first configurable element is the class alias strategy. It is used during serialization and deserialization to convert the name of the class from a local qualified name to the remote qualified name.
The built-in implementation PackageTranslatorAliasStrategy
simply translates the package prefix from one name to another. For example with
a client package prefix com.mycompany.client
and a server package prefix com.mycompany.server
, the class name com.mycompany.server.domain.DomainObject
will be translated to com.mycompany.client.domain.DomainObject
and the corresponding class will be instantiated.
The default does not alias or unalias anything.
The role of the introspector is to retrieve (usually by reflection) the list of properties for a specified class.
The default IntrospectorImpl
is able to instrospect properties of a JavaBean-style class and can be used in most cases.
A custom introspector could be necessary for example to deal with JavaFX objects with bindable properties.
Its role is to instantiate partial objects. When Javassist is present in the classpath, the built-in JavassistPartialObjectFactory
is used and creates a
Javassist proxy for the class. The default behaviour when Javassist is not present is NoProxyPartialObjectFactory
which simply instantiates the
target class. Obviously this is not suitable if you really want to use partial objects because they will be sent as 'full' objects from client to server,
but with some properties set to null (or default values).
The factory must implement the following method:
/**
* Create a proxy instance for the target class
* @param context the current Spearal context
* @param cls the class to proxy
* @param partialProperties the defined properties at initialization
*/
public Object instantiatePartial(SpearalContext context, Class<?> cls, Property[] partialProperties);
The role of the securizer is to allow or forbid the serialization/deserialization of any particular type. It can be important in particular
during deserialization to prevent an attacker to let Spearal instantiate a sensitive class with which it could access the underlying system.
The default SecurizerImpl
enforces that the target class must implement Serializable
but any other policy can be implemented.
The securizer must implement the following method:
void checkDecodable(Type type) throws SecurityException;
void checkEncodable(Class<?> cls) throws SecurityException;
The role of the loader is to instantiate the target class from its aliased name (or names when multiple interfaces are received).
The default TypeLoaderImpl
parses the class names, and does the following depending on the cases:
-
If there is only one name, it loads the type. If the type is an interface, it instantiates a Java proxy, else it instantiates the class itself.
-
If the class is not found, it instantiates the class
ClassNotFound
which is a anonymous object that will contain the deserialized data. -
If there are many names, it tries to create a Java proxy for all the specified interfaces.
If you need that the classes are loaded in a specific ClassLoader
, just reconfigure the default TypeLoaderImpl
:
spearalFactory.configure(new TypeLoaderImpl(myClassLoader));
Any other loading policy can be implemented, for example to get object instances from a pool, an external object factory or apply specific transformations or initialization on the instantiated objects.
The loader must implement the following method:
Class<?> loadClass(SpearalContext context, String classNames, Type target);