gwc
gwc

Reputation: 1293

Java ClassLoader different under Eclipse than when outside of Eclipse

I have written a simple Plugin system so that I can include extensions from external sources. The Plugin Manager loads plugins from predetermined application and user plugins directories. I use a custom URLClassLoader so that I can sandbox all Plugins. Here's the general approach:

  1. Locate the Plugin's JAR file in one of the predetermined directories.
  2. If the Plugin has already been loaded, return the instance of the Plugin that has already been created.
  3. If an instance of the custom URLClassLoader has not been created for the directory containing the Plugin, create one.
  4. Loop through each class in the Plugin's JAR file looking for Classes that implement the PluginInterface by loading the Class using Class.forName( pluginName, false, PluginClassLoader ) and then testing to see if the PluginInterface isAssignableFrom the loaded Class.
  5. If the Class implements the PluginInterface then a new instance of the Class is created, initialized, and saved for later use.

All of this works great when I run it from within the Eclipse IDE. But, when I run it outside of the IDE, the check to see if the Plugin implements the PluginInterface fails. I believe this is because under Eclipse, both the Plugin and the Interface have a related (parent or child) ClassLoader (namely sun.misc.Launcher$AppClassLoader@73d16e93) and outside of Eclipse the PluginInterface has an unrelated ClassLoader (namely java.net.URLClassLoader@14ae5a5).

Here's the code:

The custom ClassLoader:

public class PROD_PluginClassLoader extends URLClassLoader {

    protected PROD_PluginClassLoader( URL pluginFileUrl ) {
        super( new URL[] { pluginFileUrl } );
    }

    protected PROD_PluginClassLoader( String pluginFilePath ) throws MalformedURLException {
        super( new URL[] { new File( pluginFilePath ).toURI().toURL() } );
    }

    protected PROD_PluginClassLoader( URL[] pluginFileUrls ) {
        super( pluginFileUrls );
    }

}

The PluginLoader:

    private static List< String > loadedPlugins = new ArrayList<>();
    private static List< PROD_PluginInterface > plugins = new ArrayList<>();
    private static Map< String, PROD_PluginClassLoader > pluginClassLoaders = new HashMap<>();

    protected static void getPluginInstance( String pluginName, String pluginFilePath ) {
        try {
            PROD_Utilities.printDebug( "Loading plugin name(" + pluginName + ") from(" + pluginFilePath + ")" );
            if ( !pluginClassLoaders.containsKey( pluginFilePath ) ) pluginClassLoaders.put( pluginFilePath, new PROD_PluginClassLoader( pluginFilePath ) );
            PROD_PluginClassLoader pLoader = pluginClassLoaders.get( pluginFilePath );
            boolean pluginLoaded = false;
            for ( String n : PROD_Utilities.getClassNamesFromJarFile( pluginFilePath ) ) {
                Class<?> pClass = Class.forName( n, false, pLoader );
                String interfaces = "";
                for ( Class<?> c : pClass.getInterfaces() ) interfaces += "," + c.getName();
                if ( !interfaces.isEmpty() ) interfaces = interfaces.substring( 1 );
                PROD_Utilities.printDebug( String.format( "Plugin name(%s) object class(%s) super(%s) interfaces(%s) isPlugin(%b)", pluginName, pClass.getName(), pClass.getSuperclass().getName(), interfaces, PROD_PluginInterface.class.isAssignableFrom( pClass ) ) );
if ( pClass.getInterfaces().length > 0 )
PROD_Utilities.printDebug(
String.format(
     "pClass loader(%s) parent(%s) pClass interface loader(%s) parent(%s) PROD_PluginInterface loader(%s) parent(%s)"
    ,pClass.getClassLoader()
    ,pClass.getClassLoader().getParent()
    ,pClass.getInterfaces()[0].getClassLoader()
    ,pClass.getInterfaces()[0].getClassLoader().getParent()
    ,PROD_PluginInterface.class.getClassLoader()
    ,PROD_PluginInterface.class.getClassLoader().getParent()
));
                if ( PROD_PluginInterface.class.isAssignableFrom( pClass ) ) {
                    Class<? extends PROD_Plugin> newClass = pClass.asSubclass( PROD_Plugin.class );
                    Constructor<?> constructor = newClass.getConstructor();
                    setPluginSandbox();
                    plugins.add( ( PROD_PluginInterface ) constructor.newInstance() );
                    plugins.get( plugins.size()-1 ).pluginInitialization();
                    unsetPluginSandbox();
                    pluginLoaded = true;
                }
            }
            if ( pluginLoaded ) loadedPlugins.add( pluginName.toLowerCase() );
            else PROD_Utilities.printError( "Plugin (" + pluginName + ") is not a valid PROD plugin." );
        } catch ( InstantiationException    | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException | ClassNotFoundException | NoSuchMethodException 
                | SecurityException         | MalformedURLException     e ) {
            PROD_Utilities.printError( "Could not load plugin (" + pluginName + ").", e.getMesprod() );
        }
    }

The debug information when running under Eclipse:

 Debug: PROD_PluginManager.getPluginInstance().line(138): Loading plugin name(proddb) from(C:\Users\userid\eclipse-workspace\prod\plugins\proddb.jar)
 Debug: PROD_PluginManager.getPluginInstance().line(147): Plugin name(proddb) object class(com.company.prod.proddb.PRODDB) super(com.company.prod.PROD_Plugin) interfaces(com.company.prod.PROD_PluginInterface) isPlugin(true)
 Debug: PROD_PluginManager.getPluginInstance().line(149): pClass loader(com.company.prod.PROD_PluginClassLoader@5ec0a365) parent(sun.misc.Launcher$AppClassLoader@73d16e93) pClass interface loader(sun.misc.Launcher$AppClassLoader@73d16e93) parent(sun.misc.Launcher$ExtClassLoader@55f96302) PROD_PluginInterface loader(sun.misc.Launcher$AppClassLoader@73d16e93) parent(sun.misc.Launcher$ExtClassLoader@55f96302)

The debug information when running outside of Eclipse:

 Debug: PROD_PluginManager.getPluginInstance().line(138): Loading plugin name(proddb) from(C:\Users\userid\eclipse-workspace\prod\plugins\proddb.jar)
 Debug: PROD_PluginManager.getPluginInstance().line(147): Plugin name(proddb) object class(com.company.prod.proddb.PRODDB) super(com.company.prod.PROD_Plugin) interfaces(com.company.prod.PROD_PluginInterface) isPlugin(false)
 Debug: PROD_PluginManager.getPluginInstance().line(149): pClass loader(com.company.prod.PROD_PluginClassLoader@12405818) parent(sun.misc.Launcher$AppClassLoader@55f96302) pClass interface loader(sun.misc.Launcher$AppClassLoader@55f96302) parent(sun.misc.Launcher$ExtClassLoader@3d3fcdb0) PROD_PluginInterface loader(java.net.URLClassLoader@14ae5a5) parent(null)

I find it very strange that the PluginInterface ClassLoader has changed to a URLClassLoader.

I believe the problem is that the PluginInterface and the Plugin don't share a related ClassLoader and thus the Plugin's PluginInterface is technically a different Java interface from the Application's PluginInterface. If that assessment is correct then my question is how do I fix this so that the PluginInterface and the Plugin do share a related ClassLoader?

Or, perhaps my assessment is incorrect. In which case, my question is why doesn't the Plugin appear to implement the PluginInterface?

I've been wrestling with this for several days now so thanks in advance for any and all answers.

Edit

How is my code (not the plugin) loaded?

From within Eclipse: using the Eclipse Run -> Run menu option.

From outside of Eclipse: java -jar prod.jar

Upvotes: 0

Views: 971

Answers (1)

gwc
gwc

Reputation: 1293

Well after a VERY long time, I finally figured this out and am posting my findings here in case anyone else runs across a similar issue with isAssignableFrom.

When exporting the program from Eclipse using the Export Wizard for Java->Runable JAR file, I chose Library handling option Package required libraries into generated JAR and was getting the isAssignableFrom failure as described in the original post. After re-exporting using Library handling option Extract required libraries into generated JAR, everything worked as expected.

Upvotes: 1

Related Questions