Serialization
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 環境設定,
in the "akka.actor.serializers"-section you bind names to implementations of the akka.serialization.Serializer
you wish to use, like this:
akka {
actor {
serializers {
java = "akka.serialization.JavaSerializer"
proto = "akka.remote.serialization.ProtobufSerializer"
myown = "docs.serialization.MyOwnSerializer"
}
}
}
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:
akka {
actor {
serializers {
java = "akka.serialization.JavaSerializer"
proto = "akka.remote.serialization.ProtobufSerializer"
myown = "docs.serialization.MyOwnSerializer"
}
serialization-bindings {
"java.lang.String" = java
"docs.serialization.Customer" = java
"com.google.protobuf.Message" = proto
"docs.serialization.MyOwnSerializable" = myown
"java.lang.Boolean" = myown
}
}
}
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, 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, a warning will be issued.
注釈
If you are using Scala for your message protocol and your messages are contained
inside of a Scala object, then in order to reference those messages, you will need
use the fully qualified Java class name. For a message named Message
contained inside
the Scala object named Wrapper
you would need to reference it as
Wrapper$Message
instead of Wrapper.Message
.
Akka provides serializers for java.io.Serializable
and protobuf
com.google.protobuf.GeneratedMessage
by default (the latter only if
depending on the akka-remote module), so normally you don't need to add
configuration for that; since com.google.protobuf.GeneratedMessage
implements java.io.Serializable
, protobuf messages will always be
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
If you want to verify that your messages are serializable you can enable the following config option:
akka {
actor {
serialize-messages = on
}
}
警告
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:
akka {
actor {
serialize-creators = on
}
}
警告
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:
import akka.actor.*;
import akka.serialization.*;
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);
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 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:
import akka.actor.*;
import akka.serialization.*;
public 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
// ... ...
}
// "fromBinary" deserializes the given array,
// using the type hint (if any, see "includeManifest" above)
@Override public Object fromBinaryJava(byte[] bytes,
Class<?> clazz) {
// Put your code that deserializes here
// ... ...
}
}
The manifest is a type hint so that the same serializer can be used for different
classes. The manifest parameter in fromBinaryJava
is the class of the object that
was serialized. In fromBinary
you can match on the class and deserialize the
bytes to different objects.
Then you only need to fill in the blanks, bind it to a name in your 環境設定 and then list which classes that should be serialized using it.
Serializer with String Manifest
The Serializer
illustrated above supports a class based manifest (type hint).
For serialization of data that need to evolve over time the SerializerWithStringManifest
is recommended instead of Serializer
because the manifest (type hint) is a String
instead of a Class
. That means that the class can be moved/removed and the serializer
can still deserialize old data by matching on the String
. This is especially useful
for 永続化.
The manifest string can also encode a version number that can be used in fromBinary
to
deserialize in different ways to migrate old data to new domain objects.
If the data was originally serialized with Serializer
and in a later version of the
system you change to SerializerWithStringManifest
the manifest string will be the full
class name if you used includeManifest=true
, otherwise it will be the empty string.
This is how a SerializerWithStringManifest
looks like:
public class MyOwnSerializer2 extends SerializerWithStringManifest {
private static final String CUSTOMER_MANIFEST = "customer";
private static final String USER_MANIFEST = "user";
private static final String UTF_8 = StandardCharsets.UTF_8.name();
// 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;
}
@Override public String manifest(Object obj) {
if (obj instanceof Customer)
return CUSTOMER_MANIFEST;
else if (obj instanceof User)
return USER_MANIFEST;
else
throw new IllegalArgumentException("Unknown type: " + obj);
}
// "toBinary" serializes the given object to an Array of Bytes
@Override public byte[] toBinary(Object obj) {
// Put the real code that serializes the object here
try {
if (obj instanceof Customer)
return ((Customer) obj).name.getBytes(UTF_8);
else if (obj instanceof User)
return ((User) obj).name.getBytes(UTF_8);
else
throw new IllegalArgumentException("Unknown type: " + obj);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
// "fromBinary" deserializes the given array,
// using the type hint
@Override public Object fromBinary(byte[] bytes, String manifest) {
// Put the real code that deserializes here
try {
if (manifest.equals(CUSTOMER_MANIFEST))
return new Customer(new String(bytes, UTF_8));
else if (manifest.equals(USER_MANIFEST))
return new User(new String(bytes, UTF_8));
else
throw new IllegalArgumentException("Unknown manifest: " + manifest);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
You must also bind it to a name in your 環境設定 and then list which classes that should be serialized using it.
Serializing ActorRefs
All ActorRefs are serializable using JavaSerializer, but in case you are writing your
own serializer, you might want to know how to serialize and deserialize them properly.
In the general case, the local address to be used depends on the type of remote
address which shall be the recipient of the serialized information. Use
Serialization.serializedActorPath(actorRef)
like this:
import akka.actor.*;
import akka.serialization.*;
// Serialize
// (beneath toBinary)
String identifier = Serialization.serializedActorPath(theActorRef);
// Then just serialize the identifier however you like
// Deserialize
// (beneath fromBinary)
final ActorRef deserializedActorRef = extendedSystem.provider().resolveActorRef(
identifier);
// Then just use the ActorRef
This assumes that serialization happens in the context of sending a message
through the remote transport. There are other uses of serialization, though,
e.g. storing actor references outside of an actor application (database, etc.).
In this case, it is important to keep in mind that the
address part of an actor’s path determines how that actor is communicated with.
Storing a local actor path might be the right choice if the retrieval happens
in the same logical context, but it is not enough when deserializing it on a
different network host: for that it would need to include the system’s remote
transport address. An actor system is not limited to having just one remote
transport per se, which makes this question a bit more interesting. To find out
the appropriate address to use when sending to remoteAddr
you can use
ActorRefProvider.getExternalAddressFor(remoteAddr)
like this:
public class ExternalAddressExt implements Extension {
private final ExtendedActorSystem system;
public ExternalAddressExt(ExtendedActorSystem system) {
this.system = system;
}
public Address getAddressFor(Address remoteAddress) {
final scala.Option<Address> optAddr = system.provider()
.getExternalAddressFor(remoteAddress);
if (optAddr.isDefined()) {
return optAddr.get();
} else {
throw new UnsupportedOperationException(
"cannot send to remote address " + remoteAddress);
}
}
}
public class ExternalAddress extends
AbstractExtensionId<ExternalAddressExt> implements ExtensionIdProvider {
public static final ExternalAddress ID = new ExternalAddress();
public ExternalAddress lookup() {
return ID;
}
public ExternalAddressExt createExtension(ExtendedActorSystem system) {
return new ExternalAddressExt(system);
}
}
public class ExternalAddressExample {
public String serializeTo(ActorRef ref, Address remote) {
return ref.path().toSerializationFormatWithAddress(
ExternalAddress.ID.get(system).getAddressFor(remote));
}
}
注釈
ActorPath.toSerializationFormatWithAddress
differs from toString
if the
address does not already have host
and port
components, i.e. it only
inserts address information for local addresses.
toSerializationFormatWithAddress
also adds the unique id of the actor, which will
change when the actor is stopped and then created again with the same name.
Sending messages to a reference pointing the old actor will not be delivered
to the new actor. If you do not want this behavior, e.g. in case of long term
storage of the reference, you can use toStringWithAddress
, which does not
include the unique id.
This requires that you know at least which type of address will be supported by
the system which will deserialize the resulting actor reference; if you have no
concrete address handy you can create a dummy one for the right protocol using
new Address(protocol, "", "", 0)
(assuming that the actual transport used is as
lenient as Akka’s RemoteActorRefProvider).
There is also a default remote address which is the one used by cluster support (and typical systems have just this one); you can get it like this:
public class DefaultAddressExt implements Extension {
private final ExtendedActorSystem system;
public DefaultAddressExt(ExtendedActorSystem system) {
this.system = system;
}
public Address getAddress() {
return system.provider().getDefaultAddress();
}
}
public class DefaultAddress extends
AbstractExtensionId<DefaultAddressExt> implements ExtensionIdProvider {
public static final DefaultAddress ID = new DefaultAddress();
public DefaultAddress lookup() {
return ID;
}
public DefaultAddressExt createExtension(ExtendedActorSystem system) {
return new DefaultAddressExt(system);
}
}
Deep serialization of Actors
The recommended approach to do deep serialization of internal actor state is to use Akka 永続化.
A Word About Java Serialization
When using Java serialization without employing the JavaSerializer
for
the task, you must make sure to supply a valid ExtendedActorSystem
in
the dynamic variable JavaSerializer.currentSystem
. This is used when
reading in the representation of an ActorRef
for turning the string
representation into a real reference. DynamicVariable
is a
thread-local variable, so be sure to have it set while deserializing anything
which might contain actor references.
Contents