getResources 的自定义类加载器问题以斜杠结尾的名称

Custom classloader trouble with getResources for names ENDING in slash

我迫切需要帮助,但无法在网上找到关于这个特定主题的任何信息(许多相关的问题都没有解决我的特定问题)。

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

尝试 #1: 创建一个 URLClassLoader 的实例,然后调用代码的 "rest"通过它。

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

尝试 #2: 创建我的 URLClass 加载程序的子 class,它比父加载程序更喜欢自己。覆盖 loadClass, getResource, getResources, getPackage, getPackages... 稍后其他方法也可以确保这一点。

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

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

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

com/acme/foo/bar/ClassName/ com/acme/foo/bar/ClassName/内Class姓名/

当我 运行 下载的代码包含 initial/boot class 路径上的所有内容(并且不使用我的 classloader)时,一切正常 - 因此我的 class 装载机坏了东西,但我需要它来工作。

我最接近的猜测是:

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

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

com/acme/foo/bar/ClassName/内Class姓名/

com/acme/foo/bar/ClassName$内Class姓名/

请注意,我不能通过假设它是 URLClassLoader 的子class 并使用反射来破解实际系统 classloader调用它的 addURL(URL) 方法。

有没有办法让它工作?请帮忙!

更新

我刚刚又做了一次尝试。我创建了一个虚拟包装器 classloader(扩展 ClassLoader,而不是 URLClassLoader),它只记录请求,然后将它们传递给父级(public 方法)或 superclass(受保护的方法)。我将其设置为系统 class 加载程序并手动将整个 "inner" class 路径添加到实际的外部路径,然后尝试 运行 代码。这工作正常,就像没有自定义系统 class 加载程序一样。记录的内容还表明,即使这些资源的系统 class 加载程序 return 为空,但大多数资源以斜杠结尾,但不是全部。我没有检查这些是否也适用于我的真实代码,但猜测它们可能 - 因为它们不是绊脚石。自定义系统 classloader 仍然以某种方式被绕过。怎么样?

更新 2

在我的自定义系统 class 加载器中,我让一些 classes 来自 outer/true 系统 class 加载器,例如java.lang 中的那些。我现在怀疑我不应该有,内部 "world" 必须完全隔离。但是,与它进行通信会带来问题,而我所剩下的就是反思......但不确定这是否有效 - 即是否可以有多个 java.lang.Class and/or java.lang.对象?

考虑到所有限制,这似乎完全不可能像我想要的那样坚如磐石:

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

b) 没有办法完全replace/hide系统class加载程序。

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

看来,你没看懂class加载器结构。在当前版本的普通JVM中,至少有三个class个加载器:

  1. bootstrap加载器负责加载核心classes。由于这涉及 class 类 ClassClassLoader 本身,因此不能用 ClassLoader 实例表示。 class getClassLoader() returns null 的所有 class 都由 bootstrap 加载程序
  2. 加载
  3. 扩展加载器。它负责在 JRE 的 ext/ 目录中加载 classes。 Afaik,它可能会在未来的版本中消失。它的 parent 加载程序是 bootstrap 加载程序
  4. 应用程序加载器。这是将由 ClassLoader.getSystemClassLoader() 返回的一个, 如果未指定其他 parent ,将使用它。在当前的配置中,它的 parent 是扩展加载器,但也许在未来的版本
  5. 中它会将 bootstrap 加载器作为其直接 parent

结论是,如果您想重新加载您的应用程序的 classes 而无需委派给 parent 加载程序破坏您的工作,您不需要操纵 class加载程序的实现。您只需指定正确的 parent。就这么简单

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

这样,新的 class 加载程序的 parent 将是原始应用程序 class 加载程序的 parent,因此已经加载的应用程序 class 是不在新加载程序的范围内,而其他一切正常。

关于以斜杠结尾的资源,比较少见。当它们实际引用目录时,它们可能会得到解析,但这取决于 URL 的协议和该协议的实际处理程序。例如。它可能适用于 file: URLs 但通常不适用于 jar: URLs 除非 jar 文件包含目录的 pseudo-entries。我还看到它适用于 ftp: URLs.

另一件需要理解的事情是,如果一个 class 直接引用另一个 class,它的原始定义 class 加载器将被查询,不一定是应用程序 class装载机。例如。当 class java.lang.String 包含对 java.lang.Object 的引用(确实如此)时,该引用将使用 bootstrap 加载程序直接解析,因为这是 [=21] 的定义加载程序=].

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