URLClassLoader находит X, но не Y в той же папке

Общая идея: я пишу загрузчик для java, который позволяет динамически перезагружать классы, чтобы разрешить изменение реализации, без перезапуска всей программы, чтобы основное приложение оставалось работающим и минимизировало время простоя. Каждый внешний фрагмент кода сгруппирован по «модулям», каждый модуль имеет основной класс с точкой входа/выхода «onEnable, postEnable, onDisable» и может состоять из любого количества классов. Чтобы загрузить модуль, класс, содержащий точку входа, указывается, а затем загружается. Я буду ссылаться на них как на «модули» и «дополнительные классы» в дальнейшем, «модуль» — это класс, содержащий вышеупомянутые функции путем реализации «модуля публичного интерфейса», «дополнительные классы» относятся ко всему, что модуль будет использовать на время выполнения, но сам по себе не является модулем (например, у нас есть модуль под названием «Автомобиль реализует модуль», и для этого модуля требуется класс «Двигатель» -> «Автомобиль» — это модуль, «Двигатель» — это дополнительный класс) )

Код того, что я делаю для первоначальной загрузки модуля (имя - это строка, содержащая полное имя класса, включая путь, пример приведен ниже):

Class<?> clazz = mainLoader.loadClass(name);
Module module = (Module) clazz.newInstance();
addLoadedModule(module);
enableLoadedModule(module);

А вот как я перезагружаю модуль, когда он уже существует, чтобы я мог переопределить реализацию. «m» — это экземпляр текущей реализации модуля, который должен быть перезагружен.

boolean differs = false;
Class<?> newClass = null;
try (URLClassLoader cl = new URLClassLoader(urls, mainLoader.getParent()))
{
    // Try to load the class and check if it differs from the already known one
    newClass = cl.loadClass(m.getClass().getName());
    differs = m.getClass() != newClass;
}
catch (IOException | ClassNotFoundException e)
{
    // Class couldn't be found, abort.
    e.printStackTrace();
    return;
}
if (!differs)
{
    // New class == old class -> no need to reload it
    return;
}
Module module = null;
try
{
    // Try to instantiate the class
    module = (Module) newClass.newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
    // Can't instantiate, abort
    e.printStackTrace();
    return;
}
// Check versions, only reload if the new implementation's version differs from the current one. Version is a custom annotation, don't worry about that; the version check works fine
Version oldVersion = m.getClass().getAnnotation(Version.class);
Version newVersion = module.getClass().getAnnotation(Version.class);
if (oldVersion.equals(newVersion))
{
    return;
}
// And if everything went well, disable and remove the old module from the list, then add and enable the new module.
disableModule(m);
modules.remove(m);
modules.put(module, false);
enableLoadedModule(module);

Это mainLoader, urls — это URL [], указывающий на место, содержащее внешние классы для загрузки:

mainLoader = new URLClassLoader(urls, this.getClass().getClassLoader());

Проблема возникает, когда я пытаюсь повторно загрузить реализацию, для которой требуется несколько классов:

Модуль класса A требует, чтобы класс B функционировал. Вот что происходит, когда я пытаюсь динамически загрузить, а затем перезагрузить класс A:

load A -> "Конечно, но мне понадобится B вместе с ним." -> автоматически загружает B -> "Вот, теперь A работает нормально." перезагрузить A -> «Конечно, но мне понадобится B». -> вылетает, потому что B не может быть найден

Оба класса находятся в одной и той же папке и имеют такую ​​структуру:

  • Класс A реализует модуль: com/foo/bar/A.class
  • Класс B: com/foo/bar/B.class
  • URL-адреса: ["com/foo/bar/"]

Я вызываю функцию с помощью load("com.foo.bar.A"), которая работает при попытке загрузить ее в первый раз, но терпит неудачу при попытке перезагрузить ее, как описано выше.

Он отлично работает при попытке загрузить «модуль одного класса», проблема возникает, когда модуль полагается на дополнительный внешний класс. Я пытался использовать разные загрузчики классов в качестве родителя для URLClassLoader в процессе перезагрузки, это sysloader, Module.class.getClassLoader(), mainLoader (используя его, он никогда не найдет новое определение класса, потому что он уже знает об этом и поэтому даже не будет пытаться снова загрузить его с диска) и mainLoader.getParent(), загрузчик классов старого модуля и родитель загрузчика классов модулей.

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

Если вам нужны какие-либо выходные данные отладки или точные ошибки, дайте мне знать, я заменил выходные данные отладки комментариями, объясняющими, что и к чему, поэтому я получил довольно подробный журнал того, что происходит, когда, но мне это не казалось необходимым, поскольку это происходит весь процесс «проверить, а затем загрузить» просто прекрасен, он вылетает при попытке включить модуль. Метод «onEnable» модуля требует дополнительного класса B, вот где он терпит неудачу. Как я уже сказал, если вам нужна реализация классов A и B, модуля, любого другого кода или результатов отладки, дайте мне знать, и я добавлю их по запросу.


person Pepich1851    schedule 29.03.2017    source источник


Ответы (2)


Вот несколько вещей, которые вы можете попробовать:

  1. Создайте расширение UrlClassLoader, чтобы вы могли отслеживать, когда он загружает класс, и какой загрузчик классов используется для загрузки класса.
  2. Другая ваша проблема заключается в том, чтобы убедиться, что ни один из этих классов не доступен в пути к классу «по умолчанию», так как это приведет к использованию этой версии. Вы не переопределяете поведение загрузки класса по умолчанию, которое заключается в том, чтобы сначала проверить родителя для класса.
  3. Другая проблема, с которой вы, вероятно, сталкиваетесь, связана с тем, как виртуальная машина кэширует классы - я не совсем уверен, как это работает, - но из того, что я испытал, кажется, что после загрузки класса он помещает его в общее пространство для хранения. чтобы он не загружал класс снова. Этот класс общего пространства не будет выгружен до тех пор, пока загрузчик классов, который его загрузил, не станет недоступным.
person Michael Wiles    schedule 29.03.2017
comment
Несмотря на то, что это не решение проблемы, в нем содержится подсказка, позволяющая решить проблему: этот класс общего пространства не будет выгружен до тех пор, пока загрузчик классов, который его загрузил, не станет недоступным. -> Что касается моего кода, загрузчик классов существует только во время предложения try/catch, где я изначально получаю класс нового модуля. Поскольку он закрывается и удаляется, как только я заканчиваю загрузку класса, он, очевидно, больше не может загружать другие классы. Теперь я сохраняю загрузчик классов и закрываю его только при загрузке нового модуля, что позволяет загружать дополнительные классы. - person Pepich1851; 29.03.2017

Решение заключается в том, что загрузчик классов закрывается и удаляется, как только загрузка исходного класса завершена, поскольку загрузчик классов существует только в предложении try/catch. Я решил проблему, сохранив загрузчик классов на карте до тех пор, пока не будет загружена новая реализация модуля, после чего я могу отказаться от старого загрузчика и вместо этого сохранить новый.

person Pepich1851    schedule 29.03.2017