Tips for writing native applications

本指南包含各种技巧,帮助你解决在尝试以本机可执行文件运行 Java 应用程序时可能遇到的问题。

This guide contains various tips and tricks for getting around problems that might arise when attempting to run Java applications as native executables.

请注意,我们区分两种上下文,在这些上下文中应用的解决方案可能不同:

Note that we differentiate two contexts where the solution applied might be different:

  • in the context of an application, you will rely on configuring the native-image configuration by tweaking your pom.xml;

  • in the context of an extension, Quarkus offers a lot of infrastructure to simplify all of this.

根据你的语境,请参阅相应部分。

Please refer to the appropriate section depending on your context.

Supporting native in your application

GraalVM 会施加许多限制,而将你的应用程序变成可执行的原生应用程序可能需要进行一些微调。

GraalVM imposes a number of constraints and making your application a native executable might require a few tweaks.

Including resources

默认情况下,在构建原生可执行文件时,GraalVM 不会将 classpath 中的任何资源包含在它创建的可执行的原生文件中。要作为原生可执行文件的一部分的资源需要作明确配置。

By default, when building a native executable, GraalVM will not include any of the resources that are on the classpath into the native executable it creates. Resources that are meant to be part of the native executable need to be configured explicitly.

Quarkus 自动包含 META-INF/resources 中存在的资源(Web 资源),但在该目录之外,你需要自行处理。

Quarkus automatically includes the resources present in META-INF/resources (the web resources) but, outside this directory, you are on your own.

需要注意的是,你必须在这里极其谨慎,因为 META-INF/resources 中的任何内容都会作为静态 Web 资源公开。因此,这个目录不是“让我们自动将这些资源包含在可执行的原生文件中”的捷径,并且应该仅用于静态 Web 资源。

Note that you need to be extremely careful here as anything in META-INF/resources will be exposed as static web resources. So this directory is not a shortcut for "let’s automatically include these resources in the native executable" and should only be used for static web resources.

其他资源应作明确声明。

Other resources should be declared explicitly.

Using the quarkus.native.resources.includes configuration property

要将更多资源包含在可执行的原生文件中,最简单的方法是使用 quarkus.native.resources.includes 配置属性,以及用于排除资源的其对应属性 quarkus.native.resources.excludes

To include more resources in the native executable, the easiest way is to use the quarkus.native.resources.includes configuration property, and its counterpart to exclude resources quarkus.native.resources.excludes.

这两个配置属性都支持全局模式。

Both configuration properties support glob patterns.

例如,在你的 application.properties 中有以下属性:

For instance, having the following properties in your application.properties:

quarkus.native.resources.includes=foo/**,bar/**/*.txt
quarkus.native.resources.excludes=foo/private/**

将会包括:

will include:

  • all files in the foo/ directory and its subdirectories except for files in foo/private/ and its subdirectories,

  • all text files in the bar/ directory and its subdirectories.

Using a configuration file

如果全局模式对于你的用例来说不够精确,并且你需要使用正则表达式,或者如果你更愿意使用 GraalVM 基础设施,你还可以创建一个 resource-config.json JSON 文件来定义应包括哪些资源。该文件和其他本机镜像配置文件应放在 src/main/resources/META-INF/native-image/<group-id>/<artifact-id> 文件夹下。这样,它们将由本机构建自动解析,而无需其它配置。

If globs are not sufficiently precise for your use case and you need to rely on regular expressions, or if you prefer relying on the GraalVM infrastructure, you can also create a resource-config.json JSON file defining which resources should be included. This, and other native image configuration files, should be placed under the src/main/resources/META-INF/native-image/<group-id>/<artifact-id> folder. This way they will be automatically parsed by the native build, without additional configuration.

使用 GraalVM 基础设施意味着你需要负责在发布新的 Mandrel 和 GraalVM 版本时更新配置文件。

Relying on the GraalVM infrastructure means that you are responsible for keeping the configuration file up to date as new Mandrel and GraalVM versions are released.

另请注意,如果 resource-config.json 文件直接放在 src/main/resources/META-INF/native-image/ 下,会被 Quarkus 覆盖,因为 Quarkus 在该目录中生成了自己的配置文件。

Please also note that the resource-config.json file will be overwritten by Quarkus if placed directly under src/main/resources/META-INF/native-image/ as Quarkus generates its own configuration file in this directory.

此类文件的示例如下:

An example of such a file is the following:

{
  "resources": [
    {
      "pattern": ".*\\.xml$"
    },
    {
      "pattern": ".*\\.json$"
    }
  ]
}

模式是有效的 Java 正则表达式。此处我们将在可执行的原生文件中包含所有 XML 文件和 JSON 文件。

The patterns are valid Java regexps. Here we include all the XML files and JSON files into the native executable.

有关此主题的更多信息,请参阅 GraalVM Accessing Resources in Native Image指南。

For more information about this topic, see the GraalVM Accessing Resources in Native Image guide.

Registering for reflection

在构建本地可执行文件时,GraalVM 在封闭世界假设下运行。它分析调用树,并删除所有没有直接使用的类/方法/字段。

When building a native executable, GraalVM operates with a closed world assumption. It analyzes the call tree and removes all the classes/methods/fields that are not used directly.

通过反射使用的元素不属于调用树中的一部分,因此它们是无效代码(如果在其他情况下没有直接调用)。要将这些元素包含在本地可执行文件中,您需要显式地为反射注册它们。

The elements used via reflection are not part of the call tree so they are dead code eliminated (if not called directly in other cases). To include these elements in your native executable, you need to register them for reflection explicitly.

这是一个非常常见的案例,因为 JSON 库通常使用反射将对象序列化为 JSON:

This is a very common case as JSON libraries typically use reflection to serialize the objects to JSON:

    public class Person {
        private String first;
        private String last;

        public String getFirst() {
            return first;
        }

        public void setFirst(String first) {
            this.first = first;
        }

        public String getLast() {
            return last;
        }

        public void setValue(String last) {
            this.last = last;
        }
    }

    @Path("/person")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public class PersonResource {

        private final Jsonb jsonb;

        public PersonResource() {
            jsonb = JsonbBuilder.create(new JsonbConfig());
        }

        @GET
        public Response list() {
            return Response.ok(jsonb.fromJson("{\"first\":  \"foo\", \"last\":  \"bar\"}", Person.class)).build();
        }
    }

如果使用以上代码,使用本地可执行文件时我们将会得到类似以下的异常:

If we were to use the code above, we would get an exception like the following when using the native executable:

Exception handling request to /person: org.jboss.resteasy.spi.UnhandledException: jakarta.json.bind.JsonbException: Can't create instance of a class: class org.acme.jsonb.Person, No default constructor found

或者,如果您使用 Jackson:

or if you are using Jackson:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.acme.jsonb.Person and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

一种更加糟糕的可能结果是,没有抛出任何异常,但相反 JSON 结果是完全空的。

An even nastier possible outcome could be for no exception to be thrown, but instead the JSON result would be completely empty.

有两种不同的方法可以修复此类问题。

There are two different ways to fix this type of issues.

Using the @RegisterForReflection annotation

最简单的注册类以供反射使用的方法是使用 `@RegisterForReflection`注解:

The easiest way to register a class for reflection is to use the @RegisterForReflection annotation:

@RegisterForReflection
public class MyClass {
}

如果您的类位于第三方 jar 中,您可以使用一个托管 `@RegisterForReflection`的空类来进行注册。

If your class is in a third-party jar, you can do it by using an empty class that will host the @RegisterForReflection for it.

@RegisterForReflection(targets={ MyClassRequiringReflection.class, MySecondClassRequiringReflection.class})
public class MyReflectionConfiguration {
}

请注意, `MyClassRequiringReflection`和 `MySecondClassRequiringReflection`将被注册以供反射使用,但 `MyReflectionConfiguration`将不会被注册。

Note that MyClassRequiringReflection and MySecondClassRequiringReflection will be registered for reflection but not MyReflectionConfiguration.

在使用具有对象映射功能的第三方库(例如 Jackson 或 GSON)时,此功能非常方便:

This feature is handy when using third-party libraries using object mapping features (such as Jackson or GSON):

@RegisterForReflection(targets = {User.class, UserImpl.class})
public class MyReflectionConfiguration {

}

注意:默认情况下, @RegisterForReflection`注解还将注册任何潜在的嵌套类以供反射使用。如果您想避免此行为,您可以将 `ignoreNested`属性设置为 `true

Note: By default the @RegisterForReflection annotation will also registered any potential nested classes for reflection. If you want to avoid this behavior, you can set the ignoreNested attribute to true.

Using a configuration file

如果您更愿意依赖 GraalVM 基础架构,还可以使用配置文件注册类以供反射使用。

You can also use a configuration file to register classes for reflection, if you prefer relying on the GraalVM infrastructure.

使用 GraalVM 基础设施意味着你需要负责在发布新的 Mandrel 和 GraalVM 版本时更新配置文件。

Relying on the GraalVM infrastructure means that you are responsible for keeping the configuration file up to date as new Mandrel and GraalVM versions are released.

例如,为了注册类的所有方法 com.acme.MyClass`供反射使用,我们创建 `reflect-config.json(最常见的位置位于 `src/main/resources`中)

As an example, in order to register all methods of class com.acme.MyClass for reflection, we create reflect-config.json (the most common location is within src/main/resources)

[
  {
    "name" : "com.acme.MyClass",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true
  }
]

有关此文件格式的更多信息,请参阅 GraalVM Reflection in Native Image指南。

For more information about the format of this file, see the GraalVM Reflection in Native Image guide.

最后的工作是让 `native-image`可执行文件了解配置文件。要做到这一点,请将配置文件放在 `src/main/resources/META-INF/native-image/<group-id>/<artifact-id>`文件夹下。这样,它们将被本地版本自动解析,无需额外配置。

The final order of business is to make the configuration file known to the native-image executable. To do that, place the configuration file under the src/main/resources/META-INF/native-image/<group-id>/<artifact-id> folder. This way they will be automatically parsed by the native build, without additional configuration.

Registering for proxy

与 `@RegisterForReflection`类似,您可以使用 `@RegisterForProxy`注册接口以供动态代理使用:

Analogous to @RegisterForReflection, you can use @RegisterForProxy to register interfaces for dynamic proxy:

@RegisterForProxy
public interface MyInterface extends MySecondInterface {
}

请注意, `MyInterface`及其所有超接口都将被注册。

Note that MyInterface and all its super interfaces will be registered.

另外,如果界面位于第三方 jar 中,你可以通过使用一个将为其托管 @RegisterForProxy 的空类来实现它。

Also, in case the interface is in a third-party jar, you can do it by using an empty class that will host the @RegisterForProxy for it.

@RegisterForProxy(targets={MyInterface.class, MySecondInterface.class})
public class MyReflectionConfiguration {
}

请注意,指定代理接口的顺序很重要。有关更多信息,请参见 Proxy javadoc

Note that the order of the specified proxy interfaces is significant. For more information, see Proxy javadoc.

Delaying class initialization

默认情况下,Quarkus 会在构建时初始化所有类。

By default, Quarkus initializes all classes at build time.

在某些情况下,某些类的初始化是在静态块中完成的,需要推迟到运行时。通常,省略此类配置会导致以下运行时异常:

There are cases where the initialization of certain classes is done in a static block needs to be postponed to runtime. Typically, omitting such configuration would result in a runtime exception like the following:

Error: No instances are allowed in the image heap for a class that is initialized or reinitialized at image runtime: sun.security.provider.NativePRNG
Trace: object java.security.SecureRandom
method com.amazonaws.services.s3.model.CryptoConfiguration.<init>(CryptoMode)
Call path from entry point to com.amazonaws.services.s3.model.CryptoConfiguration.<init>(CryptoMode):

另一个常见错误来源是当 GraalVM 采用的映像堆包含一个 Random/SplittableRandom 实例时:

Another common source of errors is when the image heap taken by GraalVM contains a Random/SplittableRandom instance:

Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected.

这通常是由 Quarkus 在构建时初始化一个具有静态 Random/SplittableRandom 字段的类造成的,从而导致将此特定实例临时包含在映像堆中。

Which is more often than not caused by Quarkus initializing at build time a class with a static Random/SplittableRandom field, causing this particular instance to be tentatively included in the image heap.

你可以在 this blog post 中找到有关此 Random/SplittableRandom 问题的详细信息。

You can find detailed information about this Random/SplittableRandom issue in this blog post.

在这些情况下,延迟执行违规类的运行时初始化可能是解决方案,为此,你可以使用 --initialize-at-run-time=<package or class> 配置旋钮。

In these cases, delaying the infringing class initialization at runtime might be the solution and, to achieve that, you can use the --initialize-at-run-time=<package or class> configuration knob.

应该使用 native-image 配置,如上述示例中所示,将其添加到该配置中。

It should be added to the native-image configuration using the quarkus.native.additional-build-args configuration property as shown in the examples above.

有关更多信息,请参阅 GraalVM Class Initialization in Native Image 指南。

For more information, see the GraalVM Class Initialization in Native Image guide.

当需要通过 quarkus.native.additional-build-args 配置属性指定多个类或包时,需要转义 , 符号。以下是一个示例:

When multiple classes or packages need to be specified via the quarkus.native.additional-build-args configuration property, the , symbol needs to be escaped. An example of this is the following:

quarkus.native.additional-build-args=--initialize-at-run-time=com.example.SomeClass\\,org.acme.SomeOtherClass

如果使用 Maven 配置而不是 application.properties 则:

and in the case of using the Maven configuration instead of application.properties:

<quarkus.native.additional-build-args>--initialize-at-run-time=com.example.SomeClass\,org.acme.SomeOtherClass</quarkus.native.additional-build-args>

Managing Proxy Classes

在编写本机应用程序时,你需要通过指定它们实现的接口列表,在图像构建时定义代理类。

While writing native application you’ll need to define proxy classes at image build time by specifying the list of interfaces that they implement.

在这种情况下,你可能会遇到以下错误:

In such a situation, the error you might encounter is:

com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.

解决此问题需要在 src/main/resources/META-INF/native-image/<group-id>/<artifact-id> 文件夹下创建一个 proxy-config.json 文件。这样,该配置将由本机构建自动解析,无需额外配置。有关此文件格式的更多信息,请参阅 Dynamic Proxy Metadata in JSON 文档。

Solving this issue requires creating a proxy-config.json file under the src/main/resources/META-INF/native-image/<group-id>/<artifact-id> folder. This way the configuration will be automatically parsed by the native build, without additional configuration. For more information about the format of this file, see the Dynamic Proxy Metadata in JSON documentation.

Modularity Benefits

在构建本机可执行文件期间,GraalVM 会分析应用程序的调用树,并生成一个代码集,其中包含它所需的所有代码。拥有一个模块化代码库是避免应用程序中未使用或可选部分出现问题并且同时减少本机可执行文件构建时间和大小的关键。在本节中,你将了解本地应用程序模块化的优势背后的详细信息。

During native executable build time GraalVM analyses the application’s call tree and generates a code-set that includes all the code it needs. Having a modular codebase is key to avoiding problems with unused or optional parts of your application, while at the same time reducing both native executable build times and size. In this section you will learn about the details behind the benefits of modularity for native applications.

当代码不够模块化时,生成的本机可执行文件最终可能包含比用户需要的更多代码。如果某个功能没有使用,并且该代码已编译到本机可执行文件中,那么这就是本机编译时间和内存使用量,以及本机可执行文件磁盘空间和初始堆大小的浪费。当使用第三方库或专用 API 子系统并且它们的使用不够模块化时,会出现更多问题,从而导致本机编译或运行时错误。在 JAXB 库中发现了最近出现的问题,它能够使用 Java 的 AWT API 反序列化包含图像的 XML 文件。绝大多数 Quarkus XML 用户不需要反序列化图像,因此用户的应用程序无需包含 Java AWT 代码,除非他们专门配置 Quarkus 将 JAXB AWT 代码添加到本机可执行文件中。然而,由于使用 AWT 的 JAXB 代码与其他 XML 解析代码位于同一个 jar 中,因此实现这种分离相当复杂,并且需要使用 Java 字节码替换才能解决。这些替换很难维护,并且很容易损坏,因此它们应该作为最后的办法。

When code is not modular enough, generated native executables can end up with more code than what the user needs. If a feature is not used and the code gets compiled into the native executable, this is a waste of native compilation time and memory usage, as well as native executable disk space and starting heap size. Even more problems arise when third party libraries or specialized API subsystems are used which cause native compilation or runtime errors, and their use is not modularised enough. A recent problem can be found in the JAXB library, which is capable of deserializing XML files containing images using Java’s AWT APIs. The vast majority of Quarkus XML users don’t need to deserialize images, so there shouldn’t be a need for users applications to include Java AWT code, unless they specifically configure Quarkus to add the JAXB AWT code to the native executable. However, because JAXB code that uses AWT is in the same jar as the rest of the XML parsing code, achieving this separation was rather complex and required the use of Java bytecode substitutions to get around it. These substitutions are hard to maintain and can easily break, hence they should be one’s last resort.

模块化代码库是避免此类问题的最佳方法。以上面的 JAXB/AWT 问题为例,如果处理图像的 JAXB 代码位于单独的模块或 jar(例如 jaxb-images)中,那么 Quarkus 可以选择不包含该模块,除非用户明确要求在构建时序列化/反序列化包含图像的 XML 文件。

A modular codebase is the best way to avoid these kind of issues. Taking the JAXB/AWT problem above, if the JAXB code that dealt with images was in a separate module or jar (e.g. jaxb-images), then Quarkus could choose not to include that module unless the user specifically requested the need to serialize/deserialize XML files containing images at build time.

模块化应用程序的另一个好处是,它们能够减少需要放入原生可执行文件中的代码集。代码集越小,原生可执行文件的构建速度就越快,生成的文件也越小。

Another benefit of modular applications is that they can reduce the code-set that will need to get into the native executable. The smaller the code-set, the faster the native executable builds will be and the smaller the native executable produced.

这里关键的要点是:将可选功能(特别是那些依赖于具有较大空间占用量的第三方库或 API 子系统)放在单独的模块中是最佳解决方案。

The key takeaway point here is the following: Keeping optional features, particularly those that depend on third party libraries or API subsystems with a big footprint, in separate modules is the best solution.

我如何知道我的应用程序是否遇到了类似的问题?除了对应用程序进行深入研究之外,找到 Maven optional dependencies的用法是应用程序可能遇到类似问题的明确指示。应避免使用此类型的依赖关系,而应将与可选依赖关系交互的代码移入单独的模块中。

How do I know if my application suffers from similar problems? Aside from a deep study of the application, finding usages of Maven optional dependencies is a clear indicator that your application might suffer from similar problems. These type of dependencies should be avoided, and instead code that interacts with optional dependencies should be moved into separate modules.

Enforcing Singletons

delay class initialization部分中已说明的那样,Quarkus 默认情况下标记所有代码在构建时进行初始化。这意味着,除非另有标记,否则静态变量将在构建时分配,并且静态块也将同时在构建时执行。

As already explained in the delay-class-init-in-your-app section, Quarkus marks all code to be initialized at build time by default. This means that, unless marked otherwise, static variables will be assigned at build time, and static blocks will be executed at build time too.

这会导致 Java 程序中的值通常在每次运行时都会发生变化,但始终返回常数值。例如,分配了`System.currentTimeMillis()`值的静态字段在作为 Quarkus 原生可执行文件执行时,始终会返回相同的值。

This can cause values in Java programs that would normally vary from one run to another, to always return a constant value. E.g. a static field that is assigned the value of System.currentTimeMillis() will always return the same value when executed as a Quarkus native executable.

依赖于静态变量初始化的单例将遇到类似的问题。例如,假设您有一个基于静态初始化的单例,以及用于查询它的 REST 端点:

Singletons that rely on static variable initialization will suffer similar problems. For example, imagine you have a singleton based around static initialization along with a REST endpoint to query it:

@Path("/singletons")
public class Singletons {

    @GET
    @Path("/static")
    public long withStatic() {
        return StaticSingleton.startTime();
    }
}

class StaticSingleton {
    static final long START_TIME = System.currentTimeMillis();

    static long startTime() {
        return START_TIME;
    }
}

当查询`singletons/static`端点时,即使在应用程序重新启动后,它也会始终返回相同的值:

When the singletons/static endpoint is queried, it will always return the same value, even after the application is restarted:

$ curl http://localhost:8080/singletons/static
1656509254532%

$ curl http://localhost:8080/singletons/static
1656509254532%

### Restart the native application ###

$ curl http://localhost:8080/singletons/static
1656509254532%

依赖于`enum`类的单例也受同一问题影响:

Singletons that rely on enum classes are also affected by the same issue:

@Path("/singletons")
public class Singletons {

    @GET
    @Path("/enum")
    public long withEnum() {
        return EnumSingleton.INSTANCE.startTime();
    }
}

enum EnumSingleton {
    INSTANCE(System.currentTimeMillis());

    private final long startTime;

    private EnumSingleton(long startTime) {
        this.startTime = startTime;
    }

    long startTime() {
        return startTime;
    }
}

当查询`singletons/enum`端点时,即使在应用程序重新启动后,它也会始终返回相同的值:

When the singletons/enum endpoint is queried, it will always return the same value, even after the application is restarted:

$ curl http://localhost:8080/singletons/enum
1656509254601%

$ curl http://localhost:8080/singletons/enum
1656509254601%

### Restart the native application ###

$ curl http://localhost:8080/singletons/enum
1656509254601%

解决此问题的一种方法是使用 CDI 的 `@Singleton`注解构建单例:

One way to fix it is to build singletons using CDI’s @Singleton annotation:

@Path("/singletons")
public class Singletons {

    @Inject
    CdiSingleton cdiSingleton;

    @GET
    @Path("/cdi")
    public long withCdi() {
        return cdiSingleton.startTime();
    }
}

@Singleton
class CdiSingleton {
    // Note that the field is not static
    final long startTime = System.currentTimeMillis();

    long startTime() {
        return startTime;
    }
}

在每次重新启动后,查询 `singletons/cdi`将返回一个不同的值,就像在 JVM 模式中一样:

After each restart, querying singletons/cdi will return a different value, just like it would in JVM mode:

$ curl http://localhost:8080/singletons/cdi
1656510218554%

$ curl http://localhost:8080/singletons/cdi
1656510218554%

### Restart the native application ###

$ curl http://localhost:8080/singletons/cdi
1656510714689%

在依赖于静态字段或枚举时,强制单例的另一种方法是delay its class initialization until run time。基于 CDI 的单例的一个好处是,您的类初始化不受约束,因此您可以根据自己的使用情况自由决定是否应在构建时还是在运行时进行初始化。

An alternative way to enforce a singleton while relying static fields, or enums, is to delay-class-init-in-your-app. The nice advantage of CDI-based singletons is that your class initialization is not constrained, so you can freely decide whether it should be build-time or run-time initialized, depending on your use case.

Beware of common Java API overrides

某些常用的 Java 方法会被用户类覆盖,例如:toStringequalshashCode…​等。大多数覆盖不会导致问题,但如果它们使用第三方库(例如用于其他格式化)或使用动态语言功能(例如反射或代理),它们可能会导致原生映像构建失败。其中一些故障可以通过配置解决,但其他故障在处理上可能更棘手。

Certain commonly used Java methods are overriden by user classes, e.g. toString, equals, hashCode…​etc. The majority of overrides do not cause problems, but if they use third party libraries (e.g. for additional formatting), or use dynamic language features (e.g. reflection or proxies), they can cause native image build to fail. Some of those failures might be solvable via configuration, but others can be more tricky to handle.

从 GraalVM 指向分析的角度来看,即使应用程序没有显式调用这些方法覆盖,这些方法覆盖中发生的内容也很重要。这是因为这些方法在整个 JDK 中使用,只需在不受约束的类型(例如`java.lang.Object`)上执行其中一个调用即可,即可分析出该特定方法的所有实现。

From a GraalVM points-to analysis perspective, what happens in these method overrides matters, even if the application does not explicitly call them. This is because these methods are used throughout the JDK, and all it takes is for one of those calls to be done on an unconstrained type, e.g. java.lang.Object, for the analysis to have to pull all implementations of that particular method.

Supporting native in a Quarkus extension

在 Quarkus 扩展中支持原生版本甚至更容易,因为 Quarkus 提供了许多工具简化了所有这些操作。

Supporting native in a Quarkus extension is even easier as Quarkus provides a lot of tools to simplify all this.

此处描述的所有内容仅在 Quarkus 扩展的上下文中有效,在应用程序中无效。

Everything described here will only work in the context of Quarkus extensions, it won’t work in an application.

Register reflection

Quarkus 通过使用 `ReflectiveClassBuildItem`简化了扩展中反射的注册,从而消除了对 JSON 配置文件的需求。

Quarkus makes registration of reflection in an extension a breeze by using ReflectiveClassBuildItem, thus eliminating the need for a JSON configuration file.

要为反射注册一个类,需要创建一个 Quarkus 处理器类并添加一个构建步骤来注册反射:

To register a class for reflection, one would need to create a Quarkus processor class and add a build step that registers reflection:

public class SaxParserProcessor {

    @BuildStep
    ReflectiveClassBuildItem reflection() {
        // since we only need reflection to the constructor of the class, we can specify `false` for both the methods and the fields arguments.
        return new ReflectiveClassBuildItem(false, false, "com.sun.org.apache.xerces.internal.parsers.SAXParser");
    }

}

有关 GraalVM 中反射的更多信息,请参阅 GraalVM Reflection in Native Image指南。

For more information about reflection in GraalVM, see the GraalVM Reflection in Native Image guide.

Including resources

在扩展的上下文中,Quarkus 允许扩展作者指定一个 NativeImageResourceBuildItem 来消除对 JSON 配置文件的需求:

In the context of an extension, Quarkus eliminates the need for a JSON configuration file by allowing extension authors to specify a NativeImageResourceBuildItem:

public class ResourcesProcessor {

    @BuildStep
    NativeImageResourceBuildItem nativeImageResourceBuildItem() {
        return new NativeImageResourceBuildItem("META-INF/extra.properties");
    }

}

有关本机可执行文件中 GraalVM 资源处理的更多信息,请参阅 GraalVM Accessing Resources in Native Image 指南。

For more information about GraalVM resource handling in native executables, see the GraalVM Accessing Resources in Native Image guide.

Delay class initialization

Quarkus 通过允许扩展作者简单地注册一个 RuntimeInitializedClassBuildItem 来简化事情。以下是一个简单的示例:

Quarkus simplifies things by allowing extensions authors to simply register a RuntimeInitializedClassBuildItem. A simple example of doing so could be:

public class S3Processor {

    @BuildStep
    RuntimeInitializedClassBuildItem cryptoConfiguration() {
        return new RuntimeInitializedClassBuildItem(CryptoConfiguration.class.getCanonicalName());
    }

}

使用此结构意味着将自动向 native-image 命令行添加一个 --initialize-at-run-time 选项。

Using such a construct means that a --initialize-at-run-time option will automatically be added to the native-image command line.

有关 --initialize-at-run-time 选项的更多信息,请参阅 GraalVM Class Initialization in Native Image 指南。

For more information about the --initialize-at-run-time option, see the GraalVM Class Initialization in Native Image guide.

Managing Proxy Classes

非常类似地,Quarkus 允许扩展作者注册一个 NativeImageProxyDefinitionBuildItem。以下是一个示例:

Very similarly, Quarkus allows extensions authors to register a NativeImageProxyDefinitionBuildItem. An example of doing so is:

public class S3Processor {

    @BuildStep
    NativeImageProxyDefinitionBuildItem httpProxies() {
        return new NativeImageProxyDefinitionBuildItem("org.apache.http.conn.HttpClientConnectionManager",
                "org.apache.http.pool.ConnPoolControl", "com.amazonaws.http.conn.Wrapped");
    }

}

使用此结构意味着将自动向 native-image 命令行添加一个 -H:DynamicProxyConfigurationResources 选项。

Using such a construct means that a -H:DynamicProxyConfigurationResources option will automatically be added to the native-image command line.

有关代理类别的更多信息,请参阅 GraalVM Configure Dynamic Proxies Manually 指南。

For more information about Proxy Classes, see the GraalVM Configure Dynamic Proxies Manually guide.

Logging with Native Image

如果您使用需要日志组件(例如 Apache Commons Logging 或 Log4j)的依赖项,并且在构建本机可执行文件时遇到 ClassNotFoundException,则可以通过排除日志库并添加相应的 JBoss Logging 适配器来解决此问题。

If you are using dependencies that require logging components such as Apache Commons Logging or Log4j and are experiencing a ClassNotFoundException when building the native executable, you can resolve this by excluding the logging library and adding the corresponding JBoss Logging adapter.

有关更多详细信息,请参阅 Logging guide

For more details please refer to the Logging guide.