From 1dbce493593b67d52cac3baa06a8211433560204 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 6 Feb 2012 21:12:26 +0100 Subject: [PATCH 1/2] Configure serializer with class as key. See #1789 --- .../akka/serialization/SerializeSpec.scala | 96 +++++++------ akka-actor/src/main/resources/reference.conf | 24 ++-- .../akka/serialization/Serialization.scala | 132 +++++++++--------- .../SerializationDocTestBase.java | 54 ------- akka-docs/java/serialization.rst | 41 ++---- .../serialization/SerializationDocSpec.scala | 32 ++--- akka-docs/scala/serialization.rst | 33 +---- akka-remote/src/main/resources/reference.conf | 15 ++ .../ProtobufSerializerSpec.scala | 25 ++++ 9 files changed, 207 insertions(+), 245 deletions(-) create mode 100644 akka-remote/src/test/scala/akka/serialization/ProtobufSerializerSpec.scala diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala index df2170905e..8cf314e0d5 100644 --- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala @@ -5,7 +5,6 @@ package akka.serialization import akka.testkit.AkkaSpec -import com.typesafe.config.ConfigFactory import akka.actor._ import java.io._ import akka.dispatch.Await @@ -17,21 +16,25 @@ import akka.pattern.ask object SerializeSpec { - val serializationConf = ConfigFactory.parseString(""" + val config = """ akka { actor { serializers { - java = "akka.serialization.JavaSerializer" test = "akka.serialization.TestSerializer" } serialization-bindings { - java = ["akka.serialization.SerializeSpec$Person", "akka.serialization.SerializeSpec$Address", "akka.serialization.MyJavaSerializableActor", "akka.serialization.MyStatelessActorWithMessagesInMailbox", "akka.serialization.MyActorWithProtobufMessagesInMailbox"] - test = ["akka.serialization.TestSerializble", "akka.serialization.SerializeSpec$PlainMessage"] + "akka.serialization.SerializeSpec$Person" = java + "akka.serialization.SerializeSpec$Address" = java + "akka.serialization.TestSerializble" = test + "akka.serialization.SerializeSpec$PlainMessage" = test + "akka.serialization.SerializeSpec$A" = java + "akka.serialization.SerializeSpec$B" = test + "akka.serialization.SerializeSpec$D" = test } } } - """) + """ @BeanInfo case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") } @@ -54,10 +57,18 @@ object SerializeSpec { class ExtendedPlainMessage extends PlainMessage + class Both(s: String) extends SimpleMessage(s) with Serializable + + trait A + trait B + class C extends B with A + class D extends A + class E extends D + } @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class SerializeSpec extends AkkaSpec(SerializeSpec.serializationConf) { +class SerializeSpec extends AkkaSpec(SerializeSpec.config) { import SerializeSpec._ val ser = SerializationExtension(system) @@ -69,8 +80,8 @@ class SerializeSpec extends AkkaSpec(SerializeSpec.serializationConf) { "Serialization" must { "have correct bindings" in { - ser.bindings(addr.getClass.getName) must be("java") - ser.bindings(classOf[PlainMessage].getName) must be("test") + ser.bindings.find(_._1 == addr.getClass).map(_._2.getClass) must be(Some(classOf[JavaSerializer])) + ser.bindings.find(_._1 == classOf[PlainMessage]).map(_._2.getClass) must be(Some(classOf[TestSerializer])) } "serialize Address" in { @@ -144,58 +155,64 @@ class SerializeSpec extends AkkaSpec(SerializeSpec.serializationConf) { } } - "resove serializer by direct interface" in { - val msg = new SimpleMessage("foo") - ser.serializerFor(msg.getClass).getClass must be(classOf[TestSerializer]) + "resolve serializer by direct interface" in { + ser.serializerFor(classOf[SimpleMessage]).getClass must be(classOf[TestSerializer]) } - "resove serializer by interface implemented by super class" in { - val msg = new ExtendedSimpleMessage("foo", 17) - ser.serializerFor(msg.getClass).getClass must be(classOf[TestSerializer]) + "resolve serializer by interface implemented by super class" in { + ser.serializerFor(classOf[ExtendedSimpleMessage]).getClass must be(classOf[TestSerializer]) } - "resove serializer by indirect interface" in { - val msg = new AnotherMessage - ser.serializerFor(msg.getClass).getClass must be(classOf[TestSerializer]) + "resolve serializer by indirect interface" in { + ser.serializerFor(classOf[AnotherMessage]).getClass must be(classOf[TestSerializer]) } - "resove serializer by indirect interface implemented by super class" in { - val msg = new ExtendedAnotherMessage - ser.serializerFor(msg.getClass).getClass must be(classOf[TestSerializer]) + "resolve serializer by indirect interface implemented by super class" in { + ser.serializerFor(classOf[ExtendedAnotherMessage]).getClass must be(classOf[TestSerializer]) } - "resove serializer for message with binding" in { - val msg = new PlainMessage - ser.serializerFor(msg.getClass).getClass must be(classOf[TestSerializer]) + "resolve serializer for message with binding" in { + ser.serializerFor(classOf[PlainMessage]).getClass must be(classOf[TestSerializer]) } - "resove serializer for message extending class with with binding" in { - val msg = new ExtendedPlainMessage - ser.serializerFor(msg.getClass).getClass must be(classOf[TestSerializer]) + "resolve serializer for message extending class with with binding" in { + ser.serializerFor(classOf[ExtendedPlainMessage]).getClass must be(classOf[TestSerializer]) + } + + "resolve serializer for message with several bindings" in { + ser.serializerFor(classOf[Both]).getClass must be(classOf[TestSerializer]) + } + + "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 { + ser.serializerFor(classOf[A]).getClass must be(classOf[JavaSerializer]) + ser.serializerFor(classOf[D]).getClass must be(classOf[TestSerializer]) + ser.serializerFor(classOf[E]).getClass must be(classOf[TestSerializer]) + } + + "throw java.io.NotSerializableException when no binding" in { + intercept[java.io.NotSerializableException] { + ser.serializerFor(classOf[Actor]) + } } } } object VerifySerializabilitySpec { - val conf = ConfigFactory.parseString(""" + val conf = """ akka { actor { serialize-messages = on - serialize-creators = on - - serializers { - java = "akka.serialization.JavaSerializer" - default = "akka.serialization.JavaSerializer" - } - - serialization-bindings { - java = ["akka.serialization.SerializeSpec$Address", "akka.serialization.MyJavaSerializableActor", "akka.serialization.MyStatelessActorWithMessagesInMailbox", "akka.serialization.MyActorWithProtobufMessagesInMailbox"] - } } } - """) + """ class FooActor extends Actor { def receive = { @@ -210,6 +227,7 @@ object VerifySerializabilitySpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class VerifySerializabilitySpec extends AkkaSpec(VerifySerializabilitySpec.conf) { import VerifySerializabilitySpec._ implicit val timeout = Timeout(5 seconds) diff --git a/akka-actor/src/main/resources/reference.conf b/akka-actor/src/main/resources/reference.conf index cdab8e968e..ebeb7766ba 100644 --- a/akka-actor/src/main/resources/reference.conf +++ b/akka-actor/src/main/resources/reference.conf @@ -97,7 +97,7 @@ akka { paths = [] } - # Routers with dynamically resizable number of routees; this feature is enabled + # Routers with dynamically resizable number of routees; this feature is enabled # by including (parts of) this section in the deployment resizer { @@ -262,23 +262,19 @@ akka { event-stream = off } - # Entries for pluggable serializers and their bindings. If a binding for a specific - # class is not found, then the default serializer (Java serialization) is used. + # Entries for pluggable serializers and their bindings. serializers { - # java = "akka.serialization.JavaSerializer" + 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"] - # } + # 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 + # using the most specific configured class, and secondly the entry configured first. + serialization-bindings { + "java.io.Serializable" = java + #"com.google.protobuf.Message" = proto + } } # Used to set the behavior of the scheduler. diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala index 05612b9cca..2d313b05a0 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala @@ -8,14 +8,21 @@ import akka.AkkaException import akka.util.ReflectiveAccess import scala.util.DynamicVariable import com.typesafe.config.Config -import akka.config.ConfigurationException import akka.actor.{ Extension, ActorSystem, ExtendedActorSystem, Address } import java.util.concurrent.ConcurrentHashMap import akka.event.Logging +import scala.collection.mutable.ArrayBuffer +import java.io.NotSerializableException case class NoSerializerFoundException(m: String) extends AkkaException(m) object Serialization { + + /** + * Tuple that represents mapping from Class to Serializer + */ + type ClassSerializer = (Class[_], Serializer) + /** * This holds a reference to the current ActorSystem (the surrounding context) * during serialization and deserialization. @@ -40,28 +47,19 @@ object Serialization { import scala.collection.JavaConverters._ import config._ - val Serializers: Map[String, String] = - getConfig("akka.actor.serializers").root.unwrapped.asScala.toMap.map { case (k, v) ⇒ (k, v.toString) } + val Serializers: Map[String, String] = configToMap(getConfig("akka.actor.serializers")) - val SerializationBindings: Map[String, Seq[String]] = { - val configPath = "akka.actor.serialization-bindings" - hasPath(configPath) match { - case false ⇒ Map() - case true ⇒ - val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).root.unwrapped.asScala.toMap.map { - case (k: String, v: java.util.Collection[_]) ⇒ (k -> v.asScala.toSeq.asInstanceOf[Seq[String]]) - case invalid ⇒ throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid)) - } - serializationBindings + val SerializationBindings: Map[String, String] = configToMap(getConfig("akka.actor.serialization-bindings")) + + private def configToMap(cfg: Config): Map[String, String] = + cfg.root.unwrapped.asScala.toMap.map { case (k, v) ⇒ (k, v.toString) } - } - } } } /** * Serialization module. Contains methods for serialization and deserialization as well as - * locating a Serializer for a particular class as defined in the mapping in the 'akka.conf' file. + * locating a Serializer for a particular class as defined in the mapping in the configuration. */ class Serialization(val system: ExtendedActorSystem) extends Extension { import Serialization._ @@ -105,8 +103,10 @@ class Serialization(val system: ExtendedActorSystem) extends Extension { } catch { case e: Exception ⇒ Left(e) } /** - * Returns the Serializer configured for the given object, returns the NullSerializer if it's null, - * falls back to the Serializer named "default" + * Returns the Serializer configured for the given object, returns the NullSerializer if it's null. + * + * @throws akka.config.ConfigurationException if no `serialization-bindings` is configured for the + * class of the object */ def findSerializerFor(o: AnyRef): Serializer = o match { case null ⇒ NullSerializer @@ -114,82 +114,86 @@ class Serialization(val system: ExtendedActorSystem) extends Extension { } /** - * Returns the configured Serializer for the given Class, falls back to the Serializer named "default". - * It traverses interfaces and super classes to find any configured Serializer that match - * the class name. + * Returns the configured Serializer for the given Class. The configured Serializer + * is used if the configured class `isAssignableFrom` from the `clazz`, i.e. + * the configured class is a super class or implemented interface. In case of + * ambiguity it is primarily using the most specific configured class, + * and secondly the entry configured first. + * + * @throws java.io.NotSerializableException if no `serialization-bindings` is configured for the class */ def serializerFor(clazz: Class[_]): Serializer = - if (bindings.isEmpty) { - // quick path to default when no bindings are registered - serializers("default") - } else { - - def resolve(c: Class[_]): Option[Serializer] = - serializerMap.get(c.getName) match { - case null ⇒ - val classes = c.getInterfaces ++ Option(c.getSuperclass) - classes.view map resolve collectFirst { case Some(x) ⇒ x } - case x ⇒ Some(x) + serializerMap.get(clazz) match { + case null ⇒ + val ser = bindings.find { case (c, s) ⇒ c.isAssignableFrom(clazz) } match { + case None ⇒ throw new NotSerializableException( + "No configured serialization-bindings for class [%s]" format clazz.getName) + case Some((c, s)) ⇒ s } - - serializerMap.get(clazz.getName) match { - case null ⇒ - val ser = resolve(clazz).getOrElse(serializers("default")) - // memorize the lookups for performance - serializerMap.putIfAbsent(clazz.getName, ser) match { - case null ⇒ - log.debug("Using serializer[{}] for message [{}]", ser.getClass.getName, clazz.getName) - ser - case some ⇒ some - } - case ser ⇒ ser - } + // memorize for performance + serializerMap.putIfAbsent(clazz, ser) match { + case null ⇒ + log.debug("Using serializer[{}] for message [{}]", ser.getClass.getName, clazz.getName) + ser + case some ⇒ some + } + case ser ⇒ ser } /** - * Tries to load the specified Serializer by the FQN + * Tries to instantiate the specified Serializer by the FQN */ def serializerOf(serializerFQN: String): Either[Exception, Serializer] = ReflectiveAccess.createInstance(serializerFQN, ReflectiveAccess.noParams, ReflectiveAccess.noArgs, system.internalClassLoader) /** * A Map of serializer from alias to implementation (class implementing akka.serialization.Serializer) - * By default always contains the following mapping: "default" -> akka.serialization.JavaSerializer - * But "default" can be overridden in config + * By default always contains the following mapping: "java" -> akka.serialization.JavaSerializer */ - lazy val serializers: Map[String, Serializer] = { - val serializersConf = settings.Serializers - for ((k: String, v: String) ← serializersConf) + private val serializers: Map[String, Serializer] = { + for ((k: String, v: String) ← settings.Serializers) yield k -> serializerOf(v).fold(throw _, identity) } /** - * bindings is a Map whose keys = FQN of class that is serializable and values = the alias of the serializer to be used + * bindings is a Seq of tuple representing the mapping from Class to Serializer. + * It is primarily ordered by the most specific classes first, and secondly in the configured order. */ - lazy val bindings: Map[String, String] = { - settings.SerializationBindings.foldLeft(Map[String, String]()) { - //All keys which are lists, take the Strings from them and Map them - case (result, (k: String, vs: Seq[_])) ⇒ result ++ (vs collect { case v: String ⇒ (v, k) }) - //For any other values, just skip them - case (result, _) ⇒ result + private[akka] val bindings: Seq[ClassSerializer] = { + val configuredBindings = for ((k: String, v: String) ← settings.SerializationBindings) yield { + val c = ReflectiveAccess.getClassFor(k, system.internalClassLoader).fold(throw _, (c: Class[_]) ⇒ c) + (c, serializers(v)) } + sort(configuredBindings) } /** - * serializerMap is a Map whose keys = FQN of class that is serializable and values is the serializer to be used for that class + * Sort so that subtypes always precede their supertypes, but without + * obeying any order between unrelated subtypes (insert sort). */ - private lazy val serializerMap: ConcurrentHashMap[String, Serializer] = { - val serializerMap = new ConcurrentHashMap[String, Serializer] - for ((k, v) ← bindings) { - serializerMap.put(k, serializers(v)) + private def sort(in: Iterable[ClassSerializer]): Seq[ClassSerializer] = + (new ArrayBuffer[ClassSerializer](in.size) /: in) { (buf, ca) ⇒ + buf.indexWhere(_._1 isAssignableFrom ca._1) match { + case -1 ⇒ buf append ca + case x ⇒ buf insert (x, ca) + } + buf } + + /** + * serializerMap is a Map whose keys is the class that is serializable and values is the serializer + * to be used for that class. + */ + private val serializerMap: ConcurrentHashMap[Class[_], Serializer] = { + val serializerMap = new ConcurrentHashMap[Class[_], Serializer] + for ((c, s) ← bindings) serializerMap.put(c, s) serializerMap } /** * Maps from a Serializer Identity (Int) to a Serializer instance (optimization) */ - lazy val serializerByIdentity: Map[Int, Serializer] = + val serializerByIdentity: Map[Int, Serializer] = Map(NullSerializer.identifier -> NullSerializer) ++ serializers map { case (_, v) ⇒ (v.identifier, v) } } diff --git a/akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java b/akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java index b68f8f2e79..3db385ca1c 100644 --- a/akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java +++ b/akka-docs/java/code/akka/docs/serialization/SerializationDocTestBase.java @@ -54,61 +54,7 @@ public class SerializationDocTestBase { } } //#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" - proto = "akka.serialization.ProtobufSerializer" - myown = "akka.docs.serialization.MyOwnSerializer" - } - - serialization-bindings { - java = ["java.lang.String", - "app.my.Customer"] - proto = ["com.google.protobuf.Message"] - myown = ["my.own.BusinessObject", - "something.equally.Awesome", - "akka.docs.serialization.MyOwnSerializable" - "java.lang.Boolean"] - } - } - } - //#serialization-bindings-config - */ - } @Test public void demonstrateTheProgrammaticAPI() { //#programmatic diff --git a/akka-docs/java/serialization.rst b/akka-docs/java/serialization.rst index 2920538ded..b0721e82cc 100644 --- a/akka-docs/java/serialization.rst +++ b/akka-docs/java/serialization.rst @@ -25,47 +25,28 @@ For Akka to know which ``Serializer`` to use for what, you need edit your :ref:` 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. +.. includecode:: ../scala/code/akka/docs/serialization/SerializationDocSpec.scala#serialize-serializers-config 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 +.. includecode:: ../scala/code/akka/docs/serialization/SerializationDocSpec.scala#serialization-bindings-config -.. note:: +You only need to specify the name of an interface or abstract base class of the messages. In case of ambiguity, +i.e. the message implements several of the configured classes, it is primarily using the most specific +configured class, and secondly the entry configured first. - You only need to specify the name of an interface or abstract base class if the messages implements - that. E.g. ``com.google.protobuf.Message`` for protobuf serialization. - -Protobuf --------- - -Akka provides a ``Serializer`` for `protobuf `_ messages. -To use that you need to add the following to the configuration:: - - akka { - actor { - serializers { - proto = "akka.serialization.ProtobufSerializer" - } - - serialization-bindings { - proto = ["com.google.protobuf.Message"] - } - } - } +Akka provides serializers for ``java.io.Serializable`` and `protobuf `_ +``com.google.protobuf.Message`` by default, so normally you don't need to add configuration for that, but +it can be done to force a specific serializer in case messages implements both ``java.io.Serializable`` +and ``com.google.protobuf.Message``. 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 +.. includecode:: ../scala/code/akka/docs/serialization/SerializationDocSpec.scala#serialize-messages-config .. warning:: @@ -74,7 +55,7 @@ If you want to verify that your messages are serializable you can enable the fol 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 +.. includecode:: ../scala/code/akka/docs/serialization/SerializationDocSpec.scala#serialize-creators-config .. warning:: diff --git a/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala b/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala index 7f1553f75c..ce40adce3e 100644 --- a/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala +++ b/akka-docs/scala/code/akka/docs/serialization/SerializationDocSpec.scala @@ -45,6 +45,9 @@ class MyOwnSerializer extends Serializer { } //#my-own-serializer +trait MyOwnSerializable +case class Customer(name: String) extends MyOwnSerializable + class SerializationDocSpec extends AkkaSpec { "demonstrate configuration of serialize messages" in { //#serialize-messages-config @@ -82,8 +85,8 @@ class SerializationDocSpec extends AkkaSpec { akka { actor { serializers { - default = "akka.serialization.JavaSerializer" - + java = "akka.serialization.JavaSerializer" + proto = "akka.serialization.ProtobufSerializer" myown = "akka.docs.serialization.MyOwnSerializer" } } @@ -91,8 +94,6 @@ class SerializationDocSpec extends AkkaSpec { """) //#serialize-serializers-config val a = ActorSystem("system", config) - SerializationExtension(a).serializers("default").getClass.getName must equal("akka.serialization.JavaSerializer") - SerializationExtension(a).serializers("myown").getClass.getName must equal("akka.docs.serialization.MyOwnSerializer") a.shutdown() } @@ -102,31 +103,26 @@ class SerializationDocSpec extends AkkaSpec { akka { actor { serializers { - default = "akka.serialization.JavaSerializer" java = "akka.serialization.JavaSerializer" proto = "akka.serialization.ProtobufSerializer" myown = "akka.docs.serialization.MyOwnSerializer" } serialization-bindings { - java = ["java.lang.String", - "app.my.Customer"] - proto = ["com.google.protobuf.Message"] - myown = ["my.own.BusinessObject", - "something.equally.Awesome", - "akka.docs.serialization.MyOwnSerializable" - "java.lang.Boolean"] - } + "java.lang.String" = java + "akka.docs.serialization.Customer" = java + "com.google.protobuf.Message" = proto + "akka.docs.serialization.MyOwnSerializable" = myown + "java.lang.Boolean" = myown + } } } """) //#serialization-bindings-config val a = ActorSystem("system", config) - SerializationExtension(a).serializers("default").getClass.getName must equal("akka.serialization.JavaSerializer") - SerializationExtension(a).serializers("java").getClass.getName must equal("akka.serialization.JavaSerializer") - SerializationExtension(a).serializers("myown").getClass.getName must equal("akka.docs.serialization.MyOwnSerializer") - SerializationExtension(a).serializerFor(classOf[String]).getClass.getName must equal("akka.serialization.JavaSerializer") - SerializationExtension(a).serializerFor(classOf[java.lang.Boolean]).getClass.getName must equal("akka.docs.serialization.MyOwnSerializer") + SerializationExtension(a).serializerFor(classOf[String]).getClass must equal(classOf[JavaSerializer]) + SerializationExtension(a).serializerFor(classOf[Customer]).getClass must equal(classOf[JavaSerializer]) + SerializationExtension(a).serializerFor(classOf[java.lang.Boolean]).getClass must equal(classOf[MyOwnSerializer]) a.shutdown() } diff --git a/akka-docs/scala/serialization.rst b/akka-docs/scala/serialization.rst index 6a0867dea2..9cfaf909b9 100644 --- a/akka-docs/scala/serialization.rst +++ b/akka-docs/scala/serialization.rst @@ -27,38 +27,19 @@ you wish to use, like this: .. includecode:: code/akka/docs/serialization/SerializationDocSpec.scala#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/SerializationDocSpec.scala#serialization-bindings-config -.. note:: +You only need to specify the name of an interface or abstract base class of the messages. In case of ambiguity, +i.e. the message implements several of the configured classes, it is primarily using the most specific +configured class, and secondly the entry configured first. - You only need to specify the name of an interface or abstract base class if the messages implements - that. E.g. ``com.google.protobuf.Message`` for protobuf serialization. - -Protobuf --------- - -Akka provides a ``Serializer`` for `protobuf `_ messages. -To use that you need to add the following to the configuration:: - - akka { - actor { - serializers { - proto = "akka.serialization.ProtobufSerializer" - } - - serialization-bindings { - proto = ["com.google.protobuf.Message"] - } - } - } +Akka provides serializers for ``java.io.Serializable`` and `protobuf `_ +``com.google.protobuf.Message`` by default, so normally you don't need to add configuration for that, but +it can be done to force a specific serializer in case messages implements both ``java.io.Serializable`` +and ``com.google.protobuf.Message``. Verification ------------ diff --git a/akka-remote/src/main/resources/reference.conf b/akka-remote/src/main/resources/reference.conf index 1158d12295..14269d305a 100644 --- a/akka-remote/src/main/resources/reference.conf +++ b/akka-remote/src/main/resources/reference.conf @@ -9,6 +9,21 @@ akka { actor { + # Entries for pluggable serializers and their bindings. + serializers { + java = "akka.serialization.JavaSerializer" + proto = "akka.serialization.ProtobufSerializer" + } + + + # 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 + # using the most specific configured class, and secondly the entry configured first. + serialization-bindings { + "com.google.protobuf.Message" = proto + "java.io.Serializable" = java + } + deployment { default { diff --git a/akka-remote/src/test/scala/akka/serialization/ProtobufSerializerSpec.scala b/akka-remote/src/test/scala/akka/serialization/ProtobufSerializerSpec.scala new file mode 100644 index 0000000000..474ef485d7 --- /dev/null +++ b/akka-remote/src/test/scala/akka/serialization/ProtobufSerializerSpec.scala @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.serialization + +import akka.testkit.AkkaSpec +import akka.remote.RemoteProtocol.MessageProtocol +import akka.actor.ProtobufProtocol.MyMessage + +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class ProtobufSerializerSpec extends AkkaSpec { + + val ser = SerializationExtension(system) + + "Serialization" must { + + "resolve protobuf serializer" in { + ser.serializerFor(classOf[MessageProtocol]).getClass must be(classOf[ProtobufSerializer]) + ser.serializerFor(classOf[MyMessage]).getClass must be(classOf[ProtobufSerializer]) + } + + } +} + From 239df9d5fbd47ab15b8f0ea48b2e7dad0a0c52c7 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 6 Feb 2012 22:20:38 +0100 Subject: [PATCH 2/2] Improvements after review --- .../scala/akka/serialization/SerializeSpec.scala | 4 ++-- .../scala/akka/serialization/Serialization.scala | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala index 8cf314e0d5..7b91e29bcb 100644 --- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala @@ -80,8 +80,8 @@ class SerializeSpec extends AkkaSpec(SerializeSpec.config) { "Serialization" must { "have correct bindings" in { - ser.bindings.find(_._1 == addr.getClass).map(_._2.getClass) must be(Some(classOf[JavaSerializer])) - ser.bindings.find(_._1 == classOf[PlainMessage]).map(_._2.getClass) must be(Some(classOf[TestSerializer])) + ser.bindings.collectFirst { case (c, s) if c == addr.getClass ⇒ s.getClass } must be(Some(classOf[JavaSerializer])) + ser.bindings.collectFirst { case (c, s) if c == classOf[PlainMessage] ⇒ s.getClass } must be(Some(classOf[TestSerializer])) } "serialize Address" in { diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala index 2d313b05a0..1a23833383 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala @@ -125,11 +125,11 @@ class Serialization(val system: ExtendedActorSystem) extends Extension { def serializerFor(clazz: Class[_]): Serializer = serializerMap.get(clazz) match { case null ⇒ - val ser = bindings.find { case (c, s) ⇒ c.isAssignableFrom(clazz) } match { - case None ⇒ throw new NotSerializableException( - "No configured serialization-bindings for class [%s]" format clazz.getName) - case Some((c, s)) ⇒ s - } + val ser = bindings.collectFirst { + case (c, s) if c.isAssignableFrom(clazz) ⇒ s + } getOrElse (throw new NotSerializableException( + "No configured serialization-bindings for class [%s]" format clazz.getName)) + // memorize for performance serializerMap.putIfAbsent(clazz, ser) match { case null ⇒ @@ -161,7 +161,7 @@ class Serialization(val system: ExtendedActorSystem) extends Extension { */ private[akka] val bindings: Seq[ClassSerializer] = { val configuredBindings = for ((k: String, v: String) ← settings.SerializationBindings) yield { - val c = ReflectiveAccess.getClassFor(k, system.internalClassLoader).fold(throw _, (c: Class[_]) ⇒ c) + val c = ReflectiveAccess.getClassFor(k, system.internalClassLoader).fold(throw _, identity[Class[_]]) (c, serializers(v)) } sort(configuredBindings)