Search code examples
javajarclassloaderdynamically-generateddynamic-class-loaders

Dynamically load top-level class in a jar outside of classpath


UPDATE - All of the files in my project now declare the same package. However, the jar I'm loading has to be outside of the classpath. I've tried child.loadClass(className) and Class.forName(className, false, child);, but neither of them seem to be able to find it.

I'm writing a program that wants to take plugins or modules in the form of jars and use them as if they were embedded in the classpath.

I've seen a lot of similar questions on here and other sites, so I apologize for the duplication. But, so far, every answer I've seen hasn't worked. Am I doing something wrong?

I have a folder full of jars. I know that each jar has one top-level class that extends my Module class. But, I don't know what those classes are called.

First, I find all the files in the directory. That works fine. Then, for each file, I try to get an instance of its top-level class using the following code:

(First it opens the jar as a zip to get the names of all the classes, then looks at the name to tell if it's a top-level class, then, if so, is supposed to load, instantiate and return it.)

private static Module jarLoader(String jar){
    try{
        ZipInputStream zip = new ZipInputStream(new FileInputStream(ModulesDir+"/"+jar));
        URL[] myJarFile = new URL[]{new URL("jar","","file:"+ModulesDir)};
        URLClassLoader child = new URLClassLoader (myJarFile , Main.class.getClassLoader());
        for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) {
            if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
                // This ZipEntry represents a class. Now, what class does it represent?
                String className = entry.getName().replace('/', '.'); // including ".class"
                className = className.substring(0, className.length() - ".class".length());


                String[] classNameParts = className.replace('$', ':').split(":");//I don't know why i have to do this, but it won't split on "$"
                if(classNameParts.length == 1){
                    System.out.println("trying to load "+className);
                    Class classToLoad = Class.forName(className, false, child);
                    System.out.println("loaded class "+classToLoad.getName());
                    if(classToLoad.getSuperclass() == Module.class){
                        Module instance = (Module) classToLoad.newInstance();
                        return instance;
                    }
                }
            }
        }
        zip.close();
    }catch (Exception e){
        e.printStackTrace();
    }
    return null;
}

It gets as far as "trying to load ..." and it shows the name of the class as I would expect it to, and then throws a java.lang.ClassNotFoundException.

Using the same name to load the class worked fine when i put it in a .java file in the source dir, but it wont get it from a jar.

Am I doing something wrong? I suspect the class name may need some kind of prefix or suffix but I cant figure out what.

Thanks for you help!


Solution

  • Everything that was suggested didn't seem to work because the files i needed to load were outside my classpath.

    This answer works though