访问java ASM中的私有内部类

Tar*_*Sha 7 java java-bytecode-asm

我有一个包含几个内部类的类.我想生成额外的内部类,使用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 {
  }

}
Run Code Online (Sandbox Code Playgroud)

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

IllegalAccessError: class Parent$OtherChild cannot access its superclass Parent$Child
Run Code Online (Sandbox Code Playgroud)

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

谢谢

Hol*_*ger 3

内部类和外部类可以访问它们的规则private是纯 Java 编程语言构造,JVM\xe2\x80\x99s 访问检查不反映该规则。当 Java\xc2\xa01.1 中引入内部类时,它们的引入方式不需要对 JVM 进行更改。从 JVM\xe2\x80\x99s 的角度来看,嵌套类是普通(顶级)类,带有一些附加的、可忽略的元信息。

\n\n

当声明内部类时private,它\xe2\x80\x99的普通类访问级别是 \xe2\x80\x9cdefault\xe2\x80\x9d 又名package-private。当它\xe2\x80\x99s声明时protected,它将是public在JVM级别。

\n\n

当嵌套类相互访问\xe2\x80\x99sprivate字段或方法时,编译器将生成具有包私有的合成帮助器方法在目标类中

\n\n

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

\n\n

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

\n\n
Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);\n
Run Code Online (Sandbox Code Playgroud)\n\n

\n\n
Method m=ClassLoader.class.getDeclaredMethod(\n    "defineClass", String.class, byte[].class, int.class, int.class);\nm.setAccessible(true);\nClass<?> genClass=(Class<?>)m.invoke(\n    Child.class.getClassLoader(), "Parent$OtherChild", bytes, 0, bytes.length);\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者,您可以声明Childprotected. 因为它\xe2\x80\x99是一个public低级别的类,因此其他类加载器可以访问它。

\n\n

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

\n\n

但是除了反射报告内部类关系之外,顶级类和嵌套类之间无论如何都没有功能差异。如前所述,类实际上不具有访问级别protectedprivate对于所有其他成员访问,您无论如何都必须自己生成必要的代码。Parent如果您可以\xe2\x80\x99t 修改现有类或的代码Parent$Child,则可以\xe2\x80\x99t 访问private这些合成访问器方法不存在的成员\xe2\x80\x99t 已存在的\xe2\x80 \xa6

\n\n
\n\n

从 Java\xc2\xa09 开始,有一种在可访问上下文中定义新类的标准方法,这使得上面显示的具有访问覆盖\xe2\x80\x9d 方法的 \xe2\x80\x9cReflection 对于此用例来说已过时,例如以下作品:

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