[spring-projects/spring-boot]LaunchedURLClassLoader可以返回不相关的JAR内容

2017-12-16 824 views
9

你好。

我们使用 Spring Boot 1.5.8 作为开源身份管理软件 midPoint 的一部分。最近我们发现,在启动过程中,midPoint 无法正确解析如下所示的 URI(注意:schema-3.7.jar 是我们的组件之一):

jar:file:/C:/tmp/mp/lib/midpoint.war!/WEB-INF/lib/schema-3.7.jar!/prism/xml/ns/public/types-3.xsd

调用方法获取的是完全不相关的二进制数据(没有任何错误指示),而不是预期的 types-3.xsd 的 XML 内容!原来数据就是这个文件的内容:

C:\tmp\mp\lib\midpoint.war!/WEB-INF/lib/hibernate-commons-annotations-4.0.5.Final.jar

schema-3.7.jar 和 hibernate-commons-annotations-4.0.5.Final.jar 文件没有任何共同点,除了它们都位于类路径上(首先是 hibernate jar,其次是 schema - 这很重要)。

我们能够针对这种情况创建一个测试用例(受到 LaunchedURLClassLoaderTests 中类似测试的启发):

@Test
public void resolveFromNestedNestedJarAbsolutePath() throws Exception {
    File file = this.temporaryFolder.newFile();
    TestJarCreator.createTestJar(file, false, true);   // creates a structure described below
    JarFile jarFile = new JarFile(file);
    JarFile nestedJarFile = jarFile.getNestedJarFile(jarFile.getEntry("nesting-nested.jar"));
    JarFile nJarFile = jarFile.getNestedJarFile(jarFile.getEntry("n123456789012345678901234567890.jar"));
    LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { nJarFile.getUrl(), nestedJarFile.getUrl() }, null);
    String absolutePath = nestedJarFile.getUrl() + "nested.jar!/3.dat";
    URL resource = loader.getResource(absolutePath);
    System.out.println("Looked for: " + absolutePath);
    System.out.println("Found resource: " + resource);
    assertThat(resource.toString()).isEqualTo(absolutePath);
    assertThat(resource.openConnection().getInputStream().read()).isEqualTo(3);
}

测试 JAR 文件是 midpoint.war 的模拟,包含以下条目(除其他外):

  • nesting-nested.jar(这类似于 midPoint 中的 schema-3.7.jar)
    • nested.jar(在 midPoint 中不存在)
    • 3.dat(包含值 3)(类似于 types-3.xsd)
  • n123456789012345678901234567890.jar(类似于 hibernate-commons-annotations-4.0.5.Final.jar)
    • (除清单外不包含任何条目)

如果类路径是:

  1. jar:文件:/ C:/用户/.../junit4507521104116618629.tmp!/n123456789012345678901234567890.jar!/
  2. jar:文件:/C:/Users/.../junit4507521104116618629.tmp!/nesting-nested.jar!/

然后方法调用:

loader.getResource("jar:file:/C:/Users/.../junit4507521104116618629.tmp!/nesting-nested.jar!/nested.jar!/3.dat")

返回 n123456789012345678901234567890.jar 的内容而不是 3.dat,没有任何警告。

为了完整起见,系统输出是

Looked for: jar:file:/C:/Users/.../junit4507521104116618629.tmp!/nesting-nested.jar!/nested.jar!/3.dat
Found resource: jar:file:/C:/Users/.../junit4507521104116618629.tmp!/nesting-nested.jar!/nested.jar!/3.dat

但例外是

org.junit.ComparisonFailure: 
Expected: 3
Actual: 80

(请注意,80 是“P”字符,是 n1234...jar 文件的第一个)

问题出在同一类中与extractFullSpec(String, String)方法相关的JarURLConnection.get(URL, JarFile)方法中。该实现忽略了这样一个事实:要解析的 URL 可能指向与调用者提供的 JarFile 不同的 JAR 文件。在这种情况下,通常不会发生任何问题;但文件名长度有可能发生冲突,导致 extractFullSpec 返回一个空字符串,稍后在 get 方法中将其解释为“我们找到了匹配项”。如果需要,我可以提供更多详细信息。

请在即将提交的提交中找到几个测试用例(均在 LaunchedURLClassLoaderTests 和 JarURLConnectionTests 中)和建议的修复。


在阅读 JarURLConnection.get/extractFullSpec 的代码时,我们发现了另外两个错误,我们为此编写了单独的测试用例。第一个与 extractFullSpec 返回的路径中多个段的解析有关(这一行应该是“=”而不是“+=” )。第二个与 jar 条目名称中的空格和其他不符合 URI 的字符有关。再次,请查看创建的测试用例和建议的修复。


您能看一下问题和建议的修复吗?如果它们是正确的,我们可以提交 PR。如果不是,请您给我们一个提示,我们应该怎样做才能以正确的方式解决它们?

谢谢。

回答

5

只需说明一下为什么在我们的情况下加载程序会获取绝对 URI 来解析(因为这可能一开始就是错误的 - 这是我真的不确定的事情)。

在我们的例子中,加载程序由 Apache CXF 解析器调用(TransportURIResolver.resolve 方法)。它得到的参数

  • curUri = jar:文件:/C:/tmp/mp/lib/midpoint.war!/WEB-INF/lib/schema-3.7.jar!/prism/xml/ns/public/types-3.xsd
  • baseUri = jar:文件:/C:/tmp/mp/lib/midpoint.war!/WEB-INF/lib/schema-3.7.jar!/xml/ns/public/common/common-3.xsd

我认为这样的参数是合法的。

然后它尝试解析 URL

jar:file:/C:/tmp/mp/lib/midpoint.war!jar:file:/C:/tmp/mp/lib/midpoint.war!/WEB-INF/lib/schema-3.7.jar!/prism/xml/ns/public/types-3.xsd

通过调用 URL(...).openStream。 URL 显然是错误的,因为那里有两个按顺序排列的midpoint-war.jar段。收到 IOException 后,它会要求类加载器执行以下操作:

getResource("jar:file:/C:/tmp/mp/lib/midpoint.war!/WEB-INF/lib/schema-3.7.jar!/prism/xml/ns/public/types-3.xsd")

这时 Spring Boot 就派上用场了。

所以实际上我不确定传递给 getResource 的参数是否合法,即主要问题是否出在 CXF 或 Spring Boot 中。但是 - 可以肯定 - 如果 LaunchedURLClassLoader 无法解析的值

jar:file:/C:/tmp/mp/lib/midpoint.war!/WEB-INF/lib/schema-3.7.jar!/prism/xml/ns/public/types-3.xsd

它应该返回 null,而不是一些不相关的内容。

2

可能与 #10268 和 #11057 中的讨论有关。恐怕这可能需要等到假期结束后才能得到@wilkinsona 的一些意见。

0

@philwebb 谢谢。我正在寻找您的意见。同时我也提交了CXF 问题。

1

感谢您的详细分析,@mederly。

我不确定我是否能看到与 #10268 和 #11057 的联系。这些问题都是 DevTools 特有的,一般来说,LaunchedURLClassLoader涉及时不会涉及到。

jar 启动器是一段非常棘手且对性能敏感的代码。例如,我们已经了解到,并且最近再次被提醒,任何产生额外垃圾的事情都会对性能产生相当大的影响。这意味着如果可能的话,通常应避免对子字符串的调用。

在我们开始寻找解决方案之前,我想确保我完全理解到底发生了什么。为此,您能否打开一个 PR,添加一个或多个失败的测试,以尽可能少地说明您发现的问题?

7

@wilkinsona 感谢您的考虑。我已经通过测试创建了 PR(11497;目前不知道如何处理 CLA)。

只是一些评论:有两个(最初报告的三个)问题尚未解决;同时修复了一个。这些都是:

  1. 返回错误的 JAR 内容(这是一个主要问题),
  2. 错误地处理不符合 URI 的字符,例如条目名称中的空格(这是一个小问题)。

添加了两个级别的测试:

  1. 较低级别:JarURLConnectionTests:connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile() 适用于问题 1,connectionToEntryUsingRelativeUrlForDoublyNestedEntryWithSpace() 和 connectionToEntryUsingRelativeUrlForDoublyNestedEntryWithTwoSpaces() 适用于问题 2。
  2. 上层:LaunchedURLClassLoaderTests:resolveFromDoublyNestedJarUsingUriAsResourceName...() 适用于问题 1(部分适用于问题 2),resolveFromDoublyNestedJarHavingSpace() 适用于问题 2。

希望没事。

2

谢谢你,@mederly。您可以签署 CLA 吗?在您完成之前,我们无法对您的 PR 中的测试做太多事情。

5

@wilkinsona 我正在尝试与我的雇主解决这个问题。有一些技术问题需要解决。我会尽快签字,

8

@wilkinsona我迷路了。 CLA 已签署(我收到一条消息,指出“感谢您签署协议!对 spring-projects/spring-boot/pull/11497 的贡献者许可协议检查现在应该通过。”),但我无法手动同步 PR #11497(从 Pivotal 页面收到“内部服务器错误”),所以我关闭了它。我也无法创建新的 PR - #11600 告诉我“请签署贡献者许可协议!”。

我不想通过一次又一次的尝试在您的系统中创建太多垃圾,所以,请看一下这个。 :) 如果有什么我能做的,请告诉我。

3

@wilkinsona 切换电子邮件地址没有帮助。当前 PR #11601 再次被 CLA 检查阻止。

1

好的。感谢您的尝试。让我们等待@rwinch 的 CLA 专业知识。

0

@mederly 由于这是公司签名,请参阅常见问题解答。具体来说:

如果您的公司将签名与 GitHub 组织相关联,则您必须确保满足以下条件。

  • 您的 GitHub 帐户是所选组织的成员
  • 您已公开您的会员资格

目前,github 状态给我到处都是独角兽,这可以解释为什么同步没有发生。

图像

3

@rwinch 谢谢你。但在我打开第二个 PR (#11600) 之前我已经满足了这两个要求。看这里。当 github 状态正常时,我现在还能做些什么(更多)吗?我应该创建第四个 PR 吗?或者有没有办法“同步”当前打开的 PR #11601?先感谢您。

8

@mederly 感谢您提供更多信息。

我发现 GitHub 权限已更改,这导致 CLA 工具无法更新状态(这就是发生错误页面的原因)。 PR 现在已标记为已签名,问题应该已得到解决。

感谢您的耐心使用 Spring,当然还有您对社区的贡献!

6

我认为这个问题现在已经通过 #12483 和 #12765 的组合解决了。 @mederly 你能用最新的快照再试一次吗?

7

@philwebb 刚才检查了master。 LaunchedURLClassLoaderTests 中的更高级别测试通过。 JarURLConnectionTests 中的较低级别测试几乎通过:除了 connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile() 之外的所有测试。

    @Test(expected = FileNotFoundException.class)
    public void connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile()
            throws Exception {
        // coincidental match (extractFullSpec would return "")
        URL url = new URL("jar:file:" + getAbsolutePath() + "!/w.jar!/3.dat");
        JarFile nested = this.jarFile
                .getNestedJarFile(this.jarFile.getEntry("nested.jar"));
        JarURLConnection.get(url, nested).getInputStream();
    }

也许我们可以忍受这一点;但如果它也能得到修复我会感觉更好。 :)


不管怎样,我确实有一个问题:1.5.x 分支上的测试没有通过。缺少的是这个更改: JarURLConnection.java:257/266 目前是

index += separator + SEPARATOR.length();

但显然应该是

index = separator + SEPARATOR.length();

(因为它是在 master 上 - 你已经在 2017 年 12 月 19 日更改了它)。如果没有这个更改,我在 1.5.x 上的许多测试都会失败。

您能否在 1.5.x 上也实现此更改?

谢谢。

9

@mederly 感谢您的检查。我们将尝试修复 1.5.x 中剩下的两个问题。