【问题标题】:Package-private method seems to not be overriden包私有方法似乎没有被覆盖
【发布时间】:2014-11-10 08:14:35
【问题描述】:

我使用 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 方法根本没有被调用。但是我可以在GeneratedInheritor的方法列表中看到getValue方法(使用GeneratedInheritor.class::getDeclaredMethods)。

【问题讨论】:

    标签: java classloader java-bytecode-asm overriding


    【解决方案1】:

    为了澄清这里发生的事情(对于非生成类也是如此),请考虑在同一个包中定义和重新定义包私有方法的两个公共类:

    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 正在使用@Override 覆盖Foo 中的方法baz。然而,即使是经验丰富的 Java 开发人员也感到惊讶的是,baz 方法在运行时不一定会被覆盖!这种混淆的根源在于编译时和运行时类的不同:

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

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

      classLoaderA.load("qux.Foo") != classLoaderB.load("qux.Foo");
      

      当(且仅当)两个类加载器不将类加载委托给同一个父级时,可能为真。包也是如此。假设上面的两个类qux.Foo是由不同的类加载器加载的。在这种情况下,它们的包都是qux 也不被认为是相等的。事实上,包也通过它们的名称和它们被检索的类的(隐式)ClassLoader 进行比较。

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

    考虑上面的qux.Barqux.Foo 是由两个不同的类加载器加载的。只要类 qux.Foo 是公开的,这就是合法的。除了定义类,qux.Foo::baz 方法是包私有的,因此对qux.Bar 不可见,它现在位于与qux.Foo 不同的运行时包中。因此,qux.Foo::bazqux.Bar 不可见,也不能被它覆盖。

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

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-28
        • 1970-01-01
        • 1970-01-01
        • 2023-04-09
        • 2010-11-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多