Adding Java docs for Serialization, and discovered some flaws with the Java API, that have been fixed

This commit is contained in:
Viktor Klang 2011-12-30 22:00:49 +01:00
parent d8b2f88ced
commit fbb7cb20a1
10 changed files with 266 additions and 57 deletions

View file

@ -17,7 +17,7 @@ import com.google.protobuf.Message
class ProtobufSerializer extends Serializer {
val ARRAY_OF_BYTE_ARRAY = Array[Class[_]](classOf[Array[Byte]])
def includeManifest: Boolean = true
def identifier = 2: Serializer.Identifier
def identifier = 2
def toBinary(obj: AnyRef): Array[Byte] = {
if (!obj.isInstanceOf[Message]) throw new IllegalArgumentException(

View file

@ -193,7 +193,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi
private def writeReplace(): AnyRef = parameters match {
case null SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, null, null)
case ps if ps.length == 0 SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, Array[Serializer.Identifier](), Array[Array[Byte]]())
case ps if ps.length == 0 SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, Array[Int](), Array[Array[Byte]]())
case ps
val serializers: Array[Serializer] = ps map SerializationExtension(Serialization.currentSystem.value).findSerializerFor
val serializedParameters: Array[Array[Byte]] = Array.ofDim[Array[Byte]](serializers.length)
@ -207,7 +207,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi
/**
* Represents the serialized form of a MethodCall, uses readResolve and writeReplace to marshall the call
*/
case class SerializedMethodCall(ownerType: Class[_], methodName: String, parameterTypes: Array[Class[_]], serializerIdentifiers: Array[Serializer.Identifier], serializedParameters: Array[Array[Byte]]) {
case class SerializedMethodCall(ownerType: Class[_], methodName: String, parameterTypes: Array[Class[_]], serializerIdentifiers: Array[Int], serializedParameters: Array[Array[Byte]]) {
//TODO implement writeObject and readObject to serialize
//TODO Possible optimization is to special encode the parameter-types to conserve space

View file

@ -73,7 +73,7 @@ class Serialization(val system: ActorSystemImpl) extends Extension {
* Returns either the resulting object or an Exception if one was thrown.
*/
def deserialize(bytes: Array[Byte],
serializerId: Serializer.Identifier,
serializerId: Int,
clazz: Option[Class[_]],
classLoader: Option[ClassLoader]): Either[Exception, AnyRef] =
try {
@ -164,9 +164,9 @@ class Serialization(val system: ActorSystemImpl) extends Extension {
lazy val serializerMap: Map[String, Serializer] = bindings mapValues serializers
/**
* Maps from a Serializer.Identifier (Byte) to a Serializer instance (optimization)
* Maps from a Serializer Identity (Int) to a Serializer instance (optimization)
*/
lazy val serializerByIdentity: Map[Serializer.Identifier, Serializer] =
lazy val serializerByIdentity: Map[Int, Serializer] =
Map(NullSerializer.identifier -> NullSerializer) ++ serializers map { case (_, v) (v.identifier, v) }
}

View file

@ -7,10 +7,6 @@ package akka.serialization
import java.io.{ ObjectOutputStream, ByteArrayOutputStream, ObjectInputStream, ByteArrayInputStream }
import akka.util.ClassLoaderObjectInputStream
object Serializer {
type Identifier = Int
}
/**
* A Serializer represents a bimap between an object and an array of bytes representing that object
*/
@ -19,7 +15,7 @@ trait Serializer extends scala.Serializable {
* Completely unique value to identify this implementation of Serializer, used to optimize network traffic
* Values from 0 to 16 is reserved for Akka internal usage
*/
def identifier: Serializer.Identifier
def identifier: Int
/**
* Serializes the given object into an Array of Byte
@ -31,10 +27,34 @@ trait Serializer extends scala.Serializable {
*/
def includeManifest: Boolean
/**
* Deserializes the given Array of Bytes into an AnyRef
*/
def fromBinary(bytes: Array[Byte]): AnyRef = fromBinary(bytes, None, None)
/**
* Deserializes the given Array of Bytes into an AnyRef with an optional type hint
*/
def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]]): AnyRef = fromBinary(bytes, manifest, None)
/**
* Produces an object from an array of bytes, with an optional type-hint and a classloader to load the class into
*/
def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef
def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]], classLoader: Option[ClassLoader]): AnyRef
}
/**
* Java API for creating a Serializer
*/
abstract class JSerializer extends Serializer {
def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef =
fromBinary(bytes, manifest.orNull, classLoader.orNull)
/**
* This method should be overridden,
* manifest and classLoader may be null.
*/
def fromBinary(bytes: Array[Byte], manifest: Class[_], classLoader: ClassLoader): AnyRef
}
object JavaSerializer extends JavaSerializer
@ -47,7 +67,7 @@ class JavaSerializer extends Serializer {
def includeManifest: Boolean = false
def identifier = 1: Serializer.Identifier
def identifier = 1
def toBinary(o: AnyRef): Array[Byte] = {
val bos = new ByteArrayOutputStream
@ -74,7 +94,7 @@ class JavaSerializer extends Serializer {
class NullSerializer extends Serializer {
val nullAsBytes = Array[Byte]()
def includeManifest: Boolean = false
def identifier = 0: Serializer.Identifier
def identifier = 0
def toBinary(o: AnyRef) = nullAsBytes
def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef = null
}

View file

@ -0,0 +1,8 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.docs.serialization
import org.scalatest.junit.JUnitSuite
class SerializationDocTest extends SerializationDocTestBase with JUnitSuite

View file

@ -0,0 +1,135 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.docs.serialization;
import akka.serialization.JSerializer;
import akka.serialization.Serialization;
import akka.serialization.SerializationExtension;
import akka.serialization.Serializer;
import org.junit.Test;
//#imports
import akka.serialization.*;
import akka.actor.ActorSystem;
import com.typesafe.config.*;
//#imports
public class SerializationDocTestBase {
//#my-own-serializer
public static class MyOwnSerializer extends JSerializer {
// This is whether "fromBinary" requires a "clazz" or not
@Override public boolean includeManifest() {
return false;
}
// Pick a unique identifier for your Serializer,
// you've got a couple of billions to choose from,
// 0 - 16 is reserved by Akka itself
@Override public int identifier() {
return 1234567;
}
// "toBinary" serializes the given object to an Array of Bytes
@Override public byte[] toBinary(Object obj) {
// Put the code that serializes the object here
//#...
return new byte[0];
//#...
}
// "fromBinary" deserializes the given array,
// using the type hint (if any, see "includeManifest" above)
// into the optionally provided classLoader.
@Override public Object fromBinary(byte[] bytes,
Class clazz,
ClassLoader classLoader) {
// Put your code that deserializes here
//#...
return null;
//#...
}
}
//#my-own-serializer
@Test public void haveExamples() {
/*
//#serialize-messages-config
akka {
actor {
serialize-messages = on
}
}
//#serialize-messages-config
//#serialize-creators-config
akka {
actor {
serialize-creators = on
}
}
//#serialize-creators-config
//#serialize-serializers-config
akka {
actor {
serializers {
default = "akka.serialization.JavaSerializer"
myown = "akka.docs.serialization.MyOwnSerializer"
}
}
}
//#serialize-serializers-config
//#serialization-bindings-config
akka {
actor {
serializers {
default = "akka.serialization.JavaSerializer"
java = "akka.serialization.JavaSerializer"
myown = "akka.docs.serialization.MyOwnSerializer"
}
serialization-bindings {
java = ["java.lang.String",
"app.my.Customer"]
myown = ["my.own.BusinessObject",
"something.equally.Awesome",
"java.lang.Boolean"]
}
}
}
//#serialization-bindings-config
*/
}
@Test public void demonstrateTheProgrammaticAPI() {
//#programmatic
ActorSystem system = ActorSystem.create("example");
// Get the Serialization Extension
Serialization serialization = SerializationExtension.get(system);
// Have something to serialize
String original = "woohoo";
// Find the Serializer for it
Serializer serializer = serialization.findSerializerFor(original);
// Turn it into bytes
byte[] bytes = serializer.toBinary(original);
// Turn it back into an object,
// the nulls are for the class manifest and for the classloader
String back = (String)serializer.fromBinary(bytes);
// Voilá!
assertEquals(original, back);
//#programmatic
system.shutdown();
}
}

View file

@ -5,8 +5,90 @@
Serialization (Java)
#####################
Serialization will soon be documented.
.. sidebar:: Contents
Until then we refer to the following section in the configuration file:
.. contents:: :local:
* `Serializers <https://github.com/jboner/akka/blob/master/akka-actor/src/main/resources/reference.conf#L180>`_
Akka has a built-in Extension for serialization,
and it is both possible to use the built-in serializers and to write your own.
The serialization mechanism is both used by Akka internally to serialize messages,
and available for ad-hoc serialization of whatever you might need it for.
Usage
=====
Configuration
-------------
For Akka to know which ``Serializer`` to use for what, you need edit your Akka Configuration,
in the "akka.actor.serializers"-section you bind names to implementations of the ``akka.serialization.Serializer``
you wish to use, like this:
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialize-serializers-config
.. note::
The name ``default`` is special in the sense that the ``Serializer``
mapped to it will be used as default.
After you've bound names to different implementations of ``Serializer`` you need to wire which classes
should be serialized using which ``Serializer``, this is done in the "akka.actor.serialization-bindings"-section:
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialization-bindings-config
.. note::
Akka currently only checks for absolute equality of Classes, i.e. it does not yet check ``isAssignableFrom``,
this means that you'll need to list the specific classes.
Verification
------------
If you want to verify that your messages are serializable you can enable the following config option:
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialize-messages-config
.. warning::
We only recommend using the config option turned on when you're running tests.
It is completely pointless to have it turned on in other scenarios.
If you want to verify that your ``Props`` are serializable you can enable the following config option:
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java#serialize-creators-config
.. warning::
We only recommend using the config option turned on when you're running tests.
It is completely pointless to have it turned on in other scenarios.
Programmatic
------------
If you want to programmatically serialize/deserialize using Akka Serialization,
here's some examples:
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java
:include: imports,programmatic
For more information, have a look at the ``ScalaDoc`` for ``akka.serialization._``
Customization
=============
So, lets say that you want to create your own ``Serializer``,
you saw the ``akka.docs.serialization.MyOwnSerializer`` in the config example above?
Creating new Serializers
------------------------
First you need to create a class definition of your ``Serializer``,
which is done by extending ``akka.serialization.JSerializer``, like this:
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java
:include: imports,my-own-serializer
:exclude: ...
Then you only need to fill in the blanks, bind it to a name in your Akka Configuration and then
list which classes that should be serialized using it.

View file

@ -1,31 +0,0 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.docs.extension
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
class SerializationDocSpec extends WordSpec with MustMatchers {
"demonstrate how to use Serialization" in {
"""
serializers {
# java = "akka.serialization.JavaSerializer"
# proto = "akka.serialization.ProtobufSerializer"
default = "akka.serialization.JavaSerializer"
}
# serialization-bindings {
# java = ["akka.serialization.SerializeSpec$Address",
# "akka.serialization.MyJavaSerializableActor",
# "akka.serialization.MyStatelessActorWithMessagesInMailbox",
# "akka.serialization.MyActorWithProtobufMessagesInMailbox"]
# proto = ["com.google.protobuf.Message",
# "akka.actor.ProtobufProtocol$MyMessage"]
# }
"""
}
}

View file

@ -5,17 +5,12 @@ package akka.docs.serialization
import org.scalatest.matchers.MustMatchers
import akka.testkit._
//#imports
import akka.actor.ActorSystem
import akka.serialization._
import com.typesafe.config.ConfigFactory
//#imports
import akka.serialization._
//#imports
object SerializationDocSpec {
}
//#my-own-serializer
class MyOwnSerializer extends Serializer {
@ -26,7 +21,7 @@ class MyOwnSerializer extends Serializer {
// Pick a unique identifier for your Serializer,
// you've got a couple of billions to choose from,
// 0 - 16 is reserved by Akka itself
def identifier = 1234567: Serializer.Identifier
def identifier = 1234567
// "toBinary" serializes the given object to an Array of Bytes
def toBinary(obj: AnyRef): Array[Byte] = {

View file

@ -12,7 +12,7 @@ import com.google.protobuf.Message
class ProtobufSerializer extends Serializer {
val ARRAY_OF_BYTE_ARRAY = Array[Class[_]](classOf[Array[Byte]])
def includeManifest: Boolean = true
def identifier = 2: Serializer.Identifier
def identifier = 2
def toBinary(obj: AnyRef): Array[Byte] = obj match {
case m: Message m.toByteArray