URLClassLoader在同一文件夹

时间:2017-03-29 09:30:17

标签: java urlclassloader

总体思路:我正在编写一个java加载器,它允许动态重新加载类以允许更改实现,而无需重新启动整个程序以保持主应用程序运行并最大限度地减少停机时间。每个外部代码都按“模块”分组,每个模块都有一个带有“onEnable,postEnable,onDisable”入口/出口点的主类,可以包含任意数量的类。要加载模块,请指定包含入口点的类,然后加载。我将在下面将它们称为“模块”和“附加类”,“模块”是通过实现“公共接口模块”包含上述功能的类,“附加类”指的是模块将使用的所有内容。运行时但不是模块本身(例如我们有一个名为“Car implements Module”的模块,该模块需要一个类“Engine”才能运行 - >“Car”是模块,“Engine”是一个额外的类“)

我最初在加载模块时所做的代码(name是一个包含完整类名的字符串,包括路径,稍后给出的示例):

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类时会发生这种情况:

加载A - &gt; “当然,但我需要带它的B。” - &GT;自动加载B - &gt; “你走了,现在A工作正常。” 重新加载A - &gt; “当然,但我需要带它的B。” - &GT;因为找不到B而崩溃

这两个类都位于完全相同的文件夹中,结构如下:

  • A类实现Module:com / foo / bar / A.class
  • B组:com / foo / bar / B.class
  • urls:[“com / foo / bar /”]

我用load(“com.foo.bar.A”)调用该函数,该函数在第一次尝试加载时有效,但在尝试重新加载它时失败,如上所述。

在尝试加载“单个类模块”时工作正常,当模块依赖于额外的外部类时会出现问题。我尝试使用不同的类加载器作为重载过程中URLClassLoader的父类,那些是sysloader,Module.class.getClassLoader(),mainLoader(使用那个,它不会找到新的类定义,因为它已经了解它,因此甚至不会尝试再次从驱动器加载它)和mainLoader.getParent(),旧模块的类加载器,以及模块类加载器的父类。

我可能只是在监督一些显而易见的事情,但我无法弄清楚为什么它会第一次加载“额外”类,但是当我重新加载基类时失败...

如果你需要任何调试输出或确切的错误让我知道,我用调试来替换调试输出,解释什么是什么,所以我得到了一个相当详细的日志,当时发生了什么,但我似乎没有必要作为它经历了整个“检查然后加载”过程就好了,它在尝试启用模块时崩溃了。模块的“onEnable”方法需要额外的B类,即失败的地方。正如我所说,如果您需要A类和B类的实现,模块,任何其他代码或调试输出让我知道,我会按要求添加它们。

2 个答案:

答案 0 :(得分:1)

您可以尝试一些事项:

  1. 创建UrlClassLoader的扩展,以便您可以跟踪何时加载类以及用于加载类的类加载器。
  2. 您的另一个问题是确保&#34;默认&#34;中没有这些类可用。类路径,因为这将导致该版本使用。您没有覆盖默认的类加载行为,即首先检查类的父级。
  3. 您可能面临的另一个问题与VM缓存课程的方式有关 - 我不完全确定这是如何工作的 - 但从我所经历的情况来看,似乎一旦课程是加载它将它放在共享存储空间中,以便它不会再次加载该类。在加载它的类加载器无法访问之前,不会卸载此共享空间类。

答案 1 :(得分:0)

解决方案在于,在完成初始类的加载后,类加载器将被关闭并删除,因为类加载器仅在try / catch子句中存在。我通过将类加载器存储在映射中直到加载模块的新实现来解决问题,然后我可以丢弃旧的加载器并存储新的加载器。