Skip to content

Commit

Permalink
Add a possibility to set a custom InstantiationStrategy. The Kryo.Def…
Browse files Browse the repository at this point in the history
…aultInstantiatorStrategy implements the usual Kryo behavior, where it tries to invoke a no-arg constructor when it needs to create a new instance.

But when org.objenesis.strategy.StdInstantiatorStrategy is used, then new instances are created without invoking a no-arg constructor, even if such a constructor is available. This improves Kryo's performance on fast-serialization tests (see issue EsotericSoftware#138)
  • Loading branch information
romix committed Dec 2, 2013
1 parent 3ab6f19 commit 9f0bfa7
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 53 deletions.
134 changes: 82 additions & 52 deletions src/com/esotericsoftware/kryo/Kryo.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ public class Kryo {
private final ClassResolver classResolver;
private int nextRegisterID;
private ClassLoader classLoader = getClass().getClassLoader();
private InstantiatorStrategy strategy;
private InstantiatorStrategy defaultStrategy = new DefaultInstantiatorStrategy();
private InstantiatorStrategy strategy = new DefaultInstantiatorStrategy();
private boolean registrationRequired;

private int depth, maxDepth = Integer.MAX_VALUE;
Expand Down Expand Up @@ -333,7 +334,11 @@ public Serializer getDefaultSerializer (Class type) {
for (int i = 0, n = defaultSerializers.size(); i < n; i++) {
DefaultSerializerEntry entry = defaultSerializers.get(i);
if (entry.type.isAssignableFrom(type)) {
return entry.serializerFactory.makeSerializer(this, type);
Serializer defaultSerializer = entry.serializerFactory.makeSerializer(this, type);
//Remember that it is a default serializer set internally by Kryo
if(i > n - lowPriorityDefaultSerializerCount)
defaultSerializer.setDefaultSerializer(true);
return defaultSerializer;
}
}

Expand Down Expand Up @@ -486,7 +491,7 @@ public Registration writeClass (Output output, Class type) {
if (depth == 0 && autoReset) reset();
}
}

/** Writes an object using the registered serializer. */
public void writeObject (Output output, Object object) {
if (output == null) throw new IllegalArgumentException("output cannot be null.");
Expand Down Expand Up @@ -1032,59 +1037,16 @@ public boolean getReferences () {
public void setInstantiatorStrategy (InstantiatorStrategy strategy) {
this.strategy = strategy;
}

public InstantiatorStrategy getInstantiatorStrategy() {
return strategy;
}


/** Returns a new instantiator for creating new instances of the specified type. By default, an instantiator is returned that
* uses reflection if the class has a zero argument constructor, an exception is thrown. If a
* {@link #setInstantiatorStrategy(InstantiatorStrategy) strategy} is set, it will be used instead of throwing an exception. */
protected ObjectInstantiator newInstantiator (final Class type) {
if (!Util.isAndroid) {
// Use ReflectASM if the class is not a non-static member class.
Class enclosingType = type.getEnclosingClass();
boolean isNonStaticMemberClass = enclosingType != null && type.isMemberClass()
&& !Modifier.isStatic(type.getModifiers());
if (!isNonStaticMemberClass) {
try {
final ConstructorAccess access = ConstructorAccess.get(type);
return new ObjectInstantiator() {
public Object newInstance () {
try {
return access.newInstance();
} catch (Exception ex) {
throw new KryoException("Error constructing instance of class: " + className(type), ex);
}
}
};
} catch (Exception ignored) {
}
}
}
// Reflection.
try {
Constructor ctor;
try {
ctor = type.getConstructor((Class[])null);
} catch (Exception ex) {
ctor = type.getDeclaredConstructor((Class[])null);
ctor.setAccessible(true);
}
final Constructor constructor = ctor;
return new ObjectInstantiator() {
public Object newInstance () {
try {
return constructor.newInstance();
} catch (Exception ex) {
throw new KryoException("Error constructing instance of class: " + className(type), ex);
}
}
};
} catch (Exception ignored) {
}
if (strategy == null) {
if (type.isMemberClass() && !Modifier.isStatic(type.getModifiers()))
throw new KryoException("Class cannot be created (non-static member class): " + className(type));
else
throw new KryoException("Class cannot be created (missing no-arg constructor): " + className(type));
}
// InstantiatorStrategy.
return strategy.newInstantiatorOf(type);
}
Expand All @@ -1095,7 +1057,10 @@ public <T> T newInstance (Class<T> type) {
Registration registration = getRegistration(type);
ObjectInstantiator instantiator = registration.getInstantiator();
if (instantiator == null) {
instantiator = newInstantiator(type);
if(registration.getSerializer().isDefaultSerializer())
instantiator = defaultStrategy.newInstantiatorOf(type);
else
instantiator = newInstantiator(type);
registration.setInstantiator(instantiator);
}
return (T)instantiator.newInstance();
Expand Down Expand Up @@ -1196,4 +1161,69 @@ public void setAsmEnabled (boolean flag) {
public boolean getAsmEnabled () {
return asmEnabled;
}

static public class DefaultInstantiatorStrategy implements org.objenesis.strategy.InstantiatorStrategy {
private InstantiatorStrategy fallbackStrategy;

public void setFallbackInstantiatorStrategy(final InstantiatorStrategy fallbackStrategy) {
this.fallbackStrategy = fallbackStrategy;
}

public InstantiatorStrategy getFallbackInstantiatorStrategy() {
return fallbackStrategy;
}

public ObjectInstantiator newInstantiatorOf (final Class type) {
if (!Util.isAndroid) {
// Use ReflectASM if the class is not a non-static member class.
Class enclosingType = type.getEnclosingClass();
boolean isNonStaticMemberClass = enclosingType != null && type.isMemberClass()
&& !Modifier.isStatic(type.getModifiers());
if (!isNonStaticMemberClass) {
try {
final ConstructorAccess access = ConstructorAccess.get(type);
return new ObjectInstantiator() {
public Object newInstance () {
try {
return access.newInstance();
} catch (Exception ex) {
throw new KryoException("Error constructing instance of class: " + className(type), ex);
}
}
};
} catch (Exception ignored) {
}
}
}
// Reflection.
try {
Constructor ctor;
try {
ctor = type.getConstructor((Class[])null);
} catch (Exception ex) {
ctor = type.getDeclaredConstructor((Class[])null);
ctor.setAccessible(true);
}
final Constructor constructor = ctor;
return new ObjectInstantiator() {
public Object newInstance () {
try {
return constructor.newInstance();
} catch (Exception ex) {
throw new KryoException("Error constructing instance of class: " + className(type), ex);
}
}
};
} catch (Exception ignored) {
}
if (fallbackStrategy == null) {
if (type.isMemberClass() && !Modifier.isStatic(type.getModifiers()))
throw new KryoException("Class cannot be created (non-static member class): " + className(type));
else
throw new KryoException("Class cannot be created (missing no-arg constructor): " + className(type));
}
// InstantiatorStrategy.
return fallbackStrategy.newInstantiatorOf(type);
}
}
}
12 changes: 11 additions & 1 deletion src/com/esotericsoftware/kryo/Serializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/** Reads and writes objects to and from bytes.
* @author Nathan Sweet <[email protected]> */
public abstract class Serializer<T> {
private boolean acceptsNull, immutable;
private boolean acceptsNull, immutable, defaultSerializer;

public Serializer () {
}
Expand Down Expand Up @@ -64,6 +64,16 @@ public boolean isImmutable () {
public void setImmutable (boolean immutable) {
this.immutable = immutable;
}


public boolean isDefaultSerializer () {
return defaultSerializer;
}

/** If true, this is a default serializer */
public void setDefaultSerializer (boolean defaultSerializer) {
this.defaultSerializer = defaultSerializer;
}

/** Sets the generic types of the field or method this serializer will be used for on the next call to read or write. Subsequent
* calls to read and write must not use this generic type information. The default implementation does nothing. Subclasses may
Expand Down
29 changes: 29 additions & 0 deletions test/com/esotericsoftware/kryo/FieldSerializerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,33 @@ public void testInstantiatorStrategy () {
roundTrip(4, 4, test);
}

/** This test uses StdInstantiatorStrategy and therefore requires a no-arg constructor. **/
@SuppressWarnings("synthetic-access")
public void testDefaultInstantiatorStrategy () {
kryo.register(HasArgumentConstructor.class);
HasArgumentConstructor test = new HasPrivateConstructor();
HasPrivateConstructor.invocations = 0;

kryo.register(HasPrivateConstructor.class);
roundTrip(4, 4, test);
assertEquals("Default constructor should not be invoked with StdInstantiatorStrategy strategy", 25,
HasPrivateConstructor.invocations);
}

/** This test uses StdInstantiatorStrategy and should bypass invocation of no-arg constructor, even if it is provided. **/
@SuppressWarnings("synthetic-access")
public void testStdInstantiatorStrategy () {
kryo.register(HasArgumentConstructor.class);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
HasArgumentConstructor test = new HasPrivateConstructor();
HasPrivateConstructor.invocations = 0;

kryo.register(HasPrivateConstructor.class);
roundTrip(4, 4, test);
assertEquals("Default constructor should not be invoked with StdInstantiatorStrategy strategy", 0,
HasPrivateConstructor.invocations);
}

public void testGenericTypes () {
kryo = new Kryo();
kryo.setRegistrationRequired(true);
Expand Down Expand Up @@ -757,8 +784,10 @@ public boolean equals (Object obj) {
}

static public class HasPrivateConstructor extends HasArgumentConstructor {
static int invocations;
private HasPrivateConstructor () {
super("cow");
HasPrivateConstructor.invocations++;
}
}

Expand Down

0 comments on commit 9f0bfa7

Please sign in to comment.