Class Loading Reference
本文档解释了 Quarkus 类加载体系结构。它适用于希望确切了解 Quarkus 如何工作的扩展作者和高级用户。
This document explains the Quarkus class loading architecture. It is intended for extension authors and advanced users who want to understand exactly how Quarkus works.
Quarkus 类加载体系结构略有不同,具体取决于应用程序的运行模式。
The Quarkus class loading architecture is slightly different depending on the mode that the application is run in.
使用 fast-jar
软件包类型(这是默认设置)运行生产应用程序时,几乎所有依赖关系都通过 io.quarkus.bootstrap.runner.RunnerClassLoader
加载,后者会在构建时编制类索引,同时由系统 ClassLoader 加载一小部分依赖关系。
When running a production application using the fast-jar
package type
(which is the default), almost all dependencies are loaded via the io.quarkus.bootstrap.runner.RunnerClassLoader
which indexes class at build time, while a small set of dependencies is loaded from the system ClassLoader.
使用 legacy-jar
软件包类型运行生产应用程序时,所有内容都在系统 ClassLoader 中加载,因此它是完全平坦的类路径。
When running a production application using the legacy-jar
package type everything is loaded
in the system ClassLoader, so it is a completely flat classpath.
平坦类路径策略也用于 GraalVM 本机映像,因为 GraalVM 实际上不支持多个 ClassLoader。
The flat classpath strategy is also used for GraalVM native images, since GraalVM does not really support multiple ClassLoaders.
对于所有其他用例(例如,测试、开发模式以及构建应用程序),Quarkus 使用下一节中概述的类加载体系结构。
For all other use cases (e.g. tests, dev mode, and building the application) Quarkus uses the class loading architecture outlined in following section.
Bootstrapping Quarkus
所有 Quarkus 应用程序都由 independent-projects/bootstrap
模块中的 QuarkusBootstrap 类创建。此类用于解决 Quarkus 应用程序所需的全部相关依赖项(部署和运行时依赖项)。此过程的最终结果是一个 CuratedApplication
,其中包含该应用程序的所有类加载信息。
All Quarkus applications are created by the QuarkusBootstrap class in the independent-projects/bootstrap
module. This
class is used to resolve all the relevant dependencies (both deployment and runtime) that are needed for the Quarkus
application. The end result of this process is a CuratedApplication
, which contains all the class loading information
for the application.
然后可使用 CuratedApplication
创建一个 AugmentAction
实例,该实例可以创建生产应用程序并启动/重新启动运行时应用程序。此应用程序实例存在于隔离的 ClassLoader 中,无需在类路径中包含任何 Quarkus 部署类,因为整理过程会为您解决这些类。
The CuratedApplication
can then be used to create an AugmentAction
instance, which can create production application
and start/restart runtime ones. This application instance exists within an isolated ClassLoader, it is not necessary
to have any of the Quarkus deployment classes on the class path as the curate process will resolve them for you.
无论 Quarkus 如何启动,此引导过程都应保持相同,只需传入不同的参数即可。
This bootstrap process should be the same no matter how Quarkus is launched, just with different parameters passed in.
Current Run Modes
目前,我们有以下用例来引导 Quarkus:
At the moment we have the following use cases for bootstrapping Quarkus:
-
Maven creating production application
-
Maven dev mode
-
Gradle creating a production application
-
Gradle dev mode
-
QuarkusTest (Maven, Gradle and IDE)
-
QuarkusUnitTest (Maven, Gradle and IDE)
-
QuarkusDevModeTest (Maven, Gradle and IDE)
-
Arquillian Adaptor
此重构的目标之一是让所有这些不同的运行模式在根本上以相同的方式引导 Quarkus。
One of the goals of this refactor is to have all these different run modes boot Quarkus in fundamentally the same way.
Notes on Transformer Safety
如果在 transformer 准备好之前加载类时是安全的,则称 ClassLoader 是“transformer 安全”。一旦加载某个类,该类就无法更改,因此,如果在 transformer 已准备就绪之前加载某个类,这将妨碍转换正常工作。在 transformer 安全的 ClassLoader 中加载类不会妨碍转换,因为已加载的类在运行时不会被使用。
A ClassLoader is said to be 'transformer safe' if it is safe to load classes in the class loader before the transformers are ready. Once a class has been loaded it cannot be changed, so if a class is loaded before the transformers have been prepared this will prevent the transformation from working. Loading classes in a transformer safe ClassLoader will not prevent the transformation, as the loaded class is not used at runtime.
ClassLoader Implementations
Quarkus 具有多个 ClassLoader。下文显示了它们之间的关系:
Quarkus has a number of ClassLoaders. This shows the relationship between them :
以下是每个 ClassLoader 的角色:
Here are the roles of each 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 会很复杂,因为这意味着所有生成器类都是隔离的,这意味着想要定制生成链条的用例会稍微复杂一些。
At present this can be configured to delegate to the Base ClassLoader, however the plan is for this option to go away and always have this as an isolated ClassLoader. Making this an isolated ClassLoader is complicated as it means that all the builder classes are isolated, which means that use cases that want to customise the build chains are slightly more complex.
- 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 安全的。
This ClassLoader is non-persistent, it will be re-created when the application is started, and is isolated. This ClassLoader is the context ClassLoader that is used when running the build steps. It is also transformer safe.
- 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 实体。需要转换此类以实现其静态方法,但此转换仅发生一次,因此重新启动会使用在首次启动时创建的该类副本。
This loads code that is not hot-reloadable, but it does support transformation (although once the class is loaded this transformation is no longer possible). This means that only transformers registered in the first application start will take effect, however as these transformers are expected to be idempotent this should not cause problems. An example of the sort of transformation that might be required here is a Panache entity packaged in an external jar. This class needs to be transformed to have its static methods implemented, however this transformation only happens once, so restarts use the copy of the class that was created on the first start.
此 ClassLoader 与 Augment 和 Deployment ClassLoader 隔离。这意味着无法在部署端设置某个静态字段中的值,并期望在运行时读取它。这允许开发和测试应用程序的行为更像生产应用程序(生产应用程序是隔离的,因为它们在全新 JVM 中运行)。
This ClassLoader is isolated from the Augment and Deployment ClassLoaders. This means that it is not possible to set values in a static field in the deployment side, and expect to read it at runtime. This allows dev and test applications to behave more like a production application (production applications are isolated in that they run in a whole new JVM).
这也意味着可以针对不同的依赖项集链接运行时版本,例如,部署时使用的 hibernate 版本可能希望包含 ByteBuddy,而运行时使用的版本则不包含。
This also means that the runtime version can be linked against a different set of dependencies, e.g. the hibernate version used at deployment time might want to include ByteBuddy, while the version used at runtime does not.
- 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 始终是隔离的。这意味着它将具有解析的依赖项列表中几乎每个类的自己的副本。例外情况是:
The runtime ClassLoader is always isolated. This means that it will have its own copies of almost every class from the resolved dependency list. The exception to this are:
-
JDK classes
-
Classes from artifacts that extensions have marked as parent first (more on this later).
Parent First Dependencies
有一些类不应孤立加载,而应该始终由 systemClassLoader(或负责引导 Quarkus 的任何 ClassLoader)加载。大多数扩展无需担心此问题,但有少数情况下有此必要:
There are some classes that should not be loaded in an isolated manner, but that should always be loaded by the system ClassLoader (or whatever ClassLoader is responsible for bootstrapping Quarkus). Most extensions do not need to worry about this, however there are a few cases where this is necessary:
-
Some logging related classes, as logging must be loaded by the system ClassLoader
-
Quarkus bootstrap itself
如果需要,可在 quarkus-extension-maven-plugin
中进行配置。请注意,如果您将某个依赖项标记为父级,则其所有依赖项也必须是父级,否则可能发生 LinkageError
.
If this is required it can be configured in the quarkus-extension-maven-plugin
. Note that if you
mark a dependency as parent first then all of its dependencies must also be parent first,
or a LinkageError
can occur.
<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
部分:
There are some dependencies that we can be sure we do not want. This generally happens when a dependency has had a name
change (e.g. smallrye-config changing groups from org.smallrye
to org.smallrye.config
, the javax
→ jakarta
rename).
This can cause problems, as if these artifacts end up in the dependency tree out of date classes can be loaded that are
not compatible with Quarkus. To deal with this, extensions can specify artifacts that should never be loaded. This is
done by modifying the quarkus-extension-maven-plugin
config in the pom (which generates the quarkus-extension.properties
file). Simply add an excludedArtifacts
section as shown below:
<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>
只有在扩展依赖于这些工件的较新版本时才应执行此操作。如果扩展没有引入替代表单工件作为依赖项,则应用程序所需的类可能最终缺失。
This should only be done if the extension depends on a newer version of these artifacts. If the extension does not bring in a replacement artifact as a dependency then classes the application needs might end up missing.
Configuring Class Loading
可以在开发和测试模式下配置类加载的一些方面。这可以使用 application.properties
来完成。请注意,类加载配置不同于常规配置,因为它不使用标准 Quarkus 配置机制(因为它需要太早),所以只支持 application.properties
。支持以下选项。
It is possible to configure some aspects of class loading in dev and test mode. This can be done using application.properties
.
Note that class loading config is different to normal config, in that it does not use the standard Quarkus config mechanisms
(as it is needed too early), so only supports application.properties
. The following options are supported.
Unresolved directive in class-loading-reference.adoc - include::{generated-dir}/config/quarkus-core_quarkus.class-loading.adoc[]
Hiding/Removing classes and resources from dependencies
可以隐藏/移除依赖项中的类和资源。这是一个高级选项,但有时可能有用。这通过 quarkus.class-loading.removed-resources
配置密钥完成,例如:
It is possible to hide/remove classes and resources from dependencies. This is an advanced option, but it can be useful
at times. This is done via the quarkus.class-loading.removed-resources
config key, for example:
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
文件。
This will remove the RemovedResource.class
file from the io.quarkus:quarkus-integration-test-shared-library
artifact.
即使此选项是一个类加载选项,它也会影响生成的应用程序,因此在创建应用程序时,无法访问已移除的资源。
Even though this option is a class loading option it will also affect the generated application, so when the application is created removed resources will not be accessible.
Reading Class Bytecode
使用正确的 ClassLoader
非常重要。建议的方法是调用 Thread.currentThread().getContextClassLoader()
方法来获取它。
It is important to use the correct ClassLoader
. The recommended approach is to get it by calling the
Thread.currentThread().getContextClassLoader()
method.
示例:
Example:
@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));
}