【问题标题】:How to make Java use my classloader for class resolution如何让 Java 使用我的类加载器进行类解析
【发布时间】:2012-06-29 22:56:53
【问题描述】:

下面的测试用例。输出:

custom loading of: pkg.TestRun
ran.
wish this would run in my custom classloader

我希望看到“自定义加载:pkg.TestRun”出现在输出的第二行和第三行之间。

如何让它从我的自定义类加载器加载类的依赖项,即使第一个类是从父类加载的?请注意,在我的真实案例中,我得到一个类未找到异常,因为父类加载器不知道 OtherClass 的等价物。

我知道一种解决方案是让自定义类加载器显式加载 TestRun。但是,如何加载 TestRun 已为父类加载器所知,我不想单独管理查找它,因为它已经完成,而且我可能很难以某种方式弄清楚它何时已经在没有我做的情况下进行管理任何事物。而且我尝试过类似 super.getResource(返回 null)或 findClass(已经将 parent 设置为它的类加载器)之类的操作,但都没有成功。

那么,我可以让父类找到类,但自定义加载器定义它吗?或者,有没有办法让它始终使用我的自定义加载器来查找依赖项?

package pkg;

public class TestCL {

    static class MyCL extends ClassLoader {
        MyCL(ClassLoader parent) {
            super(parent);
        }

        @Override
        public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
            System.out.println("custom loading of: " + name);
            return getParent().loadClass(name);
        }
    }

    public static void main(String[] args) throws Exception {
        MyCL cl = new MyCL(Thread.currentThread().getContextClassLoader());
        Thread.currentThread().setContextClassLoader(cl);
        cl.loadClass("pkg.TestRun").getMethod("run", new Class[] {}).invoke(null);
    }
}

class TestRun {
    public static void run() {
        System.out.println("ran.");
        OtherClass.runAlso();
    }
}

class OtherClass {
    public static void runAlso() {
        System.out.println("wish this would run in my custom classloader");
    }
}

【问题讨论】:

  • 为什么你认为你需要一个自定义类加载器?
  • -verbose:class 命令行选项是您尝试调试类加载问题时的好帮手。
  • 您的意思是要查看“自定义加载 OtherClass”吗?我会使用调试器在主类加载器中找到加载 OtherClass 的内容。
  • 我需要一个自定义类加载器,因为动态加载动态创建的类。我已经知道哪个加载器在加载 OtherClass,它是父加载器。
  • 是的,但是调试器会告诉你为什么它被父加载器加载,即堆栈跟踪

标签: java classloader


【解决方案1】:

问题是您没有使用自定义类加载器加载任何内容
它要求它的父类加载类,因此所有内容都由主类加载器加载。

试试这个修改后的代码:

package stackoverflow.june2012.classloader;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

public class TestCL {

static class MyCL extends URLClassLoader {
    MyCL(URL[] urls) {
        // NOTE: No parent classloader!
        super(urls, null);
    }

    @Override
    public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        System.out.println("custom loading of: " + name);
        return super.loadClass(name, resolve);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass: " + name);
        System.out.println("NOTE: Only called if this classloader does NOT have a parent");
        return super.findClass(name);
    }
}

public static void main(String[] args) throws Exception {
    URL url = new File("./bin").toURI().toURL();
    System.out.println("url to search for classes: " + url);
    MyCL cl = new MyCL(new URL[] {url});
    Thread.currentThread().setContextClassLoader(cl);
    Class loadClass = cl.loadClass("stackoverflow.june2012.classloader.TestRun");
    System.out.println("Loaded TestRun using classloader: " + loadClass.getClassLoader());
    loadClass.getMethod("run", new Class[] {}).invoke(null);
}

}

我不得不将你的其他 2 个类移动到一个单独的文件中,否则它无法访问,因为它在不同的类加载器中是包私有的:

package stackoverflow.june2012.classloader;

public class TestRun {
    public static void run() {
    System.out.println("ran.");
    OtherClass.runAlso();
    }
}

class OtherClass {
    public static void runAlso() {
        System.out.println("wish this would run in my custom classloader");
    }
}

【讨论】:

  • 看来这需要我知道可以找到 TestRun 的 URL。我想避免这就是为什么我说:“我知道一种解决方案是让自定义类 loader 显式加载 TestRun。但是,父类加载器已经知道它,我不想因为它已经完成了,所以必须单独找到它。”虽然我在其中看到了一个错误(添加了 loader),但我现在已经修复了。
  • 哦,我明白你的意思了。我认为答案是“不”。你必须让你的自定义类加载器来完成所有的工作。据我所知,如果类加载器已经加载了一个类,那么它将加载它的所有依赖项。可以通过多种方式查找 URL,在我的示例中,我只是以一种非常简单的方式进行了查找。对于真实代码,您可以(例如)扫描已知的 .class 文件并从中构造自定义类加载器的 URL。
【解决方案2】:

一种解决方案是使用父类加载器(或已经知道它在哪里的)来读取类的字节,然后在自定义类加载器中定义它。

所以,是这样的:

public static byte[] loadBytesForClass(ClassLoader loader, String fqn) throws IOException {
    InputStream input = loader.getResourceAsStream(fqn.replace(".", "/") + ".class");
    if (input == null) {
        System.out.println("Could not load bytes for class: " + fqn);
    }
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    StringUtil.copy(input, output);
    return output.toByteArray();
}

然后您可以使用这些字节调用defineClass(name, bytes, 0, bytes.length) 在自定义类加载器中定义它。

【讨论】:

    猜你喜欢
    • 2016-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多