0

I am making a program that operates as multiple JAR file dependencies. Basically, the thing loops through the .class files in a JAR file and gets a Class object for each of them. Each JAR has a Plugin.class file that I don't want to be available, but I want all the Classes to be accessible by other JAR dependencies as well as the main program. For example, in one JAR I have the class something.somethingelse.SomeClass, and from a second one (I made sure it is loaded second) I want to be able to import (at execution because it's in a separate JARfile) something.somethingelse.SomeClass and use it. I Have tried this after loading it into a Class object but it gives me ClassNotFound errors. I am using the newest java update and the newest version of eclipse IDE. I have three projects, "main", "aaa", and "aab". I have aaa and aab exported to JARs of which the contents are loaded into Class objects by main. aaa is loaded before aab, and I want aab to be able to access the classes from aaa through import aaa.Class. How can I (from main) make the classes of both jarfiles available to each other?

Here is my load plugin function:

public static void load(File file) throws Exception
    {
        JarFile jarFile = new JarFile(file);
        Enumeration e = jarFile.entries();
            URL[] urls = new URL[] { file.toURI().toURL() };
        ClassLoader cl = new URLClassLoader(urls);
        while (e.hasMoreElements()) {
            JarEntry je = (JarEntry) e.nextElement();
            if(je.isDirectory() || !je.getName().endsWith(".class") || je.getName() == "Plugin.class"){
                continue;
            }
            // -6 because of .class
            String className = je.getName().substring(0,je.getName().length()-6);
            className = className.replace('/', '.');
            Class c = cl.loadClass(className);

        }
            ClassLoader loader = new URLClassLoader(urls);
        Class c = loader.loadClass("Plugin");
        Object cobj = c.newInstance();
        Method[] allMethods = c.getDeclaredMethods();
        Method method = null;
        boolean found = false;
        for (Method m : allMethods) {
        String mname = m.getName();
        if (mname == "startPlugin"){
            method = m;
            found = true;
        }
    }
    if(found)
    {
        method.invoke(cobj);
    }
    else
    {
        //skip class
    }
    } 

And then my first JAR (aaa.jar) declares a class called hlfl.ui.UserInterface. My second JAR's Plugin class is as follows:

import hlfl.ui.*;
public class Plugin {
    //THIS DEPENDENCY EXPORTS TO: aab.jar
    public void startPlugin()
    {
        System.out.println("Plugin Loading Interface Loaded [AAB]");
        UserInterface c = new UserInterface();
    }
}

But when I run it it gives me the following:

java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun. reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sf.htmlguy.hlaunch.PluginLoader.load(PluginLoader.java:58)
    at sf.htmlguy.hlaunch.PluginLoader.loadAll(PluginLoader.java:22)
    at sf.htmlguy.hlaunch.HLaunch.main(HLaunch.java:14)
Caused by: java.lang.NoClassDefFoundError: hlfl/ui/UserInterface
    at Plugin.startPlugin(Plugin.java:7)
    ... 7 more
    Caused by: java.lang.ClassNotFoundException: hlfl.ui.UserInterface
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 8 more

Just in case, the code is on SourceForge (the three projects are in subdirectories, "hlaunch for linux" is the main one.): https://sourceforge.net/p/hlaunch/code

Ethan McTague
  • 2,236
  • 3
  • 21
  • 53
  • 3
    I'm finding your question very hard to understand - especially as it's not clear whether you mean "import" at compile-time (as normal) or whether you mean you want to use it at execution time. A full concrete example would really help. – Jon Skeet Mar 08 '14 at 13:32
  • Okay, so it's odd to call that "importing". You've now provided *some* code, but it's far from a complete example. We've no idea what `hlfl.ui.UserInterface` is or where you'd expect it to come from. – Jon Skeet Mar 08 '14 at 13:45
  • My code is on sourceforge: https://sourceforge.net/p/hlaunch/code – Ethan McTague Mar 08 '14 at 13:46
  • @user3053430 Off-topic but `if (mname == "startPlugin"){` is not correct. This code should look like `if (mname.equals("startPlugin")){`. For more info take a look at [how-do-i-compare-strings-in-java](http://stackoverflow.com/questions/513832/how-do-i-compare-strings-in-java). – Pshemo Mar 08 '14 at 13:46
  • 1
    @user3053430: We don't need to work our way through your full real code. We want a minimal example which is *just* enough to show the problem without doing anything else. That should be short enough to include directly within the question... along with how you're compiling, jar-ing, and running. Please read http://tinyurl.com/so-list – Jon Skeet Mar 08 '14 at 13:48

1 Answers1

2

As far as I can tell your load method is creating a URLClassLoader containing just one JAR file. So you're going to end up with a classloader structure like this

                  main
                 /    \
                /      \
  UCL with aaa.jar    UCL with aab.jar

thus classes in aaa and in aab can both see classes in main, but aaa and aab cannot see each other. If you want each plugin to be able to see the classes of those plugins that were loaded before it, then you need to arrange things so that each plugin you load uses the classloader of the previous plugin as its parent

          main
            |
       UCL with aaa.jar
            |
       UCL with aab.jar

To do this you'd have to cache the loader you create when you load one plugin, and then pass that as a parameter when you create the next plugin's classloader.

private static ClassLoader lastPluginClassLoader = null;

public static void load(File file) throws Exception {
  //...
  ClassLoader loader = null;
  if(lastPluginClassLoader == null) {
    loader = new URLClassLoader(urls);
  } else {
    loader = new URLClassLoader(urls, lastPluginClassLoader);
  }
  lastPluginClassLoader = loader;
  // ...
}

But all this (a) is not thread safe unless synchronized and (b) makes the behaviour critically dependent on the order in which the plugins are loaded. To do things properly you'd need some way to declare which plugins depend on which other plugins, and set up the classloader tree appropriately, etc. etc.

... and if you go too far down that road you've just re-invented OSGi.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183