在 java ASM 中访问私有内部 类
Access private inner classes in java ASM
我有一个 class,其中包含几个内部 class。我想使用 ASM 库生成与 compile-time 私有内部 classes 交互的额外内部 classes。我的代码如下所示:
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 {
}
}
如图所示,交互的一个简单示例是继承 - 我正在尝试生成扩展私有内部 class OtherChild =] Child。我在 class 加载器验证 class 定义时收到此错误消息:
IllegalAccessError: class Parent$OtherChild cannot access its superclass Parent$Child
有没有办法生成内部 classes,可以 与其他私有内部 classes 交互?您可以假设这是从 "safe zone" 执行的,其中私有内部 class 是可访问的。
谢谢
内部和外部 classes 可以访问其 private
成员的规则是一个纯粹的 Java 编程语言结构,它不会被 JVM 的访问检查反映出来。在 Java 1.1 中引入内部 classes 时,它们的引入方式不需要更改 JVM。从 JVM 的角度来看,嵌套的 classes 是普通的(顶级)classes,带有一些额外的、可忽略的元信息。
当一个内部 class 声明 private
时,它的普通 class 访问级别是“默认”,也就是 package-private。当声明为 protected
时,它将在 JVM 级别上为 public
。
当嵌套的 classes 访问彼此的 private
字段或方法时,编译器将在目标中生成具有 package-private 访问权限的合成辅助方法class,提供所需的访问权限。
所以从 JVM 的角度来看,您正在尝试子 class 一个 package-private class 而名称中的美元只是一个普通的名字字符。生成的 class 具有匹配的限定名称,但您试图在 不同的 class 加载程序 中定义它,因此 JVM 认为这些包在运行时不相同, 尽管他们的名字相同。
如果在同一个 class 加载程序中定义 class,则可以验证包级别访问是否有效。换行
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
class,因此其他 class 加载器可以访问它。
请注意,在这两种情况下,您都没有创建新的内部 class,而只是一个名为 Parent$OtherChild
的 class 扩展内部 class。唯一的区别是关于外-内 class 关系的元信息,但是如果您将该属性添加到生成的 class 中并声称它是 Parent
的内部 class,它可能会被验证者拒绝,因为 Parent
的元信息没有提到内部 class OtherChild
的存在。这是 JVM 可能查看此属性的唯一位置。
但是除了反射报告内部 class 关系之外,顶级 classes 和嵌套 classes 之间在功能上没有任何区别。如前所述,classes 实际上没有 protected
或 private
的访问级别,对于所有其他成员访问,无论如何您都必须自己生成必要的代码。如果您无法修改现有 classes Parent
或 Parent$Child
的代码,则无法访问这些合成访问器方法无法访问的 private
成员的代码'已经存在…
从 Java 9 开始,有一种标准方法可以在可访问的上下文中定义新的 class,这使得上面显示的“使用访问覆盖的反射”方法对于此用例已过时,例如以下作品:
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() {}
}
}
我将私有内部 class 更改为 public 内部 class,运行 您的代码没问题。
@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();
}
我有一个 class,其中包含几个内部 class。我想使用 ASM 库生成与 compile-time 私有内部 classes 交互的额外内部 classes。我的代码如下所示:
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 {
}
}
如图所示,交互的一个简单示例是继承 - 我正在尝试生成扩展私有内部 class OtherChild =] Child。我在 class 加载器验证 class 定义时收到此错误消息:
IllegalAccessError: class Parent$OtherChild cannot access its superclass Parent$Child
有没有办法生成内部 classes,可以 与其他私有内部 classes 交互?您可以假设这是从 "safe zone" 执行的,其中私有内部 class 是可访问的。
谢谢
内部和外部 classes 可以访问其 private
成员的规则是一个纯粹的 Java 编程语言结构,它不会被 JVM 的访问检查反映出来。在 Java 1.1 中引入内部 classes 时,它们的引入方式不需要更改 JVM。从 JVM 的角度来看,嵌套的 classes 是普通的(顶级)classes,带有一些额外的、可忽略的元信息。
当一个内部 class 声明 private
时,它的普通 class 访问级别是“默认”,也就是 package-private。当声明为 protected
时,它将在 JVM 级别上为 public
。
当嵌套的 classes 访问彼此的 private
字段或方法时,编译器将在目标中生成具有 package-private 访问权限的合成辅助方法class,提供所需的访问权限。
所以从 JVM 的角度来看,您正在尝试子 class 一个 package-private class 而名称中的美元只是一个普通的名字字符。生成的 class 具有匹配的限定名称,但您试图在 不同的 class 加载程序 中定义它,因此 JVM 认为这些包在运行时不相同, 尽管他们的名字相同。
如果在同一个 class 加载程序中定义 class,则可以验证包级别访问是否有效。换行
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
class,因此其他 class 加载器可以访问它。
请注意,在这两种情况下,您都没有创建新的内部 class,而只是一个名为 Parent$OtherChild
的 class 扩展内部 class。唯一的区别是关于外-内 class 关系的元信息,但是如果您将该属性添加到生成的 class 中并声称它是 Parent
的内部 class,它可能会被验证者拒绝,因为 Parent
的元信息没有提到内部 class OtherChild
的存在。这是 JVM 可能查看此属性的唯一位置。
但是除了反射报告内部 class 关系之外,顶级 classes 和嵌套 classes 之间在功能上没有任何区别。如前所述,classes 实际上没有 protected
或 private
的访问级别,对于所有其他成员访问,无论如何您都必须自己生成必要的代码。如果您无法修改现有 classes Parent
或 Parent$Child
的代码,则无法访问这些合成访问器方法无法访问的 private
成员的代码'已经存在…
从 Java 9 开始,有一种标准方法可以在可访问的上下文中定义新的 class,这使得上面显示的“使用访问覆盖的反射”方法对于此用例已过时,例如以下作品:
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() {}
}
}
我将私有内部 class 更改为 public 内部 class,运行 您的代码没问题。
@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();
}