繁体   English   中英

Package-private方法似乎没有被覆盖

[英]Package-private method seems to not be overriden

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

为了澄清这里发生的事情(对于非生成类也是如此),请考虑同一包中的两个公共类,它们都定义并重新定义了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正在使用@Override Foobaz方法。 但是,即使是经验丰富的Java开发人员也感到惊讶,方法baz不一定在运行时被覆盖! 这种混淆的根源是编译时间和运行时类的差异:

  1. 在编译时,任何类qux.Foo被视为与任何其他qux.Foo相等。 用该名称定义两个类是非法的( .java文件的命名约定已经隐含了这些名称)。 同样,两个软件包quxqux总是仅按它们的名称被认为是相等的。 因此,编译器正确地验证该qux.Bar覆盖包私人bazqux.Foo既是类在包中定义qux

  2. 在运行时,两个名为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::baz不可见qux.Bar ,并且不能被它覆盖。

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

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

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

如果您使用其他类加载器:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM