如何让Java使用我的类加载器进行类解析

如何让Java使用我的类加载器进行类解析,java,classloader,Java,Classloader,下面的测试用例。输出: custom loading of: pkg.TestRun ran. wish this would run in my custom classloader 我希望看到“定制加载:pkg.TestRun”显示在输出的第二行和第三行之间 即使第一个类是从父类加载的,如何让它从自定义类加载程序加载类的依赖关系?注意,在我的实际情况中,我得到了一个类not found异常,因为父类加载器不知道OtherClass的等价物 我知道一个解决方案是让自定义类加载器显式地加载Te

下面的测试用例。输出:

custom loading of: pkg.TestRun
ran.
wish this would run in my custom classloader
我希望看到“定制加载:pkg.TestRun”显示在输出的第二行和第三行之间

即使第一个类是从父类加载的,如何让它从自定义类加载程序加载类的依赖关系?注意,在我的实际情况中,我得到了一个类not found异常,因为父类加载器不知道OtherClass的等价物

我知道一个解决方案是让自定义类加载器显式地加载TestRun。然而,如何加载TestRun已经为父类加载器所知,我不想单独找到它,因为它已经完成,所以我可能很难弄清楚它何时已经在管理中而不需要我做任何事情。我尝试过像super.getResource(返回null)或findClass(已经将父类设置为它的类加载器)这样的操作,但都不起作用

那么,我可以让父级找到类,但自定义加载程序定义它吗?或者,是否有一种方法可以使它始终使用我的自定义加载程序来查找依赖项

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");
    }
}

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

请尝试此修改后的代码:

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);
}

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

比如说:

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)
使用这些字节在自定义类加载器中定义它。

为什么您认为您需要自定义类加载器?当您尝试调试类加载问题时,
-verbose:class
命令行选项是您的朋友。您的意思是要查看“自定义加载其他类”吗?我将使用调试器在主类加载器中查找加载其他类的内容。我需要一个自定义类加载器,因为动态加载创建的类。我已经知道哪个加载程序正在加载另一个类,它是父加载程序。是的,但调试器将向您显示为什么父加载程序正在加载它,即堆栈跟踪,这需要我知道找到TestRun的URL。我想避免这一点,这就是为什么我说:“我知道一个解决方案是让自定义类加载器显式地加载TestRun。但是,父类加载器已经知道了它,我不希望必须单独找到它,因为它已经完成了。”尽管我看到了一个错误(添加加载器)我现在把它修好了。哦,我明白你的意思了。我认为答案是“不”。你必须让你的自定义类加载器来完成所有的工作。据我所知,如果类加载器加载了一个类,那么它将加载它的所有依赖项。查找URL有很多方法,在我的示例中,我只是用了一种非常简单的方法。对于真正的代码,您可以(例如)扫描已知的.class文件,并从中构造自定义类加载器的URL。
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();
}