Ich schreibe eine Anwendung, in der reflektierte Methodenobjekte mit spezifischen Signaturen in regulären INVOKEVIRTUAL-Aufrufen in durch ASM generierten Klassen ausgepackt werden, so dass diese Methoden sein können wiederholt leistungsorientiert aufgerufen. Methoden, die ausgepackt werden sollen, haben immer einen spezifischen Rückgabetyp und einen ersten Parameter, können aber eine beliebige Anzahl anderer Parameter eines beliebigen Typs nach diesem Punkt haben.Alle Reflektionsmethoden, die auf den Konstruktor der durch ASM generierten Klasse zugreifen, werfen NoClassDefFoundError, wenn die Klasse primitive Typen referenziert
Ich habe zwei Klassen definiert, um dies zu tun, InvokerProxy
und NewInvokerProxyFactory
.
public interface InvokerProxy {
ExitCode execute(IODescriptor io, Object... args);
}
public final class NewInvokerProxyFactory {
private static final String GENERATED_CLASS_NAME = "InvokerProxy";
private static final Map<Class<?>, Consumer<MethodVisitor>> UNBOXING_ACTIONS;
private static final AtomicInteger NEXT_ID = new AtomicInteger();
private NewInvokerProxyFactory() {}
public static InvokerProxy makeProxy(Method backingMethod, Object methodParent) {
String proxyCanonicalName = makeUniqueName(InvokerProxyFactory.class.getPackage(), backingMethod);
String proxyJvmName = proxyCanonicalName.replace(".", "/");
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, proxyJvmName, null, Type.getInternalName(Object.class), new String[]{Type.getInternalName(InvokerProxy.class)});
cw.visitSource("<dynamic>", null);
{
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "parent", Type.getDescriptor(Object.class), null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, proxyJvmName, "parent", Type.getDescriptor(Object.class));
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "execute", Type.getMethodDescriptor(Type.getType(ExitCode.class), Type.getType(IODescriptor.class), Type.getType(Object[].class)), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, proxyJvmName, "parent", Type.getDescriptor(Object.class));
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(methodParent.getClass()));
mv.visitVarInsn(ALOAD, 1);
Class<?>[] paramTypes = backingMethod.getParameterTypes();
for (int i = 1; i < paramTypes.length; i++) {
mv.visitVarInsn(ALOAD, 2);
mv.visitLdcInsn(i-1);
mv.visitInsn(AALOAD);
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(paramTypes[i]));
if (paramTypes[i].isPrimitive()) {
UNBOXING_ACTIONS.get(paramTypes[i]).accept(mv);
}
}
mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(methodParent.getClass()), backingMethod.getName(), Type.getMethodDescriptor(backingMethod), false);
mv.visitInsn(ARETURN);
mv.visitMaxs(backingMethod.getParameterTypes().length + 2, 3);
mv.visitEnd();
}
cw.visitEnd();
try {
return (InvokerProxy) SystemClassLoader.defineClass(proxyCanonicalName, cw.toByteArray()).getDeclaredConstructor(Object.class).newInstance(methodParent);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new InvokerProxyGenerationException("Exception creating invoker proxy for method '" + backingMethod + "'", e);
}
}
private static String makeUniqueName(Package parentPackage, Method method) {
return String.format("%s.%s_%d", parentPackage.getName(), GENERATED_CLASS_NAME, NEXT_ID.getAndIncrement());
}
static {
Map<Class<?>, Consumer<MethodVisitor>> actions = new HashMap<>();
actions.put(Byte.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Byte.class), "byteValue", "()B", false));
actions.put(Short.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Short.class), "shortValue", "()S", false));
actions.put(Integer.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", "()I", false));
actions.put(Long.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Long.class), "longValue", "()J", false));
actions.put(Float.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Float.class), "floatValue", "()F", false));
actions.put(Double.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Double.class), "doubleValue", "()D", false));
actions.put(Boolean.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Boolean.class), "booleanValue", "()Z", false));
actions.put(Character.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Character.class), "charValue", "()C", false));
UNBOXING_ACTIONS = actions;
}
}
Durch Tests habe ich entdeckt, dass, wenn das Verfahren durch den InvokerProxyFactory keine primitive Parameter hat ausgepackt werden (int, char, float, etc ..), versucht, einen Konstruktor zu sehen Für diese Klasse durch eine der normalerweise zur Verfügung gestellten Reflexionsverfahren (Class.getConstructors
, Class.getDeclaredConstructor
, etc ...) wird eine java.lang.NoClassDefFoundError
führen, die den ersten in der Methodensignatur gefundenen primitiven Typ als ihre Nachricht zitiert. Die Ausnahme wird anscheinend von URLClassLoader.findClass
verursacht, wo ein ClassNotFoundException
mit der gleichen Nachricht ausgelöst wird.
Scheinbar dieses Problem geht sogar über Konstruktoren hinaus, da sogar Unsafe.allocateInstance
diese gleiche Ausnahme beim Erstellen einer Instanz der generierten Klasse auslöst. Es gibt auch absolut keine Probleme, Konstruktoren nachzuschlagen oder Instanzen zu erstellen, wenn die unverpackte Methode keine primitiven Parameter hat.
Können Sie die Stapelspur der Ausnahmebedingung und die generierte Klassendatei nach Möglichkeit veröffentlichen? Gibt es auch einen Grund, warum Sie nicht nur aufgerufenenDynamic verwenden können? Es wurde bereits entwickelt, um das zu tun, was Sie tun, aber effizienter. – Antimony
Interessantes Projekt, aber ich bin mir ziemlich sicher, dass die Reflection-Implementierung und der Hotspot-Compiler dies bereits tun. Ich würde Interesse an Benchmarks haben, sobald Sie das laufen haben. –
@ Jörn Horstmann: Ich bin sehr zuversichtlich, dass ein direkter 'MethodHandle' die Boxen nicht braucht varargs ist möglicherweise effiziente oder zumindest auf dem Niveau diesen im Vergleich zu. Und wenn die Anzahl der Parameter fest ist, kann 'LambdaMetaFactory' dasselbe tun. – Holger