在Java ASM中访问私有内部类

8

我有一个包含几个内部类的类。我想使用ASM库生成与编译时私有内部类交互的额外内部类。我的代码如下:

public class Parent {

  public void generateClass() {
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    cw.visit(49, Opcodes.ACC_PUBLIC, "Parent$OtherChild", null,
             Type.getInternalName(Child.class), new String[]{});
    // .. generate the class
    byte[] bytes = cw.toByteArray();
    Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);
  }

  private static class Child {
  }

}

如图所示,一个简单的交互示例是继承——我正在尝试生成扩展了私有内部类Child的类OtherChild。当类加载器验证类定义时,我收到以下错误消息:
IllegalAccessError: class Parent$OtherChild cannot access its superclass Parent$Child

有没有一种方法可以生成内部类,使其能够与其他私有内部类进行交互?您可以假设这是在“安全区域”中执行,因为私有内部类是可访问的。谢谢。
2个回答

3
内部类和外部类可以访问它们的私有成员变量是Java编程语言的纯构造,但并未反映在JVM的访问检查中。当内部类在Java 1.1中被引入时,它们以一种不需要对JVM进行更改的方式被引入。从JVM的角度来看,嵌套类是带有某些额外可忽略元信息的普通(顶层)类。
当内部类声明为 private 时,它的普通类访问级别即为“default”,也就是包范围私有。当它被声明为protected时,在JVM级别上将是public
当嵌套类访问彼此的私有字段或方法时,编译器会在目标类中生成具有包范围私有访问权限的合成辅助方法,以提供所需的访问权限。
因此,从JVM的角度来看,您正在尝试继承一个包范围私有类,名称中的美元符号只是普通的名称字符。生成的类具有匹配的限定名称,但您正在尝试将其定义在不同的类加载器中,因此JVM在运行时认为这些包不是相同的,尽管它们的名称相同。
如果您在同一类加载器中定义该类,可以验证包级别访问是否有效。更改此行。
Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);

to

Method m=ClassLoader.class.getDeclaredMethod(
    "defineClass", String.class, byte[].class, int.class, int.class);
m.setAccessible(true);
Class<?> genClass=(Class<?>)m.invoke(
    Child.class.getClassLoader(), "Parent$OtherChild", bytes, 0, bytes.length);

或者,您可以将Child声明为protected。由于它是一个底层的public类,因此其他类加载器可以访问它。

请注意,在这两种情况下,您没有创建一个新的内部类,而只是创建了一个名为Parent$OtherChild的类,该类扩展了一个内部类。唯一的区别是关于外部-内部类关系的元信息,但如果您向生成的类添加该属性,声称它是Parent的内部类,那么它可能会被验证器拒绝,因为Parent的元信息中没有提到内部类OtherChild的存在。这是JVM可能查看此属性的唯一位置。

但是除了反射报告内部类关系之外,顶级类和嵌套类之间没有功能上的区别。如前所述,类实际上没有访问级别protectedprivate,对于所有其他成员访问,您都必须自己生成必要的代码。如果您无法修改现有类ParentParent$Child的代码,则不能访问其private成员,除非这些合成访问器方法已经存在...


从Java 9开始,有一种在可访问上下文中定义新类的标准方式,这使得上面显示的“带访问权限覆盖的反射”方法对于此用例已过时,例如,以下内容有效:

public class Parent {
    public void generateClass() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        String superType = Type.getInternalName(Child.class);
        cw.visit(49, Opcodes.ACC_PUBLIC, "Parent$OtherChild", null, superType, null);
        MethodVisitor mv = cw.visitMethod(0, "<init>", "()V", null, null);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superType, "<init>", "()V", false);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
        // etc
        byte[] bytes = cw.toByteArray();
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        try {
            Class<?> genClass = lookup.defineClass(bytes);
            Child ch = (Child)
                lookup.findConstructor(genClass, MethodType.methodType(void.class))
                      .invoke();
            System.out.println(ch);
        } catch(Throwable ex) {
            Logger.getLogger(Parent.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    private static class Child {
        Child() {}
    }
}

简直不敢相信我竟然忘了说,你两年前给我的解决方案实际上对我有用!谢谢! - Tareq Sha
@TareqSha 亡羊补牢,为时未晚;-) 我把它当作一个机会来更新我的回答。 - Holger

-1
我将私有内部类更改为公共内部类,运行您的代码没有问题。
@Test
    public void changeToPublic() throws Exception {
        String className = "com.github.asm.Parent$Child";
        ClassReader classReader = new ClassReader(className);
        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM6, classWriter) {

            @Override
            public void visitInnerClass(String name, String outerName, String innerName, int access) {
                super.visitInnerClass(name, outerName, innerName, Modifier.PUBLIC);
            }

            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                super.visit(version, Modifier.PUBLIC, name, signature, superName, interfaces);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                return super.visitMethod(Modifier.PUBLIC, name, descriptor, signature, exceptions);
            }
        };
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
        byte[] bytes = classWriter.toByteArray();
        ClassLoaderUtils.defineClass(getClass().getClassLoader(), className, bytes);
        new Parent().generateClass();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接