change serialization to strictly rely on subtyping

- when encountering new message type, check all bindings which map apply
- if multiple are found, choose the most specific one if that exists or
  verify that all mappings yield the same serializer
- in case of remaining ambiguity, throw exception
- also add special handling for “none” serializer mapping: turn off a
  default
This commit is contained in:
Roland 2012-02-07 15:11:16 +01:00
parent 50d107e150
commit 8b9f1caf67
6 changed files with 72 additions and 44 deletions

View file

@ -179,14 +179,10 @@ class SerializeSpec extends AkkaSpec(SerializeSpec.config) {
ser.serializerFor(classOf[ExtendedPlainMessage]).getClass must be(classOf[TestSerializer]) ser.serializerFor(classOf[ExtendedPlainMessage]).getClass must be(classOf[TestSerializer])
} }
"resolve serializer for message with several bindings" in { "throw exception for message with several bindings" in {
ser.serializerFor(classOf[Both]).getClass must be(classOf[TestSerializer]) intercept[java.io.NotSerializableException] {
} ser.serializerFor(classOf[Both])
}
"resolve serializer in the order of the bindings" in {
ser.serializerFor(classOf[A]).getClass must be(classOf[JavaSerializer])
ser.serializerFor(classOf[B]).getClass must be(classOf[TestSerializer])
ser.serializerFor(classOf[C]).getClass must be(classOf[JavaSerializer])
} }
"resolve serializer in the order of most specific binding first" in { "resolve serializer in the order of most specific binding first" in {

View file

@ -269,15 +269,16 @@ akka {
# Entries for pluggable serializers and their bindings. # Entries for pluggable serializers and their bindings.
serializers { serializers {
java = "akka.serialization.JavaSerializer" java = "akka.serialization.JavaSerializer"
# proto = "akka.serialization.ProtobufSerializer"
} }
# Class to Serializer binding. You only need to specify the name of an interface # Class to Serializer binding. You only need to specify the name of an interface
# or abstract base class of the messages. In case of ambiguity it is primarily # or abstract base class of the messages. In case of ambiguity it is
# using the most specific configured class, and secondly the entry configured first. # using the most specific configured class, throwing an exception otherwise.
#
# To disable one of the default serializers, assign its class to "none", like
# "java.io.Serializable" = none
serialization-bindings { serialization-bindings {
"java.io.Serializable" = java "java.io.Serializable" = java
#"com.google.protobuf.Message" = proto
} }
} }

View file

@ -125,17 +125,23 @@ class Serialization(val system: ExtendedActorSystem) extends Extension {
def serializerFor(clazz: Class[_]): Serializer = def serializerFor(clazz: Class[_]): Serializer =
serializerMap.get(clazz) match { serializerMap.get(clazz) match {
case null case null
val ser = bindings.collectFirst { // bindings are ordered from most specific to least specific
case (c, s) if c.isAssignableFrom(clazz) s def unique(cs: Seq[Class[_]], ser: Set[Serializer]): Boolean = (cs forall (_ isAssignableFrom cs(0))) || ser.size == 1
} getOrElse (throw new NotSerializableException(
"No configured serialization-bindings for class [%s]" format clazz.getName))
// memorize for performance val possible = bindings filter { _._1 isAssignableFrom clazz }
serializerMap.putIfAbsent(clazz, ser) match { possible.size match {
case null case 0
log.debug("Using serializer[{}] for message [{}]", ser.getClass.getName, clazz.getName) throw new NotSerializableException("No configured serialization-bindings for class [%s]" format clazz.getName)
ser case x if x == 1 || unique(possible map (_._1), possible.map(_._2)(scala.collection.breakOut))
case some some val ser = possible(0)._2
serializerMap.putIfAbsent(clazz, ser) match {
case null
log.debug("Using serializer[{}] for message [{}]", ser.getClass.getName, clazz.getName)
ser
case some some
}
case _
throw new NotSerializableException("Multiple serializers found for " + clazz + ": " + possible)
} }
case ser ser case ser ser
} }
@ -160,7 +166,7 @@ class Serialization(val system: ExtendedActorSystem) extends Extension {
* It is primarily ordered by the most specific classes first, and secondly in the configured order. * It is primarily ordered by the most specific classes first, and secondly in the configured order.
*/ */
private[akka] val bindings: Seq[ClassSerializer] = { private[akka] val bindings: Seq[ClassSerializer] = {
val configuredBindings = for ((k: String, v: String) settings.SerializationBindings) yield { val configuredBindings = for ((k: String, v: String) settings.SerializationBindings if v != "none") yield {
val c = ReflectiveAccess.getClassFor(k, system.internalClassLoader).fold(throw _, identity[Class[_]]) val c = ReflectiveAccess.getClassFor(k, system.internalClassLoader).fold(throw _, identity[Class[_]])
(c, serializers(v)) (c, serializers(v))
} }

View file

@ -32,14 +32,26 @@ should be serialized using which ``Serializer``, this is done in the "akka.actor
.. includecode:: ../scala/code/akka/docs/serialization/SerializationDocSpec.scala#serialization-bindings-config .. includecode:: ../scala/code/akka/docs/serialization/SerializationDocSpec.scala#serialization-bindings-config
You only need to specify the name of an interface or abstract base class of the messages. In case of ambiguity, You only need to specify the name of an interface or abstract base class of the
i.e. the message implements several of the configured classes, it is primarily using the most specific messages. In case of ambiguity, i.e. the message implements several of the
configured class, and secondly the entry configured first. configured classes, the most specific configured class will be used, i.e. the
one of which all other candidates are superclasses. If this condition cannot be
met, because e.g. ``java.io.Serializable`` and ``MyOwnSerializable`` both apply
and neither is a subtype of the other, an exception will be thrown during
serialization.
Akka provides serializers for ``java.io.Serializable`` and `protobuf <http://code.google.com/p/protobuf/>`_ Akka provides serializers for :class:`java.io.Serializable` and `protobuf
``com.google.protobuf.Message`` by default, so normally you don't need to add configuration for that, but <http://code.google.com/p/protobuf/>`_
it can be done to force a specific serializer in case messages implements both ``java.io.Serializable`` :class:`com.google.protobuf.GeneratedMessage` by default (the latter only if
and ``com.google.protobuf.Message``. depending on the akka-remote module), so normally you don't need to add
configuration for that; since :class:`com.google.protobuf.GeneratedMessage`
implements :class:`java.io.Serializable`, protobuf messages will always by
serialized using the protobuf protocol unless specifically overridden. In order
to disable a default serializer, map its marker type to “none”::
akka.actor.serialization-bindings {
"java.io.Serializable" = none
}
Verification Verification
------------ ------------

View file

@ -32,14 +32,26 @@ should be serialized using which ``Serializer``, this is done in the "akka.actor
.. includecode:: code/akka/docs/serialization/SerializationDocSpec.scala#serialization-bindings-config .. includecode:: code/akka/docs/serialization/SerializationDocSpec.scala#serialization-bindings-config
You only need to specify the name of an interface or abstract base class of the messages. In case of ambiguity, You only need to specify the name of an interface or abstract base class of the
i.e. the message implements several of the configured classes, it is primarily using the most specific messages. In case of ambiguity, i.e. the message implements several of the
configured class, and secondly the entry configured first. configured classes, the most specific configured class will be used, i.e. the
one of which all other candidates are superclasses. If this condition cannot be
met, because e.g. ``java.io.Serializable`` and ``MyOwnSerializable`` both apply
and neither is a subtype of the other, an exception will be thrown during
serialization.
Akka provides serializers for ``java.io.Serializable`` and `protobuf <http://code.google.com/p/protobuf/>`_ Akka provides serializers for :class:`java.io.Serializable` and `protobuf
``com.google.protobuf.Message`` by default, so normally you don't need to add configuration for that, but <http://code.google.com/p/protobuf/>`_
it can be done to force a specific serializer in case messages implements both ``java.io.Serializable`` :class:`com.google.protobuf.GeneratedMessage` by default (the latter only if
and ``com.google.protobuf.Message``. depending on the akka-remote module), so normally you don't need to add
configuration for that; since :class:`com.google.protobuf.GeneratedMessage`
implements :class:`java.io.Serializable`, protobuf messages will always by
serialized using the protobuf protocol unless specifically overridden. In order
to disable a default serializer, map its marker type to “none”::
akka.actor.serialization-bindings {
"java.io.Serializable" = none
}
Verification Verification
------------ ------------

View file

@ -11,17 +11,18 @@ akka {
# Entries for pluggable serializers and their bindings. # Entries for pluggable serializers and their bindings.
serializers { serializers {
java = "akka.serialization.JavaSerializer"
proto = "akka.serialization.ProtobufSerializer" proto = "akka.serialization.ProtobufSerializer"
} }
# Class to Serializer binding. You only need to specify the name of an interface # Class to Serializer binding. You only need to specify the name of an interface
# or abstract base class of the messages. In case of ambiguity it is primarily # or abstract base class of the messages. In case of ambiguity it is
# using the most specific configured class, and secondly the entry configured first. # using the most specific configured class, giving an error if two mappings are found
# which cannot be decided by sub-typing relation.
serialization-bindings { serialization-bindings {
"com.google.protobuf.Message" = proto # Since com.google.protobuf.Message does not extend Serializable but GeneratedMessage
"java.io.Serializable" = java # does, need to use the more specific one here in order to avoid ambiguity
"com.google.protobuf.GeneratedMessage" = proto
} }
deployment { deployment {