在 GitHub 上叉我

Maven Surefire 中的类加载和分叉

本页讨论 Maven Surefire 下的类加载和分叉,这是 Surefire 和 Failsafe Maven 插件使用的共享组件,着眼于解决问题。

执行摘要

如果您遇到问题,您可能需要修改以下三个设置:forkCountuseSystemClassLoaderuseManifestOnlyJar.

Maven Surefire 项目解决了什么问题?

最初,问题似乎很简单。只需使用类路径启动 Java,如下所示:

java -classpath foo.jar:bar.jar MyApp

但是这里有一个问题:在某些操作系统(Windows)上,您可以制作命令行的时间有限制,因此您可以制作类路径的时间也有限制。不同版本的 Windows 限制不同;在某些版本中,只允许几百个字符,而在其他版本中则为几千个,但无论哪种情况,限制都非常严格。

Maven Surefire 2.8.2 更新

事实证明,将 设置CLASSPATH为环境变量可以消除大部分实际长度限制,如SUREFIRE-727中所述。这意味着本文中大部分与长度相关的问题可能已经过时。

通用解决方案

您可以使用两个“技巧”来解决此问题;在某些情况下,它们都可能导致其他问题。

1.隔离类加载器:一种解决方法是使用隔离类加载器。除了直接启动MyApp之外,我们还可以启动其他一些具有更短类路径的应用程序(“引导程序”)。然后我们可以创建一个新的 java.lang.ClassLoader (通常是 a java.net.URLClassLoader)并配置所需的类路径。然后引导程序可以从类加载器加载MyApp ;MyApp引用其他类时,它们会自动从我们隔离的类加载器中加载。

使用隔离类加载器的问题是你的类路径并不正确,一些应用程序可以检测到这个和对象。例如,系统属性java.class.path不会包含您的 jar;如果您的应用程序注意到这一点,则可能会导致问题。

使用隔离类加载器还有另一个类似的问题:任何类都可能调用静态方法ClassLoader.getSystemClassLoader()并尝试从该类加载器中加载类,而不是使用默认的类加载器。如果类需要创建自己的类加载器,它们通常会这样做。不幸的是,像 Jetty、Tomcat、BEA WebLogic 和 IBM WebSphere 这样的基于 Java 的 Web 应用程序服务器很可能试图摆脱隔离类加载器的限制。

2. Manifest-Only JAR:另一种解决方法是使用“manifest-only JAR”。在这种情况下,您将创建一个几乎完全为空的临时 JAR,除了一个META-INF/MANIFEST.MF文件。Java 清单可以包含 Java 虚拟机将作为指令尊重的属性。例如,您可以有一个Class-Path属性,它指定要添加到类路径的其他 JAR 列表。那么你可以像这样运行你的代码:

java -classpath booter.jar MyApp

这有点现实,因为在这种情况下系统类加载器、线程上下文类加载器和默认类加载器都是相同的;不可能“逃避”类加载器。但这仍然是对“正常”类路径的奇怪模拟,应用程序仍然有可能注意到这一点。同样,java.class.path可能不是您所期望的(“为什么它只包含一个罐子?”)。此外,可以查询系统类加载器以从中获取 jar 列表;如果您的应用程序只找到我们的存在,您的应用程序可能会感到困惑booter.jar

每种解决方案的优点/缺点

如果您的应用程序尝试查询其自己的类加载器以获取 JAR 列表,则它在隔离类加载器下的工作可能比在仅清单 JAR 下工作得更好。但是,如果您的应用程序试图逃避其默认的类加载器,它可能根本无法在隔离的类加载器下工作。

使用隔离类加载器的一个优点是,它是使用隔离类加载器的唯一方法,无需派生单独的进程,在与 Maven 本身相同的进程中运行所有测试。但这本身就有很大的风险,尤其是当 Maven 嵌入在您的 IDE 中运行时!

最后,当然,您可以尝试连接一个普通的旧 Java 类路径,并希望它足够短。在最坏的情况下,您的类路径可能会在某些机器上运行,而在其他机器上则不行。Windows 机器的行为与 Linux 机器不同;短用户名的用户可能比长用户名的用户更成功,等等。出于这个原因,我们选择不将基本类路径设为默认值,尽管我们确实提供了它作为一个选项(主要是作为最后的手段)。

Maven Surefire 是做什么的?

Surefire 提供了一种使用多种策略的机制。决定这一点的主要参数称为useSystemClassLoader. 如果useSystemClassLoadertrue,那么我们使用仅清单 JAR;否则,我们使用隔离的类加载器。如果你想使用一个基本的普通 Java 类路径,你可以设置useManifestOnlyJar=falsewhich only has an effect when useSystemClassLoader=true.

useSystemClassLoaderSurefire 2.3 和 Surefire 2.4 之间更改的默认值,这是一个非常显着的变化。在 Surefire 2.3useSystemClassLoader中,false默认情况下,我们使用了一个隔离的类加载器。在 Surefire 2.4useSystemClassLoader中,true默认为。没有任何价值适用于所有人,但我们认为这个默认值是一种改进;当我们useSystemClassLoader=true.

不幸的是,如果useSystemClassLoader为您的应用程序设置不正确,您将遇到一个很难诊断的问题。您甚至可能被迫阅读像这样的长文档页面。;-)

如果您在加载类时遇到问题,请尝试设置useSystemClassLoader=false以查看是否有帮助。您可以使用下面的 POM 片段或通过设置-Dsurefire.useSystemClassLoader=false. 如果这不起作用,请尝试设置useSystemClassLoadertrue并设置useManifestOnlyJarfalse.

<project>
  [...]
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M5</version>
        <configuration>
          <useSystemClassLoader>false</useSystemClassLoader>
        </configuration>
      </plugin>
    </plugins>
  </build>
  [...]
</project>

调试类路径问题

如果您已经阅读到这里,您可能已经完全具备诊断类加载过程中可能出现的问题的能力。以下是一些尝试的一般提示:

  • --debug使用(或等效地)运行 Maven-X以获得更详细的输出
  • 检查您的forkCount. 如果forkCount=0(或forkMode=never不推荐使用的版本),则无法使用系统类加载器或普通的旧 Java 类路径;我们必须使用一个隔离的类加载器。
  • 如果您使用默认值,useSystemClassLoader=true并且useManifestOnlyJar=false. 在这种情况下,请查看生成的仅清单 Surefire 引导程序 JAR。打开它(它只是一个 zip)并阅读它的清单。
  • 使用 运行 Maven -Dmaven.surefire.debug,并使用调试器附加到正在运行的进程。