具有资源加载的自定义 ClassLoader

Custom ClassLoader with resource loading

我正在编写一个插件加载程序 -- 它加载不在 class 路径上的 jar。我编写了一个简单的自定义 ClassLoader,它在其构造函数中采用 JarFile,并在 JarFile 中查找名为 class 的文件。这个加载器简单地覆盖了 ClassLoader 的 findClass() 方法,并且工作正常。

然后我确定我还需要能够从插件jar 中获取资源。所以我覆盖了findResource()。这导致基本插件 class 无法在 jar 中找到其他 classes 的意外结果:我得到 NoClassDefFoundErrors!

换句话说,如果我有包含 MyPlugin 和 MyPluginComponent 的 plugin.jar:

这表明 findResource() 的实现以某种方式无法找到 class 文件——但它甚至没有被调用(根据我的日志记录),所以我不明白这是怎么回事案件。这种互动是如何产生的,我该如何解决?

我试图编写一个独立的小示例,但 运行 难以手动生成不会产生 "incompatible magic number" 错误的 jar 文件。希望无论我做错了什么,仅从 class 加载程序就可以看出。对于给您带来的不便,我们深表歉意,感谢您的宝贵时间。

import com.google.common.io.CharStreams;

import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;

import java.lang.ClassLoader;

import java.net.URL;

import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Custom class loader for loading plugin classes. Adapted from
 * http://kalanir.blogspot.com/2010/01/how-to-write-custom-class-loader-to.html
 */
public static class PluginLoader extends ClassLoader {
    private JarFile jarFile_;
    public PluginLoader(JarFile jarFile) {
        super(Thread.currentThread().getContextClassLoader());
        jarFile_ = jarFile;
    }

    @Override
    public Class findClass(String className) {
        try {
            // Replace "." with "/" for seeking through the jar.
            String classPath = className.replace(".", "/") + ".class";
            System.out.println("Searching for " + className + " under " + classPath);
            JarEntry entry = jarFile_.getJarEntry(classPath);
            if (entry == null) {
                return null;
            }
            InputStream stream = jarFile_.getInputStream(entry);
            String contents = CharStreams.toString(
                    new InputStreamReader(stream));
            stream.close();
            byte[] bytes = contents.getBytes();
            Class result = defineClass(className, bytes, 0, bytes.length);
            return result;
        }
        catch (IOException e) {
            System.out.println(e + "Unable to load jar file " + jarFile_.getName());
        }
        catch (ClassFormatError e) {
            System.out.println(e + "Unable to read class data for class " + className + " from jar " + jarFile_.getName());
        }
        return null;
    }

    @Override
    protected URL findResource(String name) {
        System.out.println("Asked to find resource at " + name);
        try {
            String base = new File(jarFile_.getName()).toURI().toURL().toString();
            URL result = new URL(String.format("jar:%s!/%s", base, name));
            System.out.println("Result is " + result);
            return result;
        }
        catch (IOException e) {
            System.out.println(e + "Unable to construct URL to find " + name);
            return null;
        }
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        System.out.println("Getting resource at "  +name);
        JarEntry entry = jarFile_.getJarEntry(name);
        if (entry == null) {
            System.out.println("Couldn't find resource " + name);
            return null;
        }
        try {
            return jarFile_.getInputStream(entry);
        }
        catch (IOException e) {
            System.out.println(e + "Unable to load resource " + name + " from jar file " + jarFile_.getName());
            return null;
        }
    }
}

如果您的要求只是阅读额外的 JAR 文件,扩展 URLClassLoader 可能是更好的选择。

public class PluginLoader extends URLClassLoader {

    public PluginLoader(String jar) throws MalformedURLException {
        super(new URL[] { new File(jar).toURI().toURL() });
    }

}