java - 如果类引用原始类型,则所有访问通过 ASM 生成的类的构造函数的反射方法都会抛出 NoClassDefFoundError

标签 java reflection constructor bytecode java-bytecode-asm

我正在编写一个应用程序,其中具有特定签名的反射 Method 对象被展开为通过 ASM 生成的类中的常规 INVOKEVIRTUAL 调用,以便可以以更加注重性能的方式重复调用这些方法。要解包的方法将始终具有特定的返回类型和第一个参数,但可以具有超过该点的任何给定数量的任何类型的其他参数。

我定义了两个类来执行此操作:InvokerProxyNewInvokerProxyFactory

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.getConstructorsClass.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/

相关文章:

java - 如何以编程方式在 Java 中创建 BDE 别名?

java - 使用 GET 重定向的 JSoup cookie 处理

javascript - 注释javascript函数参数?

c# - 使用依赖注入(inject)通过构造函数反射调用c#类方法

c++ - 在将对象传递给内核之前防止复制构造函数

java - 我正在尝试做一个自然数到二进制数转换器

java - 如何正确使用原始类型

java - 如何在 Java 中使用反射访问抽象类中的私有(private)方法?

c++ - 在哪些情况下调用 C++ 复制构造函数?

c# - 用于在 Visual Studio 中创建构造函数的代码片段或快捷方式