我使用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文件的命名约定已经隐含了这些名称)。同样,两个软件包quxqux总是仅凭其名称被视为相等。因此,编译器正确验证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.Barqux.Foo由两个不同的类加载器加载。只要qux.Foo类是公共的,这是合法的。但是,除了定义类之外,方法qux.Foo::baz是程序包专用的,因此对于qux.Bar而言不可见,该方法现在与qux.Foo位于不同的运行时程序包中。因此,qux.Foo::bazqux.Bar不可见,并且不能被它覆盖。

因此,根据调用new Bar().baz()方法的代码的运行时包,调用Foo可能会显示Barbaz。代码是否属于Foo的程序包,调用打印的Foo,它属于Bar的程序包,调用打印Bar。如果它不属于这两个软件包中的任何一个(甚至可能不是第三类加载器的qux软件包),则这两个方法都不可见,并且会抛出IllegalAccessError

这样,您现在就可以了解发生了什么。您的GeneratedInheritorDynamicallyCreatedClassesLoader加载。后者不是正在执行测试代码的类的类加载器。因此,它不属于您的运行时生成类的包,并且生成类的getValue方法对您的测试代码不可见。但是,您打算覆盖的原始getValue方法对您的测试代码可见并被调用。这样,您遇到的异常就会被抛出。

09-25 20:44