Я написал простую систему плагинов, чтобы я мог включать расширения из внешних источников. Диспетчер подключаемых модулей загружает подключаемые модули из заранее определенных каталогов приложения и пользователя plugins
. Я использую собственный URLClassLoader
, чтобы можно было изолировать все плагины. Вот общий подход:
- Найдите файл JAR подключаемого модуля в одном из заранее определенных каталогов.
- Если подключаемый модуль уже был загружен, верните экземпляр подключаемого модуля, который уже был создано.
- Если экземпляр настраиваемого
URLClassLoader
не был создан для каталога, содержащего подключаемый модуль, создайте его. - L oop через каждый класс в файле JAR подключаемого модуля, ищущий для классов, реализующих
PluginInterface
, путем загрузки класса с использованием Class.forName( pluginName, false, PluginClassLoader )
и последующего тестирования, чтобы увидеть, является ли PluginInterface
isAssignableFrom
загруженным классом. - Если класс реализует
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