com.fasterxml.jackson.module.afterburner.deser.CreatorOptimizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jackson-module-afterburner Show documentation
Show all versions of jackson-module-afterburner Show documentation
Jackson (https://github.com/FasterXML/jackson) extension module
used to enhance performance using bytecode generation to replace use of Reflection for
field access and method calls
package com.fasterxml.jackson.module.afterburner.deser;
import static org.objectweb.asm.Opcodes.*;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import com.fasterxml.jackson.module.afterburner.util.ClassName;
import com.fasterxml.jackson.module.afterburner.util.DynamicPropertyAccessorBase;
import com.fasterxml.jackson.module.afterburner.util.MyClassLoader;
/**
* Helper class that tries to generate {@link ValueInstantiator} class
* that calls constructors and/or factory methods directly, instead
* of using Reflection.
*/
public class CreatorOptimizer
extends DynamicPropertyAccessorBase
{
protected final Class> _valueClass;
protected final MyClassLoader _classLoader;
protected final StdValueInstantiator _originalInstantiator;
public CreatorOptimizer(Class> valueClass, MyClassLoader classLoader,
StdValueInstantiator orig)
{
_valueClass = valueClass;
_classLoader = classLoader;
_originalInstantiator = orig;
}
public ValueInstantiator createOptimized()
{
/* [Issue#11]: Need to avoid optimizing if we use delegate- or
* property-based creators.
*/
if (_originalInstantiator.canCreateFromObjectWith()
|| _originalInstantiator.canCreateUsingDelegate()) {
return null;
}
// for now, only consider need to handle default creator
AnnotatedWithParams defaultCreator = _originalInstantiator.getDefaultCreator();
if (defaultCreator != null) {
AnnotatedElement elem = defaultCreator.getAnnotated();
if (elem instanceof Constructor>) {
// First things first: as per [Issue#34], can NOT access private ctors or methods
Constructor> ctor = (Constructor>) elem;
if (!Modifier.isPrivate(ctor.getModifiers())) {
return createSubclass(ctor, null).with(_originalInstantiator);
}
} else if (elem instanceof Method) {
Method m = (Method) elem;
int mods = m.getModifiers();
// and as above, can't access private ones
if (Modifier.isStatic(mods) && !Modifier.isPrivate(mods)) {
return createSubclass(null, m).with(_originalInstantiator);
}
}
}
return null;
}
protected OptimizedValueInstantiator createSubclass(Constructor> ctor, Method factory)
{
MyClassLoader loader = (_classLoader == null) ?
new MyClassLoader(_valueClass.getClassLoader(), true) : _classLoader;
final ClassName baseName = ClassName.constructFor(_valueClass, "$Creator4JacksonDeserializer");
// We need to know checksum even for lookups, so generate it first
final byte[] bytecode = generateOptimized(baseName, ctor, factory);
baseName.assignChecksum(bytecode);
Class> impl = null;
try {
impl = loader.loadClass(baseName.getDottedName());
} catch (ClassNotFoundException e) { }
if (impl == null) {
impl = loader.loadAndResolve(baseName, bytecode);
}
try {
return (OptimizedValueInstantiator) impl.newInstance();
} catch (Exception e) {
throw new IllegalStateException("Failed to generate accessor class '"+baseName+"': "+e.getMessage(), e);
}
}
protected byte[] generateOptimized(ClassName baseName, Constructor> ctor, Method factory)
{
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String superClass = internalClassName(OptimizedValueInstantiator.class.getName());
final String tmpClassName = baseName.getSlashedTemplate();
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, tmpClassName, null, superClass, null);
cw.visitSource(baseName.getSourceFilename(), null);
// First: must define 2 constructors:
// (a) default constructor, for creating bogus instance (just calls default instance)
// (b) copy-constructor which takes StdValueInstantiator instance, passes to superclass
final String optimizedValueInstDesc = Type.getDescriptor(OptimizedValueInstantiator.class);
final String stdValueInstDesc = Type.getDescriptor(StdValueInstantiator.class);
// default (no-arg) constructor:
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, superClass, "", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// then single-arg constructor
mv = cw.visitMethod(ACC_PUBLIC, "", "("+stdValueInstDesc+")V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, superClass, "", "("+stdValueInstDesc+")V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// and then non-static factory method to use second constructor (implements base-class method)
// protected abstract OptimizedValueInstantiator with(StdValueInstantiator src);
mv = cw.visitMethod(ACC_PUBLIC, "with", "("
+stdValueInstDesc+")"+optimizedValueInstDesc, null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, tmpClassName);
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, tmpClassName, "", "("+stdValueInstDesc+")V", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
// And then override: public Object createUsingDefault()
mv = cw.visitMethod(ACC_PUBLIC, "createUsingDefault",
"(" +Type.getDescriptor(DeserializationContext.class)+")Ljava/lang/Object;", null, null);
mv.visitCode();
// 19-Apr-2017, tatu: Need to take care to of try catch block...
Label startTryBlock = new Label();
Label endTryBlock = new Label();
Label startCatchBlock = new Label();
// Initiale try-catch block
mv.visitTryCatchBlock(startTryBlock, endTryBlock, startCatchBlock, "java/lang/Exception");
mv.visitLabel(startTryBlock);
// Then new/static-factory call
if (ctor != null) {
addCreator(mv, ctor);
} else {
addCreator(mv, factory);
}
mv.visitInsn(ARETURN);
mv.visitLabel(endTryBlock);
// and then do catch block
mv.visitLabel(startCatchBlock);
mv.visitVarInsn(ASTORE, 2); // push Exception e
mv.visitVarInsn(ALOAD, 0); // this
mv.visitVarInsn(ALOAD, 1); // Arg #1 ("ctxt")
mv.visitVarInsn(ALOAD, 2); // caught exception
// 27-Jul-2017, tatu: as per [modules-base#27], need name not desc here. For reasons.
final String optimizedValueInstName = Type.getInternalName(OptimizedValueInstantiator.class);
mv.visitMethodInsn(INVOKEVIRTUAL,
optimizedValueInstName, "_handleInstantiationProblem",
String.format("(%s%s)Ljava/lang/Object;",
Type.getDescriptor(DeserializationContext.class),
"Ljava/lang/Exception;"),
false);
mv.visitInsn(ARETURN);
// and call it all done
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
protected void addCreator(MethodVisitor mv, Constructor> ctor)
{
Class> owner = ctor.getDeclaringClass();
String valueClassInternal = Type.getInternalName(owner);
mv.visitTypeInsn(NEW, valueClassInternal);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, valueClassInternal, "", "()V",
owner.isInterface());
}
protected void addCreator(MethodVisitor mv, Method factory)
{
Class> owner = factory.getDeclaringClass();
Class> valueClass = factory.getReturnType();
mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(owner),
factory.getName(), "()"+Type.getDescriptor(valueClass),
owner.isInterface());
}
}