我正在编写一个应用程序,其中具有特定签名的反射 Method 对象被展开为通过 ASM 生成的类中的常规 INVOKEVIRTUAL 调用,以便可以以更加注重性能的方式重复调用这些方法。要解包的方法将始终具有特定的返回类型和第一个参数,但可以具有超过该点的任何给定数量的任何类型的其他参数。
我定义了两个类来执行此操作: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、float 等),则尝试通过任何通常提供的反射方法查找该类的构造函数(Class.getConstructors
、Class.getDeclaredConstructor
等...)将导致 java.lang.NoClassDefFoundError
引用找到的第一个基本类型在方法签名中作为其消息。该异常显然是由 URLClassLoader.findClass
引起的,其中抛出了 ClassNotFoundException
并带有相同的消息。
显然这个问题甚至超出了构造函数的范围,因为即使 Unsafe.allocateInstance
在创建生成类的实例时也会抛出相同的异常。当展开的方法没有任何原始参数时,查找构造函数或创建实例也绝对没有问题。
最佳答案
下面的代码看起来很可疑
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(paramTypes[i]));
即使 paramTypes[i]
是原始类型,也会无条件调用此代码。然而,ASM documentation表示只能针对真实对象或数组类型调用 getInternalName
。当给定原语时,ASM 可能只是生成一个虚假的类名,因此会出现错误。
public static String getInternalName(Class c)
Returns the internal name of the given class. The internal name of a class is its fully qualified name, as returned by Class.getName(), where '.' are replaced by '/'.
Parameters:c - an object or array class.
Returns:the internal name of the given class.
另请注意,CHECKCAST 指令无论如何对于原始类型都无效。
关于java - 如果类引用原始类型,则所有访问通过 ASM 生成的类的构造函数的反射方法都会抛出 NoClassDefFoundError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38340186/