简体   繁体   English

自定义ClassLoader与资源加载

[英]Custom ClassLoader with resource loading

I'm writing a plugin loader -- it loads jars that are not on the classpath. 我正在写一个插件加载器-它加载不在类路径上的jar。 I wrote a simple custom ClassLoader that takes a JarFile in its constructor and looks in the JarFile for the named class. 我编写了一个简单的自定义ClassLoader,该类在其构造函数中使用JarFile并在JarFile中查找命名类。 This loader simply overrides the findClass() method of ClassLoader, and works fine. 该加载器仅重写ClassLoader的findClass()方法,并且可以正常工作。

Then I determined that I also needed to be able to get resources from the plugin jar. 然后,我确定我还需要能够从插件jar中获取资源。 So I overrode findResource() . 所以我覆盖了findResource() This had the unexpected result of causing the base plugin class to not be able to find other classes in the jar: I get NoClassDefFoundErrors! 这产生了导致基础插件类无法在jar中找到其他类的意外结果:我收到了NoClassDefFoundErrors!

In other words, if I have plugin.jar that contains MyPlugin and MyPluginComponent: 换句话说,如果我有包含MyPlugin和MyPluginComponent的plugin.jar:

  • if I do not override findResource() , then I can load the jar, create an instance of MyPlugin, and that in turn can create a MyPluginComponent. 如果我不重写findResource() ,那么我可以加载jar,创建MyPlugin的实例,然后可以创建MyPluginComponent。 However I cannot find resources that are bundled in the jar. 但是,我找不到打包在jar中的资源。
  • if I do override findResource() , then I can load the jar, create an instance of MyPlugin, but if MyPlugin attempts to create a MyPluginComponent, I get a NoClassDefFoundError. 如果我确实重写findResource() ,则可以加载jar,创建MyPlugin的实例, 但是如果MyPlugin尝试创建MyPluginComponent,则会收到NoClassDefFoundError。

This suggests that somehow the implementation of findResource() is unable to find class files -- but it's not even getting called (per my logging), so I don't see how that could be the case. 这表明findResource()的实现某种程度上无法找到类文件-但是(根据我的日志记录)它甚至没有被调用,所以我不知道这种情况如何。 How does this interaction work out, and how do I fix it? 如何进行这种互动,以及如何解决?

I tried to write a small self-contained example, and ran into difficulty manually generating a jar file that wouldn't produce "incompatible magic number" errors. 我试图写一个小的独立示例,但遇到了手动生成不会产生“不兼容的幻数”错误的jar文件的麻烦。 Hopefully whatever I'm doing wrong will be evident from the class loader alone. 希望我做错了什么,仅从类加载器中就可以看出来。 Sorry for the inconvenience, and thank you for your time. 不便之处,敬请原谅。感谢您的宝贵时间。

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

If your requirement is to just read extra JAR files extending URLClassLoader might be a better option. 如果您的需求是仅读取额外的JAR文件,则扩展URLClassLoader可能是一个更好的选择。

public class PluginLoader extends URLClassLoader {

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

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM