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 如何启动,此引导过程都应保持相同,只需传入不同的参数即可。

Current Run Modes

目前,我们有以下用例来引导 Quarkus:

  • Maven creating production application

  • Maven dev mode

  • Gradle 创建生产应用程序

  • Gradle dev mode

  • QuarkusTest(Maven、Gradle 和 IDE)

  • QuarkusUnitTest(Maven、Gradle 和 IDE)

  • QuarkusDevModeTest(Maven、Gradle 和 IDE)

  • Arquillian Adaptor

此重构的目标之一是让所有这些不同的运行模式在根本上以相同的方式引导 Quarkus。

Notes on Transformer Safety

如果在 transformer 准备好之前加载类时是安全的,则称 ClassLoader 是“transformer 安全”。一旦加载某个类,该类就无法更改,因此,如果在 transformer 已准备就绪之前加载某个类,这将妨碍转换正常工作。在 transformer 安全的 ClassLoader 中加载类不会妨碍转换,因为已加载的类在运行时不会被使用。

ClassLoader Implementations

Quarkus 具有多个 ClassLoader。下文显示了它们之间的关系:

classloader hierarchy

以下是每个 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.configjavaxjakarta`的重命名)。这可能会导致问题,因为如果这些工件最终进入依赖项树,则有可能加载与 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));
}