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

1.6k views Asked by At

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!

2

There are 2 answers

0
QuinnF On BEST ANSWER

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

This answer works though

0
chengpohi On

throw ClassNotFoundException is that your load jar file not be added in class path, and the below code not successfully load your jar file.

URL[] myJarFile = new URL[]{new URL("jar","","file:"+ModulesDir)};
        URLClassLoader child = new URLClassLoader (myJarFile , Main.class.getClassLoader());

it should be like:

   URL[] myJarFile = new URL[]{new URL("jar","",jarFilePath)};

   URLClassLoader child = new URLClassLoader (myJarFile, this.getClass().getClassLoader());

and when javac compile class, the inner class will be expressed in the full path and split by $, such as com/hello/world/TT$InnerClass.class.

The full sample:

 private void jarLoader(){
        try{
            String ModulesDir = "./test/test.jar";
            ZipInputStream zip = new ZipInputStream(new FileInputStream(ModulesDir));

            URL[] myJarFile = new URL[]{new URL("jar","",ModulesDir)};

            URLClassLoader child = new URLClassLoader (myJarFile , this.getClass().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, true, child);
                        System.out.println("loaded class "+classToLoad.getName());
                    }
                }
            }
            zip.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }