繁体   English   中英

自定义类加载器在斜杠中使用名称ENDING的getResources时出现问题

[英]Custom classloader trouble with getResources for names ENDING in slash

我迫不及待地寻求帮助,但无法在网上找到关于这个特定主题的任何内容(许多相关的问题都没有得到解答)。

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

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

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

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

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

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

失败:这样做得更好 - 走得更远,但仍未能尝试获取资源。 但这次是不同的资源。 我添加了日志记录到所有重写的方法来记录他们的调用和资源名称。 经常找到资源。 有些人仍然没有,即使他们在那里(已确认)。 但是,即使我努力学习,我还不知道的是有很多关于资源名称以斜线结尾的调用。 有些也有斜线,通常会出现美元符号(嵌套/内部类资源)。 一些已请求但未找到的示例:

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

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

我最接近的猜测是:

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

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

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

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

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

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

UPDATE

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

更新2

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

考虑到所有限制因素,这似乎并不像我想要的那样以坚如磐石的方式完全实现:

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

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

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

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

  1. 引导加载程序 ,负责加载核心类。 因为这涉及类,如ClassClassLoader本身,它不能由一个表示ClassLoader实例。 getClassLoader()返回null所有类都由引导加载程序加载
  2. 扩展加载器。 它负责在JRE的ext/目录中加载类。 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类永远不会包含对应用程序类的引用,并且新的加载器没有原始的应用程序加载器,因为它的父级将永远不会干扰原始应用程序加载器加载的类。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM