包私有方法似乎没有被覆盖

时间:2014-11-10 08:14:35

标签: java classloader java-bytecode-asm method-overriding

我使用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)。

2 个答案:

答案 0 :(得分:5)

为了澄清这里发生了什么(对于非生成的类也是如此),考虑同一个包中的两个公共类,它们都定义并重新定义了一个包私有方法:

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不一定在运行时被覆盖!这种混淆的根源是编译时和运行时类的区别:

  1. 在编译时,任何类qux.Foo都被视为等于任何其他qux.Foo。使用此名称定义两个类(.java文件的命名约定已隐含的内容)是非法的。同样,只有两个包quxqux被认为是相同的。因此,编译器会正确验证qux.Bar是否覆盖baz中的包私有qux.Foo,因为这两个类都在包qux中定义。

  2. 在运行时,名为qux.Foo的两个类可能不再相等。运行时类由包含类名和类ClassLoader的元组标识。如果名称相同但不是ClassLoader,则两个Class实例视为相等。因此:

    classLoaderA.load("qux.Foo") != classLoaderB.load("qux.Foo");
    
    如果(并且仅当)两个类加载器不将类加载委托给同一父代,则

    可能为真。包裹也是如此。假设上面的两个类qux.Foo由不同的类加载器加载。在这种情况下,它们的qux包也不被认为是相等的。事实上,包也会通过它们的名称和它们从中检索的类的(隐式)ClassLoader进行比较。

  3. 但这在实践中意味着什么?

    考虑上述qux.Barqux.Foo由两个不同的类加载器加载。只要班级qux.Foo是公开的,这是合法的。除了定义类之外,方法qux.Foo::baz是包私有的,因此qux.Bar不可见,qux.Foo现在位于与qux.Foo::baz不同的运行时包中。因此,qux.Barnew Bar().baz()不可见,并且无法被其覆盖。

    因此,调用Foo可能会打印Barbaz,具体取决于调用Foo方法的代码的运行时包。代码是否属于Foo的包,调用打印的Bar是否属于Bar的包,调用打印qux。如果它不属于这两个包中的任何一个(甚至可能是第三个类加载器的GeneratedInheritor包),则两个方法都不可见,并且抛出IllegalAccessError

    有了这个,你现在能够理解发生了什么。 DynamicallyCreatedClassesLoader加载了您的getValue。后者不是执行测试代码的类的类加载器。因此,它不属于运行时生成的类的包,并且生成的类的getValue方法对于您的测试代码是不可见的。但是,您想要覆盖的原始{{1}}方法对您的测试代码是可见的并被调用。这样,您遇到的异常就会被抛出。

答案 1 :(得分:0)

为了解决这个问题,我执行了以下步骤。

如果您使用不同的类加载器:

  1. 基类应该是公共的(如果我们使用默认访问修饰符,那么我们的类加载器将无法找到生成的类)
  2. 要成功覆盖getValue方法,它必须至少具有“受保护”访问修饰符。