mirror of
https://github.com/corda/corda.git
synced 2025-04-08 03:44:49 +00:00
Serialization documentation backport (#3594)
This commit is contained in:
parent
b36cfab871
commit
9259c9ec45
@ -1,3 +1,9 @@
|
||||
.. highlight:: kotlin
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||
|
||||
Pluggable Serializers for CorDapps
|
||||
==================================
|
||||
|
||||
@ -25,8 +31,7 @@ Serializers inheriting from SerializationCustomSerializer have to implement two
|
||||
|
||||
Example
|
||||
-------
|
||||
Consider this example class
|
||||
|
||||
Consider the following class:
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
@ -34,6 +39,9 @@ Consider this example class
|
||||
private final Int a
|
||||
private final Int b
|
||||
|
||||
// Because this is marked private the serialization framework will not
|
||||
// consider it when looking to see which constructor should be used
|
||||
// when serializing instances of this class.
|
||||
private Example(Int a, Int b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
@ -48,23 +56,166 @@ Consider this example class
|
||||
Without a custom serializer we cannot serialize this class as there is no public constructor that facilitates the
|
||||
initialisation of all of its properties.
|
||||
|
||||
To be serializable by Corda this would require a custom serializer as follows:
|
||||
.. note:: This is clearly a contrived example, simply making the constructor public would alleviate the issues.
|
||||
However, for the purposes of this example we are assuming that for external reasons this cannot be done.
|
||||
|
||||
To be serializable by Corda this would require a custom serializer to be written that can transform the unserializable
|
||||
class into a form we can serialize. Continuing the above example, this could be written as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
/**
|
||||
* The class lacks a public constructor that takes parameters it can associate
|
||||
* with its properties and is thus not serializable by the CORDA serialization
|
||||
* framework.
|
||||
*/
|
||||
class Example {
|
||||
private int a;
|
||||
private int b;
|
||||
|
||||
public int getA() { return a; }
|
||||
public int getB() { return b; }
|
||||
|
||||
public Example(List<int> l) {
|
||||
this.a = l.get(0);
|
||||
this.b = l.get(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the class that will Proxy instances of Example within the serializer
|
||||
*/
|
||||
public class ExampleProxy {
|
||||
/**
|
||||
* These properties will be serialized into the byte stream, this is where we choose how to
|
||||
* represent instances of the object we're proxying. In this example, which is somewhat
|
||||
* contrived, this choice is obvious. In your own classes / 3rd party libraries, however, this
|
||||
* may require more thought.
|
||||
*/
|
||||
private int proxiedA;
|
||||
private int proxiedB;
|
||||
|
||||
/**
|
||||
* The proxy class itself must be serializable by the framework, it must thus have a constructor that
|
||||
* can be mapped to the properties of the class via getter methods.
|
||||
*/
|
||||
public int getProxiedA() { return proxiedA; }
|
||||
public int getProxiedB() { return proxiedB; }
|
||||
|
||||
public ExampleProxy(int proxiedA, int proxiedB) {
|
||||
this.proxiedA = proxiedA;
|
||||
this.proxiedB = proxiedB;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finally this is the custom serializer that will automatically loaded into the serialization
|
||||
* framework when the CorDapp Jar is scanned at runtime.
|
||||
*/
|
||||
public class ExampleSerializer implements SerializationCustomSerializer<Example, ExampleProxy> {
|
||||
|
||||
/**
|
||||
* Given an instance of the Example class, create an instance of the proxying object ExampleProxy.
|
||||
*
|
||||
* Essentially convert Example -> ExampleProxy
|
||||
*/
|
||||
public ExampleProxy toProxy(Example obj) {
|
||||
return new ExampleProxy(obj.getA(), obj.getB());
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversely, given an instance of the proxy object, revert that back to an instance of the
|
||||
* type being proxied.
|
||||
*
|
||||
* Essentially convert ExampleProxy -> Example
|
||||
*/
|
||||
public Example fromProxy(ExampleProxy proxy) {
|
||||
List<int> l = new ArrayList<int>(2);
|
||||
l.add(proxy.getProxiedA());
|
||||
l.add(proxy.getProxiedB());
|
||||
return new Example(l);
|
||||
}
|
||||
}
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class ExampleSerializer : SerializationCustomSerializer<Example, ExampleSerializer.Proxy> {
|
||||
/**
|
||||
* This is the actual proxy class that is used as an intermediate representation
|
||||
* of the Example class
|
||||
*/
|
||||
data class Proxy(val a: Int, val b: Int)
|
||||
|
||||
/**
|
||||
* This method should be able to take an instance of the type being proxied and
|
||||
* transpose it into that form, instantiating an instance of the Proxy object (it
|
||||
* is this class instance that will be serialized into the byte stream.
|
||||
*/
|
||||
override fun toProxy(obj: Example) = Proxy(obj.a, obj.b)
|
||||
|
||||
/**
|
||||
* This method is used during deserialization. The bytes will have been read
|
||||
* from the serialized blob and an instance of the Proxy class returned, we must
|
||||
* now be able to transform that back into an instance of our original class.
|
||||
*
|
||||
* In our example this requires us to evoke the static "of" method on the
|
||||
* Example class, transforming the serialized properties of the Proxy instance
|
||||
* into a form expected by the construction method of Example.
|
||||
*/
|
||||
override fun fromProxy(proxy: Proxy) : Example {
|
||||
val constructorArg = IntArray(2);
|
||||
constructorArg[0] = proxy.a
|
||||
constructorArg[1] = proxy.b
|
||||
return Example.of(constructorArg)
|
||||
}
|
||||
}
|
||||
|
||||
In the above examples
|
||||
|
||||
- ``ExampleSerializer`` is the actual serializer that will be loaded by the framework to serialize instances of the ``Example`` type.
|
||||
- ``ExampleSerializer.Proxy``, in the Kotlin example, and ``ExampleProxy`` in the Java example, is the intermediate representation used by the framework to represent instances of ``Example`` within the wire format.
|
||||
|
||||
The Proxy Object
|
||||
----------------
|
||||
|
||||
The proxy object should be thought of as an intermediate representation that the serialization framework
|
||||
can reason about. One is being written for a class because, for some reason, that class cannot be
|
||||
introspected successfully but that framework. It is therefore important to note that the proxy class must
|
||||
only contain elements that the framework can reason about.
|
||||
|
||||
The proxy class itself is distinct from the proxy serializer. The serializer must refer to the unserializable
|
||||
type in the ``toProxy`` and ``fromProxy`` methods.
|
||||
|
||||
For example, the first thought a developer may have when implementing a proxy class is to simply *wrap* an
|
||||
instance of the object being proxied. This is shown below
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class ExampleSerializer : SerializationCustomSerializer<Example, ExampleSerializer.Proxy> {
|
||||
data class Proxy(val a: Int, val b: Int)
|
||||
/**
|
||||
* In this example, we are trying to wrap the Example type to make it serializable
|
||||
*/
|
||||
data class Proxy(val e: Example)
|
||||
|
||||
override fun toProxy(obj: Example) = Proxy(obj.a, obj.b)
|
||||
override fun toProxy(obj: Example) = Proxy(obj)
|
||||
|
||||
override fun fromProxy(proxy: Proxy) : Example {
|
||||
val constructorArg = IntArray(2);
|
||||
constructorArg[0] = proxy.a
|
||||
constructorArg[1] = proxy.b
|
||||
return Example.create(constructorArg)
|
||||
return proxy.e
|
||||
}
|
||||
}
|
||||
|
||||
However, this will not work because what we've created is a recursive loop whereby synthesising a serializer
|
||||
for the ``Example`` type requires synthesising one for ``ExampleSerializer.Proxy``. However, that requires
|
||||
one for ``Example`` and so on and so forth until we get a ``StackOverflowException``.
|
||||
|
||||
The solution, as shown initially, is to create the intermediate form (the Proxy object) purely in terms
|
||||
the serialization framework can reason about.
|
||||
|
||||
.. important:: When composing a proxy object for a class be aware that everything within that structure will be written
|
||||
into the serialized byte stream.
|
||||
|
||||
Whitelisting
|
||||
------------
|
||||
By writing a custom serializer for a class it has the effect of adding that class to the whitelist, meaning such
|
||||
|
@ -0,0 +1,108 @@
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.SerializationCustomSerializer;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JavaCustomSerializerTests {
|
||||
/**
|
||||
* The class lacks a public constructor that takes parameters it can associate
|
||||
* with its properties and is thus not serializable by the CORDA serialization
|
||||
* framework.
|
||||
*/
|
||||
static class Example {
|
||||
private Integer a;
|
||||
private Integer b;
|
||||
|
||||
Integer getA() { return a; }
|
||||
Integer getB() { return b; }
|
||||
|
||||
public Example(List<Integer> l) {
|
||||
this.a = l.get(0);
|
||||
this.b = l.get(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the class that will Proxy instances of Example within the serializer
|
||||
*/
|
||||
public static class ExampleProxy {
|
||||
/**
|
||||
* These properties will be serialized into the byte stream, this is where we choose how to
|
||||
* represent instances of the object we're proxying. In this example, which is somewhat
|
||||
* contrived, this choice is obvious. In your own classes / 3rd party libraries, however, this
|
||||
* may require more thought.
|
||||
*/
|
||||
private Integer proxiedA;
|
||||
private Integer proxiedB;
|
||||
|
||||
/**
|
||||
* The proxu class itself must be serializable by the framework, it must thus have a constructor that
|
||||
* can be mapped to the properties of the class via getter methods.
|
||||
*/
|
||||
public Integer getProxiedA() { return proxiedA; }
|
||||
public Integer getProxiedB() { return proxiedB; }
|
||||
|
||||
|
||||
public ExampleProxy(Integer proxiedA, Integer proxiedB) {
|
||||
this.proxiedA = proxiedA;
|
||||
this.proxiedB = proxiedB;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finally this is the custom serializer that will automatically loaded into the serialization
|
||||
* framework when the CorDapp Jar is scanned at runtime.
|
||||
*/
|
||||
public static class ExampleSerializer implements SerializationCustomSerializer<Example, ExampleProxy> {
|
||||
|
||||
/**
|
||||
* Given an instance of the Example class, create an instance of the proxying object ExampleProxy.
|
||||
*
|
||||
* Essentially convert Example -> ExampleProxy
|
||||
*/
|
||||
public ExampleProxy toProxy(Example obj) {
|
||||
return new ExampleProxy(obj.getA(), obj.getB());
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversely, given an instance of the proxy object, revert that back to an instance of the
|
||||
* type being proxied.
|
||||
*
|
||||
* Essentially convert ExampleProxy -> Example
|
||||
*
|
||||
*/
|
||||
public Example fromProxy(ExampleProxy proxy) {
|
||||
List<Integer> l = new ArrayList<Integer>(2);
|
||||
l.add(proxy.getProxiedA());
|
||||
l.add(proxy.getProxiedB());
|
||||
return new Example(l);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeExample() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
|
||||
List<Integer> l = new ArrayList<Integer>(2);
|
||||
l.add(10);
|
||||
l.add(20);
|
||||
Example e = new Example(l);
|
||||
|
||||
CorDappCustomSerializer ccs = new CorDappCustomSerializer(new ExampleSerializer(), factory);
|
||||
factory.registerExternal(ccs);
|
||||
|
||||
ser.serialize(e, TestSerializationContext.testSerializationContext);
|
||||
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user