Я пишу приложение, в котором отраженные объекты метода со специфическими сигнатурами разворачиваются на обычные вызовы INVOKEVIRTUAL в классах, сгенерированных через ASM, так что эти методы могут быть многократно вызываемых более сознательным образом. Методы, которые необходимо развернуть, всегда будут иметь определенный тип возврата и первый параметр, но могут иметь любое заданное количество других параметров любого типа, прошедших эту точку.Все методы отражения доступа к конструктору класса, сгенерированного через ASM throw NoClassDefFoundError, если класс ссылается на примитивный тип
Для этого я определил два класса, InvokerProxy
и 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;
}
}
Через тестирования я обнаружил, что если метод будучи разворачивал в InvokerProxyFactory имеет какие-либо примитивные параметры (INT, CHAR, поплавок и т.д ..), пытаясь посмотреть конструктор для этого класса через любой из обычно предоставляемых методов отражения (Class.getConstructors
, Class.getDeclaredConstructor
и т. д.) приведет к , цитирующему первый примитивный тип, найденный в сигнатуре метода в качестве его сообщения. Исключением, по-видимому, является URLClassLoader.findClass
, где ClassNotFoundException
сбрасывается с тем же сообщением.
Очевидно, этот вопрос выходит за рамки конструкторов, так как даже Unsafe.allocateInstance
генерирует это же исключение при создании экземпляра сгенерированного класса. Также нет проблем с поиском конструкторов или созданием экземпляров, когда развернутый метод не имеет примитивных параметров.
Не могли бы вы разместить стоп-трек исключения и сгенерированный файл классов, если это возможно? Кроме того, есть ли причина, по которой вы не можете просто использовать invokedynamic? Он уже разработан, чтобы делать то, что вы делаете, но более эффективно. – Antimony
Интересный проект, но я уверен, что реализация отражения и компилятор hotspot уже делают это. Меня бы интересовали контрольные показатели, как только у вас будет этот запуск. –
@ Jörn Horstmann: Я вполне уверен, что прямой метод MethodHandle, который не нуждается в боксом varargs, потенциально более эффективен или, по крайней мере, по сравнению с этим. И если количество параметров фиксировано, 'LambdaMetaFactory' может сделать то же самое. – Holger