Общая идея: я пишу загрузчик для 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, модуля, любого другого кода или результатов отладки, дайте мне знать, и я добавлю их по запросу.