Overriding default accessor method across different classloaders breaks polymorphism

点点圈 提交于 2019-12-01 16:58:23

From Java Virtual Machine Specification:

5.3 Creation and Loading
...
At run time, a class or interface is determined not by its name alone, but by a pair: its fully qualified name and its defining class loader. Each such class or interface belongs to a single runtime package. The runtime package of a class or interface is determined by the package name and defining class loader of the class or interface.


5.4.4 Access Control
...
A field or method R is accessible to a class or interface D if and only if any of the following conditions is true:
  • ...
  • R is either protected or package private (that is, neither public nor protected nor private), and is declared by a class in the same runtime package as D.

The Java Language Specification mandates that a class can only override methods that it can access. If the super class method is not accessible, it is shadowed rather than overridden.

Reflection "works" because you ask Outside.class for its run method. If you ask Base.class instead, you'll get the super implementation:

        Method m = Base.class.getDeclaredMethod("run");
        m.setAccessible(true);
        m.invoke(p);

You can verify that the method is deemed inaccessible by doing:

public class Outside extends Base {
    @Override
    public void run() {
        System.out.println("Outside.");
        super.run(); // throws an IllegalAccessError
    }
}

So, why is the method not accessible? I am not totally sure, but I suspect that just like equally named classes loaded by different class loaders result in different runtime classes, equally named packages loaded by different class loaders result in different runtime packages.

Edit: Actually, the reflection API says that it's the same package:

    Base.class.getPackage() == p.getClass().getPackage() // true

I found the (hack) way to load external class in main classloader so this problem is gone.

Read a class as bytes and invoke protected ClassLoader#defineClass method.

code:

URL[] url = { new URL("file:/home/mart/workspace6/test2/bin/") };
URLClassLoader ucl = URLClassLoader.newInstance(url);

InputStream is = ucl.getResourceAsStream("Outside.class");
byte[] bytes = new byte[is.available()];
is.read(bytes);
Method m = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
m.setAccessible(true);
Class<Base> outsideClass = (Class<Base>) m.invoke(Base.class.getClassLoader(), "Outside", bytes, 0, bytes.length);

Base p = outsideClass.newInstance();
System.out.println(p.getClass());
p.run();

outputs ok. outside as expected.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!