java - 在 java ASM 中访问私有(private)内部类

标签 java java-bytecode-asm

我有一个包含多个内部类的类。我想使用 ASM 库生成额外的内部类,这些内部类与编译时私有(private)内部类交互。我的代码如下所示:

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 {
  }

}

如图所示,一个简单的交互示例是继承 - 我正在尝试生成扩展私有(private)内部类 Child 的类 OtherChild。我在类加载器验证类定义时收到此错误消息:

IllegalAccessError: class Parent$OtherChild cannot access its superclass Parent$Child

有没有办法生成可以与其他私有(private)内部类交互的内部类?您可以假设这是从可访问私有(private)内部类的“安全区”执行的。

谢谢

最佳答案

内部类和外部类可以访问它们的 private 成员的规则是一个纯 Java 编程语言构造,它不会反射(reflect)在 JVM 的访问检查中。在 Java 1.1 中引入内部类时,它们的引入方式不需要更改 JVM。从 JVM 的角度来看,嵌套类是带有一些额外的、可忽略的元信息的普通(顶级)类。

当一个内部类被声明为private时,它的普通类访问级别是“默认”,也就是package-private。当它被声明为 protected 时,它将在 JVM 级别上为 public

当嵌套类访问彼此的 private 字段或方法时,编译器将在目标类中生成具有 package-private 访问权限的合成辅助方法,提供所需的访问权限。

因此,从 JVM 的角度来看,您正在尝试子类化一个 package-private 类,名称中的美元只是一个普通的名称字符。生成的类具有匹配的限定名称,但您试图在不同的类加载器中定义它,因此 JVM 认为这些包在运行时不相同,尽管它们的名称相同。

如果您在同一类加载器中定义类,则可以验证包级访问是否有效。换行

Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);

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 的内部类,它可能会被 validator ,因为 Parent 的元信息没有提到内部类 OtherChild 的存在。这是 JVM 可能查看此属性的唯一位置。

但是除了反射报告内部类关系之外,顶级类和嵌套类之间在功能上没有任何区别。如前所述,类实际上没有访问级别 protected 也没有 private 并且对于所有其他成员访问,无论如何您都必须自己生成必要的代码。如果您无法修改现有类 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() {}
    }
}

关于java - 在 java ASM 中访问私有(private)内部类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35785440/

相关文章:

java - Liferay 没有重定向到登录页面

java - 如何在java中生成列表/数组的随机排列?

java - 无效条目压缩大小

java - ASM 3.3.1 中缺少 ASMifier 类

java - Firebase Analytics 未显示在控制台中

java - 为什么我不能加载附加到引导类加载器搜索的资源?

Java邮件 : Adding properties to a session while reusing Transport

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

java - 有没有办法知道访问字节码时使用 `new` 创建了哪些类?

java - 如何使用 ASM 生成模拟 invokevirtual 的 invokedynamic 调用