我有一个包含几个内部类的类。我想生成额外的内部类,使用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
有没有办法生成可以与其他私有内部类交互的内部类?您可以假设这是从&#34;安全区域&#34;私人内部类可以访问的地方。
谢谢
答案 0 :(得分:3)
内部和外部类可以访问其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唯一可以查看此属性的地方。
但除了Reflection报告内部类关系外,顶层类和嵌套类之间无任何功能差异。如上所述,类实际上没有访问级别protected
和private
,并且对于所有其他成员访问,您必须自己生成必要的代码。如果您无法修改现有类Parent
或Parent$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() {}
}
}
答案 1 :(得分:-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();
}