我使用ASM生成基于类ToOverride
的超类。我想重写其ToOverride::getValue
方法。提到的类如下:
public abstract class ToOverride {
Object getValue(String str, Object arg) throws Exception {
throw new IllegalStateException("PARENT!");
}
Object justMethod() {
return "test";
}
}
在纯Java中,预期的类如下所示:
public class GeneratedInheritor extends ToOverride {
@Override
Object getValue(String str, Object arg) throws Exception {
return str + arg;
}
}
我生成了后一个类,它导致以下字节码。
// class version 49.0 (49)
// access flags 0x21
public class com/example/GeneratedInheritor extends com/example/ToOverride {
// access flags 0x1
public <init>()V
ALOAD 0
INVOKESPECIAL com/example/ToOverride.<init> ()V
RETURN
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x0
getValue(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; throws java/lang/Exception
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
MAXSTACK = 2
MAXLOCALS = 3
}
生成的类使用以下
ClassLoader
加载:class DynamicallyCreatedClassesLoader extends ClassLoader {
public Class defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
我还使用ASM * s
CheckClassAdapter
测试了生成的字节码。没有发生错误。当我调用生成的类的getValue
方法时,它会生成java.lang.IllegalStateException: PARENT!
,这意味着根本没有调用我覆盖的GeneratedInheritor::getValue
方法。但是我可以在getValue
的方法列表上看到GeneratedInheritor
方法(使用GeneratedInheritor.class::getDeclaredMethods
)。 最佳答案
为了弄清这里发生的事情(对于非生成类也是如此),请考虑同一包中的两个公共类,它们都定义并重新定义了package-private方法:
package qux;
public class Foo {
void baz() { System.out.println("Foo"); }
}
package qux;
public class Bar extends Foo {
@Override
void baz() { System.out.println("Bar"); }
}
这样可以很好地进行编译,并且Java编译器会通过使用
Bar
来确认baz
正在覆盖Foo
中的@Override
方法。但是,即使经验丰富的Java开发人员也感到惊讶,方法baz
不一定在运行时被覆盖!这种混乱的根源是编译时间和运行时类的差异:在编译时,任何类
qux.Foo
被认为与任何其他qux.Foo
相等。用这个名称定义两个类是非法的(.java
文件的命名约定已经隐含了这些名称)。同样,两个软件包qux
和qux
总是仅凭其名称被视为相等。因此,编译器正确验证qux.Bar
是否覆盖了baz
中的程序包专用qux.Foo
,因为这两个类均在程序包qux
中定义。在运行时,两个名为
qux.Foo
的类可能不再相等。运行时类由元组标识,该元组由类名称和类的ClassLoader
组成。如果名称相等但ClassLoader
不相等,则两个Class
实例不视为相等。从而:classLoaderA.load("qux.Foo") != classLoaderB.load("qux.Foo");
当(且仅当)两个类加载器未将类加载委托给同一父级时,可能为true。包也是如此。假定上面的两个类
qux.Foo
由不同的类加载器加载。在这种情况下,它们都为qux
的程序包也不被视为相等。实际上,还按名称和从中检索到的类的(隐式)ClassLoader
对软件包进行比较。但是,这实际上意味着什么?
考虑上面的
qux.Bar
和qux.Foo
由两个不同的类加载器加载。只要qux.Foo
类是公共的,这是合法的。但是,除了定义类之外,方法qux.Foo::baz
是程序包专用的,因此对于qux.Bar
而言不可见,该方法现在与qux.Foo
位于不同的运行时程序包中。因此,qux.Foo::baz
对qux.Bar
不可见,并且不能被它覆盖。因此,根据调用
new Bar().baz()
方法的代码的运行时包,调用Foo
可能会显示Bar
或baz
。代码是否属于Foo
的程序包,调用打印的Foo
,它属于Bar
的程序包,调用打印Bar
。如果它不属于这两个软件包中的任何一个(甚至可能不是第三类加载器的qux
软件包),则这两个方法都不可见,并且会抛出IllegalAccessError
。这样,您现在就可以了解发生了什么。您的
GeneratedInheritor
由DynamicallyCreatedClassesLoader
加载。后者不是正在执行测试代码的类的类加载器。因此,它不属于您的运行时生成类的包,并且生成类的getValue
方法对您的测试代码不可见。但是,您打算覆盖的原始getValue
方法对您的测试代码可见并被调用。这样,您遇到的异常就会被抛出。