Maven Surefire 中的类加载和分叉
本页讨论 Maven Surefire 下的类加载和分叉,这是 Surefire 和 Failsafe Maven 插件使用的共享组件,着眼于解决问题。
执行摘要
如果您遇到问题,您可能需要修改以下三个设置:forkCount
、useSystemClassLoader
和useManifestOnlyJar
.
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
。如果useSystemClassLoader
是true
,那么我们使用仅清单 JAR;否则,我们使用隔离的类加载器。如果你想使用一个基本的普通 Java 类路径,你可以设置useManifestOnlyJar=false
which only has an effect when useSystemClassLoader=true
.
useSystemClassLoader
Surefire 2.3 和 Surefire 2.4 之间更改的默认值,这是一个非常显着的变化。在 Surefire 2.3useSystemClassLoader
中,false
默认情况下,我们使用了一个隔离的类加载器。在 Surefire 2.4useSystemClassLoader
中,true
默认为。没有任何价值适用于所有人,但我们认为这个默认值是一种改进;当我们useSystemClassLoader=true
.
不幸的是,如果useSystemClassLoader
为您的应用程序设置不正确,您将遇到一个很难诊断的问题。您甚至可能被迫阅读像这样的长文档页面。;-)
如果您在加载类时遇到问题,请尝试设置useSystemClassLoader=false
以查看是否有帮助。您可以使用下面的 POM 片段或通过设置-Dsurefire.useSystemClassLoader=false
. 如果这不起作用,请尝试设置useSystemClassLoader
回true
并设置useManifestOnlyJar
为false
.
<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
,并使用调试器附加到正在运行的进程。