2

我正在尝试以下 wrt Refleciton,让我知道这是否可能

public class A {
    void start(){
       execute();
     }

}

public class B extends A {
    void execute()
    {
       doWork();
    }
    public abstract void doWork();
}

我将上述类打包在一个 jar 中,并让它在 JVM 上运行。现在我试图在运行时创建另一个类,在运行时编译它并尝试使用反射来调用 B 类的执行函数()。

public class C extends B {
     @Override
     public void doWork()
     {
       //Implementation
     }
}

反射代码:

Classloader = 应用程序 jar 的 url 和 C.class 的 url,在运行时编译。父加载器 - Thread.currentThread().getContextClassLoader() 我还将当前线程的上下文类加载器设置为上面创建的类加载器。

Class<? extends B> cls = (Class<? extends B>) Class.forName("className", true, clsLoader);

Object obj = cls.newInstance();
Method method = obj.getClass().getSuperclass().getMethod("start", new Class[0]);
method.invoke(obj, new Object[0]);   

我能够获得该方法并且调用也被调用。但是,当调用 B 类的执行时,它试图调用 doWork(),然后我遇到了 AbstractMethodError。在查看异常时,我发现异常发生在不正确的类加载器/jar 中。但我不确定如何解决我的情况。

有人可以帮忙吗?

4

1 回答 1

0

首先,让我们澄清一下关于 a 的上下文类加载器的神话Thread:除非代码通过(并使用它)明确地请求该加载器Thread.getContextClassLoader(),否则它与类加载根本无关。

每个类都有一个启动类加载器,它定义了该类,并且对该类中其他类的引用始终通过该启动类加载器解析。这甚至适用于反射加载 via Class.forName(String)(没有明确的ClassLoader);它将使用调用者的初始化类加载器。

如果您想C通过需要访问的自定义类加载器加载B,因为C子类化它,确定父加载器以创建新加载器的最佳方法是B.class.getClassLoader(). 但是,在您的简单设置中,它与返回的加载器相同,ClassLoader.getSystemClassLoader()也是返回的默认类加载器Thread.getContextClassLoader(),这就是它起作用的原因。

您知道它有效,因为您可以C成功加载。虽然其他引用可能会被延迟解析,但直接超类必须立即解析,因此在's 加载器B的范围内。C

假设没有声明execute()inAabstract修饰符 onclass B只是发布问题时的疏忽,那么调用该方法不起作用的原因要简单得多:该方法abstract void doWork();不是public.

由于BC由不同的类加载器加载,因此它们被认为驻留在不同的包中,而不管它们的限定名称如何。因此, in 中的方法doWork()不会C覆盖 in 中的doWork()方法B。这在编译时没有被检测到,因为在编译时没有类加载器层次结构。因此编译器会根据它们的限定名称(或显式包声明)考虑B并驻留在同一个包中。C因此,编译器假定C.doWork()实现B.doWork()并且C可以声明为非abstract.

如果您doWork()Bas中声明该方法public,它应该可以工作。它应该工作得更简单:

try(URLClassLoader l = new URLClassLoader(new URL[]{/* url pointing to C */})) {
    l.loadClass("C").asSubclass(B.class).newInstance().execute();
}
于 2016-05-04T09:44:38.303 回答