【问题标题】:Access private inner classes in java ASM访问 java ASM 中的私有内部类
【发布时间】:2016-03-03 23:53:07
【问题描述】:

我有一个包含几个内部类的类。我想使用 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

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

谢谢

【问题讨论】:

    标签: java java-bytecode-asm


    【解决方案1】:

    内部类和外部类可以访问其private 成员的规则是一种纯Java 编程语言结构,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 的内部类,它可能会被验证器拒绝,因为元信息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() {}
        }
    }
    

    【讨论】:

    • 不敢相信我忘了说你两年前给出的解决方案确实对我有用!谢谢!
    • @TareqSha 迟到总比不到好 ;-) 我借此机会更新我的答案。
    【解决方案2】:

    我把私有内部类改成公共内部类,运行你的代码没问题。

    @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();
        }
    

    【讨论】:

      猜你喜欢
      • 2011-02-17
      • 1970-01-01
      • 1970-01-01
      • 2018-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-22
      相关资源
      最近更新 更多