Writing Native Applications Tips

本指南包含各种提示和技巧,用来解决以 native 可执行文件运行 Java 应用程序时可能出现的问题。

注意,不同的两种情况我们可能需要区分适用的解决办法:

  • 在应用程序中,您将依靠通过调整你的 pom.xml 来配置 native-image;

  • 在 extension 的情况下,Quarkus 为简化所有这一切提供了大量的基础设施。

请根据您的情况参阅相应的章节。

应用支持 native

GraalVM 设置了一些限制,应用程序改成 native 可执行程序可能需要一些调整。

包括资源

默认情况下,当构建 native 可执行文件时, GraalVM 不会包含在类路径上的任何资源到它创建的 native 可执行文件。 需要指明要成为 native 可执行文件部分的资源。

Quarkus 自动包括 META-INF/resources (网页资源) 中存在的资源,但在这个目录之外,你要自己处理。

若要在 native 可执行文件中包含更多资源,请创建一个 resources-config.json (最常见的位置在 src/main/resources) JSON 文件中定义了哪些资源应该包括:

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

pattern 是有效的 Java 正则表达式。 我们在这里将所有 XML 文件和 JSON 文件都包含在 native 可执行文件中。

您可以在 GraalVM 文档 中找到更多关于此主题的信息。

最后是通过 application.properties 中添加适当的配置,将配置文件发布到 native-image 可执行文件:

quarkus.native.additional-build-args =-H:ResourceConfigurationFiles=resources-config.json

上一个代码片段中,我们能够简单地使用 resources-config.json 而不是仅仅因为它被添加到 src/main/resources 中而指定文件的整个路径。 如果该文件被添加到另一个目录,则必须手动指定正确的文件路径。

多个选项可以用逗号分隔。 例如,人们可以使用:

quarkus.native.additional-build-args =\
    -H:ResourceConfigurationFiles=resources-config.json,\
    -H:ReflectionConfigurationFiles=reflection-config.json

in order to ensure that various resources are included and additional reflection is registered.

如果因为某些原因,不希望将上述配置添加到 application.properties,可以配置构建工具来执行同样的操作。

当使用 Maven时,我们可以使用以下配置:

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
            <quarkus.native.additional-build-args>-H:ResourceConfigurationFiles=resources-config.json</quarkus.native.additional-build-args>
        </properties>
    </profile>
</profiles>

当使用 Gradle, 如此配置 buildNative 任务:

buildNative {
    additionalBuildArgs = [
            '-H:ResourceConfigurationFiles=resources-config.json'
    ]
}

反射注册

When building a native executable, GraalVM operates with a closed world assumption. 它分析调用树并删除所有没有直接使用的类/方法/字段。

通过反射使用的元素并不是调用树的一部分,因此它们被裁剪掉(换句话说就是没有直接调用)。 要将这些元素包含在您的 native 可执行文件中,您需要明确注册它们以进行反射。

这是一个非常常见的情况,因为 JSON 库通常使用反射将对象序列化为 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();
        }
    }

如果我们使用上面的代码,会在使用 native 可执行文件时出现如下异常:

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

或者如果您正在使用 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 结果却直接完全空白。

解决这类问题有两种不同的方式。

使用 @RegisterForReflection 注解

最简单的方法是使用 @RegisterForReflection 注解注册一个类用于反射:

@RegisterForReflection
public class MyClass {
}

使用配置文件

显然,如果该类位于第三方jar,则无法添加 @RegisterForReflection

在这种情况下,您可以使用配置文件来注册类用于反射。

例如,为了注册 com.acme.MyClass 类的所有方法进行反射,我们创建了 reflection-config.json (最常见的位置在 src/main/resources)

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

欲了解此文件格式的更多详情,请访问https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md[GraalVM 文档].

最后通过在 application.properties 中添加适当的配置,将配置文件发布到 native-image 可执行文件:

quarkus.native.additional-build-args =-H:ReflectionConfigurationFiles=reflection-config.json

上一个代码片段中,我们能够简单地使用 reflection-config.json 而不是仅仅因为它被添加到 src/main/resources 中而指定文件的整个路径。 如果该文件被添加到另一个目录,则必须手动指定正确的文件路径。

多个选项可以用逗号分隔。 例如,人们可以使用:

quarkus.native.additional-build-args =\
    -H:ResourceConfigurationFiles=resources-config.json,\
    -H:ReflectionConfigurationFiles=reflection-config.json

in order to ensure that various resources are included and additional reflection is registered.

如果因为某些原因,不希望将上述配置添加到 application.properties,可以配置构建工具来执行同样的操作。

当使用 Maven时,我们可以使用以下配置:

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
            <quarkus.native.additional-build-args>-H:ReflectionConfigurationFiles=reflection-config.json</quarkus.native.additional-build-args>
        </properties>
    </profile>
</profiles>

当使用 Gradle, 如此配置 buildNative 任务:

buildNative {
    additionalBuildArgs = [
            '-H:ReflectionConfigurationFiles=reflection-config.json'
    ]
}

延迟类初始化

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

有时,某些类的初始化是在静态区块中完成的,需要推迟到运行时间。 通常省略这种配置会导致运行时异常,比如:

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):

如果你需要推迟一个类的初始化, 你可以使用 --initialize-at-run-time=<package or class> 配置块.

It should be added to the native-image configuration using an <additionalBuildArg> as shown in the examples above.

您可以在 GraalVM 文档找到更多关于所有这一切的信息。

当多个类或包需要通过 quarkus.native.additional-build-args 配置属性指定时,, 符号需要跳过。 关于这的一个例子如下:

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

管理代理类

在编写 native 应用程序时,您需要在镜像构建时定义代理类,指定它们执行的接口列表。

在这种情况下,您可能遇到的错误是:

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.

解决这个问题需要添加 -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> 选项并提供动态代理配置文件。 您可以在 GraalVM 文档中找到有关此文件格式的所有信息。

在 Quarkus extenion 中支持原生

在 Quarkus 扩展中支持本土甚至更容易,因为Quarkus 提供了许多工具来简化所有这一切。

这里描述的一切都只能在 Quarkus 扩展中运行,它不会在应用程序中运行。

注册反射

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

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

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反射的更多信息可以找到 here

使用 @RegisterForReflection 替代版

至于应用程序,您也可以使用 @RegisterForReflection 注解,如果该类在您的扩展而不是第三方jar中。

包括资源

在扩展的情况下,Quarkus 允许 extension 作者指定一个 NativeImageResourceBuildItem ,从而消除了JSON 配置文件的需要:

public class ResourcesProcessor {

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

}

关于 native 可执行文件中GraalVM资源处理的更多信息,请访问https://github.com/oracle/graal/blob/master/substratevm/SourcesURCES.md[GraalVM文档]。

延迟类初始化

Quarkus 通过允许扩展作者简单注册一个 RuntimeInitializedClassBuildItem 简化了事务。 这样做的一个简单例子可以是:

public class S3Processor {

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

}

使用这种构建意味着`--initialize-at-run-time`选项将自动添加到 `native-image`命令行中。

更多关于 --initialize-at-run-time 的信息,请访问 GraalVM 文档

管理代理类

非常相似,Quarkus 允许扩展作者注册一个“NativeImageProxyDefinitionBuildItem”。 这样做的一个例子是:

public class S3Processor {

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

}

使用这种构建意味着`-H:DynamicProxyConfigurationResources`选项将自动添加到 `native-image`命令行。

欲了解更多关于代理类的信息,您可以改为 GraalVM 文档

quarkus.pro 是基于 quarkus.io 的非官方中文翻译站 ,最后更新 2020/04 。
沪ICP备19006215号-8
QQ交流群:1055930959
微信群: