Java ClassLoader в Eclipse отличается от загрузчика вне Eclipse - PullRequest
0 голосов
/ 06 мая 2020

Я написал простую систему плагинов, чтобы я мог включать расширения из внешних источников. Диспетчер подключаемых модулей загружает подключаемые модули из заранее определенных каталогов приложения и пользователя plugins. Я использую собственный URLClassLoader, чтобы можно было изолировать все плагины. Вот общий подход:

  1. Найдите файл JAR подключаемого модуля в одном из заранее определенных каталогов.
  2. Если подключаемый модуль уже был загружен, верните экземпляр подключаемого модуля, который уже был создано.
  3. Если экземпляр настраиваемого URLClassLoader не был создан для каталога, содержащего подключаемый модуль, создайте его.
  4. L oop через каждый класс в файле JAR подключаемого модуля, ищущий для классов, реализующих PluginInterface, путем загрузки класса с использованием Class.forName( pluginName, false, PluginClassLoader ) и последующего тестирования, чтобы увидеть, является ли PluginInterface isAssignableFrom загруженным классом.
  5. Если класс реализует PluginInterface, то новый экземпляр класса создается, инициализируется и сохраняется для дальнейшего использования.

Все это отлично работает, когда я запускаю его из среды Eclipse IDE. Но когда я запускаю его вне среды IDE, проверка, реализует ли подключаемый модуль PluginInterface, не выполняется. Я считаю, что это связано с тем, что в Eclipse и подключаемый модуль, и интерфейс имеют связанный (родительский или дочерний) ClassLoader (а именно sun.misc.Launcher$AppClassLoader@73d16e93), а за пределами Eclipse PluginInterface имеет несвязанный ClassLoader (а именно java.net.URLClassLoader@14ae5a5 ).

Вот код:

Пользовательский 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 );
    }

}

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() );
        }
    }

Отладочная информация при работе под 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)

Отладочная информация при работе вне 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)

Мне очень странно, что PluginInterface ClassLoader изменился на URLClassLoader.

Я считаю, что проблема в том, что PluginInterface и подключаемый модуль не имеют общего ClassLoader и, следовательно, PluginInterface подключаемого модуля технически отличается Java интерфейсом от PluginInterface Приложения. Если эта оценка верна, то мой вопрос: как мне это исправить, чтобы PluginInterface и плагин делали совместно с ClassLoader?

Или, возможно, моя оценка неверна . В этом случае у меня вопрос: почему плагин, похоже, не реализует PluginInterface?

Я борюсь с этим уже несколько дней, поэтому заранее благодарим за любые ответы.

Редактировать

Как загружается мой код (не плагин)?

Изнутри Eclipse: с помощью параметра меню Eclipse Run -> Run.

Из-за пределов Затмение: java -jar prod.jar

...