简体   繁体   中英

Custom ClassLoader with resource loading

I'm writing a plugin loader -- it loads jars that are not on the classpath. I wrote a simple custom ClassLoader that takes a JarFile in its constructor and looks in the JarFile for the named class. This loader simply overrides the findClass() method of ClassLoader, and works fine.

Then I determined that I also needed to be able to get resources from the plugin jar. So I overrode 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!

In other words, if I have plugin.jar that contains MyPlugin and MyPluginComponent:

  • 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. However I cannot find resources that are bundled in the 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.

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. 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. 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.

public class PluginLoader extends URLClassLoader {

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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