Class Loading Reference
本文档解释了 Quarkus 类加载体系结构。它适用于希望确切了解 Quarkus 如何工作的扩展作者和高级用户。
Quarkus 类加载体系结构略有不同,具体取决于应用程序的运行模式。
使用 fast-jar
软件包类型(这是默认设置)运行生产应用程序时,几乎所有依赖关系都通过 io.quarkus.bootstrap.runner.RunnerClassLoader
加载,后者会在构建时编制类索引,同时由系统 ClassLoader 加载一小部分依赖关系。
使用 legacy-jar
软件包类型运行生产应用程序时,所有内容都在系统 ClassLoader 中加载,因此它是完全平坦的类路径。
平坦类路径策略也用于 GraalVM 本机映像,因为 GraalVM 实际上不支持多个 ClassLoader。
对于所有其他用例(例如,测试、开发模式以及构建应用程序),Quarkus 使用下一节中概述的类加载体系结构。
Bootstrapping Quarkus
所有 Quarkus 应用程序都由 independent-projects/bootstrap
模块中的 QuarkusBootstrap 类创建。此类用于解决 Quarkus 应用程序所需的全部相关依赖项(部署和运行时依赖项)。此过程的最终结果是一个 CuratedApplication
,其中包含该应用程序的所有类加载信息。
然后可使用 CuratedApplication
创建一个 AugmentAction
实例,该实例可以创建生产应用程序并启动/重新启动运行时应用程序。此应用程序实例存在于隔离的 ClassLoader 中,无需在类路径中包含任何 Quarkus 部署类,因为整理过程会为您解决这些类。
无论 Quarkus 如何启动,此引导过程都应保持相同,只需传入不同的参数即可。
ClassLoader Implementations
Quarkus 具有多个 ClassLoader。下文显示了它们之间的关系:
以下是每个 ClassLoader 的角色:
- Base ClassLoader
-
This is usually the normal JVM System ClassLoader. In some environments such as Maven it may be different. This ClassLoader is used to load the bootstrap classes, and other ClassLoader instances will delegate the loading of JDK classes to it.
- Augmentation ClassLoader
-
This loads all the
-deployment
artifacts and their dependencies, as well as other user dependencies. It does not load the application root or any hot deployed code. This ClassLoader is persistent, even if the application restarts it will remain (which is why it cannot load application classes that may be hot deployed). Its parent is the base ClassLoader, and it is transformer safe.
目前,可以将其配置为委托给基本 ClassLoader,但是该计划是取消此选项,并始终将其作为隔离的 ClassLoader。将其设为隔离的 ClassLoader 会很复杂,因为这意味着所有生成器类都是隔离的,这意味着想要定制生成链条的用例会稍微复杂一些。
- Deployment ClassLoader
-
This can load all application classes. Its parent is the Augmentation ClassLoader, so it can also load all deployment classes.
此 ClassLoader 是非持久的,将在应用程序启动时重新创建,且是隔离的。此 ClassLoader 是用于运行生成步骤时的上下文 ClassLoader。它也是 transformer 安全的。
- Base Runtime ClassLoader
-
This loads all the runtime extension dependencies, as well as other user dependencies (note that this may include duplicate copies of classes also loaded by the Augmentation ClassLoader). It does not load the application root or any hot deployed code. This ClassLoader is persistent, even if the application restarts it will remain (which is why it cannot load application classes that may be hot deployed). Its parent is the base ClassLoader.
此 ClassLoader 会加载不可热重新加载的代码,但它支持转换(尽管一旦加载了该类,就不再可能进行此转换)。这意味着只有在首次应用程序启动时注册的 transformer 才会生效,但由于预期这些 transformer 是幂等的,因此这不会造成问题。此处可能需要转换工作的示例是打包在外部 jar 中的 Panache 实体。需要转换此类以实现其静态方法,但此转换仅发生一次,因此重新启动会使用在首次启动时创建的该类副本。
此 ClassLoader 与 Augment 和 Deployment ClassLoader 隔离。这意味着无法在部署端设置某个静态字段中的值,并期望在运行时读取它。这允许开发和测试应用程序的行为更像生产应用程序(生产应用程序是隔离的,因为它们在全新 JVM 中运行)。
这也意味着可以针对不同的依赖项集链接运行时版本,例如,部署时使用的 hibernate 版本可能希望包含 ByteBuddy,而运行时使用的版本则不包含。
- Runtime Class Loader
-
This ClassLoader is used to load the application classes and other hot deployable resources. Its parent is the base runtime ClassLoader, and it is recreated when the application is restarted.
Isolated ClassLoaders
运行时 ClassLoader 始终是隔离的。这意味着它将具有解析的依赖项列表中几乎每个类的自己的副本。例外情况是:
-
JDK classes
-
扩展已标记为父项优先的工件中的类(稍后将对此进行更详细的说明)。
Parent First Dependencies
有一些类不应孤立加载,而应该始终由 systemClassLoader(或负责引导 Quarkus 的任何 ClassLoader)加载。大多数扩展无需担心此问题,但有少数情况下有此必要:
-
某些与日志记录相关的类,因为日志记录必须由系统 ClassLoader 加载
-
Quarkus bootstrap itself
如果需要,可在 quarkus-extension-maven-plugin
中进行配置。请注意,如果您将某个依赖项标记为父级,则其所有依赖项也必须是父级,否则可能发生 LinkageError
.
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<configuration>
<parentFirstArtifacts>
<parentFirstArtifact>io.quarkus:quarkus-bootstrap-core</parentFirstArtifact>
<parentFirstArtifact>io.quarkus:quarkus-development-mode-spi</parentFirstArtifact>
<parentFirstArtifact>org.jboss.logmanager:jboss-logmanager</parentFirstArtifact>
<parentFirstArtifact>org.jboss.logging:jboss-logging</parentFirstArtifact>
<parentFirstArtifact>org.ow2.asm:asm</parentFirstArtifact>
</parentFirstArtifacts>
</configuration>
</plugin>
Banned Dependencies
有些依赖项我们能确定不需要。这通常在依赖项更名时发生(例如 smallrye-config 将组从 org.smallrye
更改为 org.smallrye.config
、javax
→ jakarta`的重命名)。这可能会导致问题,因为如果这些工件最终进入依赖项树,则有可能加载与 Quarkus 不兼容的过时类。为了解决这个问题,扩展可以指定绝不能加载的工件。这可以通过修改 pom 中的 `quarkus-extension-maven-plugin
配置(生成 quarkus-extension.properties
文件)来完成。只需按以下所示添加一个 excludedArtifacts
部分:
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<configuration>
<excludedArtifacts>
<excludedArtifact>io.smallrye:smallrye-config</excludedArtifact>
<excludedArtifact>javax.enterprise:cdi-api</excludedArtifact>
</excludedArtifacts>
</configuration>
</plugin>
只有在扩展依赖于这些工件的较新版本时才应执行此操作。如果扩展没有引入替代表单工件作为依赖项,则应用程序所需的类可能最终缺失。
Configuring Class Loading
可以在开发和测试模式下配置类加载的一些方面。这可以使用 application.properties
来完成。请注意,类加载配置不同于常规配置,因为它不使用标准 Quarkus 配置机制(因为它需要太早),所以只支持 application.properties
。支持以下选项。
Unresolved include directive in modules/ROOT/pages/class-loading-reference.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-core_quarkus.class-loading.adoc[]
Hiding/Removing classes and resources from dependencies
可以隐藏/移除依赖项中的类和资源。这是一个高级选项,但有时可能有用。这通过 quarkus.class-loading.removed-resources
配置密钥完成,例如:
quarkus.class-loading.removed-resources."io.quarkus\:quarkus-integration-test-shared-library"=io/quarkus/it/shared/RemovedResource.class
这会从 io.quarkus:quarkus-integration-test-shared-library
工件中移除 RemovedResource.class
文件。
即使此选项是一个类加载选项,它也会影响生成的应用程序,因此在创建应用程序时,无法访问已移除的资源。
Reading Class Bytecode
使用正确的 ClassLoader
非常重要。建议的方法是调用 Thread.currentThread().getContextClassLoader()
方法来获取它。
示例:
@BuildStep
GeneratedClassBuildItem instrument(final CombinedIndexBuildItem index) {
final String classname = "com.example.SomeClass";
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
final byte[] originalBytecode = IoUtil.readClassAsBytes(cl, classname);
final byte[] enhancedBytecode = ... // class instrumentation from originalBytecode
return new GeneratedClassBuildItem(true, classname, enhancedBytecode));
}