0

我急切地寻求帮助,但无法在网上找到有关此特定主题的任何信息(许多相关主题让我的特定问题未得到解答)。

具体来说,我需要能够从中央和外部代码存储库下载代码(jar)。这是由需要将其添加到类加载器的类路径以供其后使用的引导代码完成的。这是我们进入已经讨论过很多次的主题的时候。我不喜欢黑客,所以我尝试了以下方法:

尝试#1:创建一个为此目的配置的 URLClassLoader 实例,然后通过它调用代码的“其余部分”。

失败:这里有1.5个问题(一个可能是另一个原因)。一是 URLClassLoader 通常更喜欢从其父级加载内容。一些代码必须同时存在于两个版本中,可能是不同的版本。如果使用来自父级的代码,它将继续使用“外部”类加载器进行其余的加载,这不是我们想要的,即使初始加载正常。其次,一些第三方库似乎直接访问系统类加载器,无论是有意还是无意(可能从它加载的类之一中获取)。

尝试#2:创建我的 URLClassLoader 的子类,它更喜欢自己而不是父类。覆盖 loadClass、getResource、getResources、getPackage、getPackages ......以后也可以使用其他方法来确保这一点。

失败:没有帮助(足够)。该第三方代码仍然无法加载某些资源

尝试#3创建另一个 URLClassLoader 的自定义子类,并使用 -Djava.system.class.loader=... 将其设置为系统类加载器

失败:这效果更好 - 更进一步,但尝试获取资源仍然失败。不过,这一次是不同的资源。我向所有被覆盖的方法添加了日志记录,以记录它们的调用和资源名称。大部分都找到了经常资源。有些人仍然没有,即使他们在那里(确认)。但是,即使我努力学习,我也不知道的是许多资源名称以斜杠结尾的调用。有些还有斜线,通常会出现美元符号(嵌套/内部类资源)。请求但未找到的一些示例:

com/acme/foo/bar/ClassName/ com/acme/foo/bar/ClassName/InnerClassName/

当我使用初始/引导类路径上的所有内容运行下载的代码时(并且不使用我的类加载器),一切正常 - 因此我的类加载器破坏了一些东西,但我需要它工作。

我最接近的猜测是:

  1. 第三方代码以某种方式获取了真正的系统类加载器,可能是通过它加载的某个类,然后使用它。我看不到对它的请求,它们肯定会失败,因为它没有整个类路径。

  2. 资源名称以斜线结尾的业务是由真正的系统类加载器支持的原因,但不是由我正在继承的 URLClassLoader 支持的原因。我只能猜测预期的返回 URL 以某种方式定位了以该名称为前缀的资源集合。尽管有可能,但这将很难匹配。此外,似乎一些斜杠位于分隔内部类名称的美元符号应该位于的位置,即在上面的示例中(为清楚起见添加了空格):

com/acme/foo/bar/ClassName / InnerClassName/

com/acme/foo/bar/ClassName $ InnerClassName/

请注意,我不能依靠假设它是 URLClassLoader 的子类并使用反射来调用其 addURL(URL) 方法来破解实际的系统类加载器。

有没有办法使这项工作?请帮忙!

更新

我刚刚做了一次额外的尝试。我创建了一个仅记录请求的虚拟包装类加载器(扩展 ClassLoader,而不是 URLClassLoader),然后将它们传递给父类(公共方法)或超类(受保护方法)。我将其设置为系统类加载器并手动将整个“内部”类路径添加到实际的外部类路径,然后尝试运行代码。这可以正常工作,就像没有自定义系统类加载器一样。记录的内容还发现,即使系统类加载器也为这些以斜线结尾的资源返回 null,但不是全部。我没有检查这些是否也适用于我的真实代码,但猜测它们可能 - 因为它们不是绊脚石。不知何故,自定义系统类加载器仍在被绕过。如何?

更新 2

在我的自定义系统类加载器中,我让一些类来自外部/真正的系统类加载器,例如 java.lang 中的那些。我现在怀疑我不应该有,内心的“世界”必须完全孤立。但是,与它进行通信会带来问题,而我剩下的就是反射......但不确定这是否会起作用 - 即是否可以有多个 java.lang.Class 和/或 java.lang 。目的?

4

2 回答 2

0

鉴于所有限制,这似乎并不完全可能以我想要的坚如磐石的方式出现:

a) 第三方库可能总是“行为不端”并获取他们不应该以一种或另一种方式使用的 lassloader。我按照 fge 的建议查看了 OneJar,但他们有同样的问题 - 他们只检测到它的可能性。

b) 无法完全替换/隐藏系统类加载器。

c) 将系统类加载器转换为 URLClassLoader 可能随时停止工作。

于 2015-03-16T14:21:28.790 回答
0

看来,您不了解类加载器结构。在当前版本的普通 JVM 中,至少有 3 个类加载器:

  1. 负责加载核心类的引导加载程序。Class由于这涉及到和自身类似的类ClassLoader,它不能用ClassLoader实例来表示。所有getClassLoader()返回值null由引导加载程序加载的类
  2. 扩展加载器。它负责ext/在 JRE 的目录中加载类。Afaik,它可能会在未来的版本中消失。它的父加载器是引导加载器
  3. 应用程序加载器。如果没有指定其他父级,它将被返回ClassLoader.getSystemClassLoader()并且将被使用。在当前配置中,它的父级是扩展加载器,但在未来的版本中,它可能会将引导加载器作为其直接父级

结论是,如果您想重新加载应用程序的类而不委托给父加载器会破坏您的工作,那么您不需要操纵类加载器的实现。您只需要指定正确的父级。这很简单

URLClassLoader cl=new URLClassLoader(urls, ClassLoader.getSystemClassLoader().getParent());

这样,新类加载器的父类将是原始应用程序类加载器的父类,因此已经加载的应用程序类不在新加载器的范围内,而其他一切都照常工作。

关于以斜线结尾的资源,它们并不常见。当它们实际引用一个目录时,它们可能会被解析,但这取决于 URL 的协议和该协议的实际处理程序。例如,它可能适用于file:URL,但通常不适用于jar:URL,除非 jar 文件包含目录的伪条目。我也看到它适用于ftp:URL。

另外要理解的是,如果一个类直接引用另一个类,会查询到它原来定义的类加载器,不一定是应用类加载器。例如,当类java.lang.String包含对java.lang.Object(确实如此)的引用时,该引用将使用引导加载程序直接解析,因为这是java.lang.String.

这意味着,如果您操纵加载器的父查找而不遵循标准父委托,则您冒着将名称解析为不同运行时类的风险,因为当父加载器加载的类引用相同名称时,您将面临解析相同名称的风险。您可以按照上述解决方案中的标准程序来避免此类问题。JRE 类永远不会包含对您的应用程序类的引用,并且没有将原始应用程序加载器作为其父级的新加载器将永远不会干扰原始应用程序加载器加载的类。

于 2015-05-20T15:24:52.233 回答