注:

了解反射和 GraalVM Native Image

简介

此实验室面向开发人员,希望了解有关 GraalVM Native Image 中反射功能的更多信息。

GraalVM Native Image 允许提前将 Java 应用编译为自包含的原生可执行文件。使用 GraalVM Native Image 时,应用程序在运行时所需的代码将添加到本机可执行文件中。

这些原生可执行文件具有许多重要的优势,因为它们:

许多先进的微服务框架都支持使用 GraalVM Native Image 进行提前编译,包括 Micronaut、Spring、Helidon 和 Quarkus。

此外,还有适用于 Native Image 的 Maven 和 Gradle 插件,可轻松构建、测试和运行 Java 应用,并使其成为原生可执行文件。

注意:Oracle Cloud Infrastructure (OCI) 无需额外付费即可提供 GraalVM 企业版。

估计的实验室时间:30 分钟

实验室目标

在此实验室中,您可以执行以下任务:

注意:每当您看到笔记本电脑图标时,您都需要在某个位置执行操作。注意。

# This is where we you will need to do something

您的开发环境由远程主机提供:具有 Oracle Linux 8、1 CPU 和 32GB 内存的 OCI 计算实例。
在远程主机准备就绪之前将显示 Luna Labs 桌面环境,这至多需要两分钟。

STEP 1:连接到虚拟主机并检查开发环境

通过在 Luna Desktop 环境中运行设置脚本,可以连接到远程主机。可通过“资源”选项卡使用此脚本

  1. 在桌面中,双击 Luna-Lab.html 图标。此时将打开该页面,其中显示特定于您的实验室的 Oracle Cloud Infrastructure 身份证明和信息。

  2. 将显示资源选项卡。请注意,资源标题旁边的齿轮在计算实例预配到茶云时会旋转。

  3. 预配实例时,这至多需要 2 分钟,您将在资源选项卡上看到以下内容

    Luna 资源标签

  4. 从“资源”选项卡复制设置 VS 代码环境的配置脚本。单击查看详细信息链接可显示配置。复制此项,如下面的屏幕截图中所示。

    复制配置脚本

  5. 打开终端,如下面的屏幕截图中所示:

    打开终端

  6. 将配置代码粘贴到终端中,终端将打开 VS 代码。

    粘贴终端 1

    粘贴终端 2

结束了!祝贺您,您现在已成功连接到 Oracle Cloud 中的远程主机!

STEP 2:关闭的世界假设

使用 GraalVM 提供的 native-image 工具构建独立的可执行文件与构建 Java 应用有一点不同。本机映像使用称为封闭世界假设的内容。

"Close World" 假设意味着在构建时(即 native-image 工具正在构建独立可执行文件时)必须知道(观察和分析)应用程序中可以调用的所有字节码。

在继续之前,不妨先了解一下使用 GraalVM Native Image 构建的应用的构建 / 运行模型。

  1. 将 Java 源代码编译为 Java 字节代码类
  2. 使用 native-image 工具将这些 Java 字节代码类构建到本地可执行文件中
  3. 运行本机可执行文件

但是,在步骤 2 中究竟会发生什么?

首先,native-image 工具执行分析以查看应用程序中的哪些类可访问。稍后我们将详细介绍。

其次,已找到的类(已知可安全初始化)(Automatic Initialization of Safe Classes) 已初始化。已初始化类的类数据将加载到图像堆中,然后又保存到独立可执行文件中(在文本部分中)。这是 GraalVM native-image 工具的功能之一,可用于快速启动应用。

注意:这与对象初始化不同。对象初始化在本地可执行文件的运行时进行。

我们说,我们将回到可到达性专题。如前所述,分析确定哪些类、方法和字段需要包括在独立可执行文件中。分析是静态的,它不运行代码。分析可以确定某些动态类加载和反射使用的案例(请参见),但是在有些情况下,它无法获取。

为了处理 Java 的动态功能,需要告知分析使用了反射的类或动态加载的类。

让我们看看示例。

STEP 3:使用反射的示例

假设您具有以下类 ReflectionExample.java(该副本位于目录 demo/ReflectionExample.java 中):

import java.lang.reflect.Method;

class StringReverser {
    static String reverse(String input) {
        return new StringBuilder(input).reverse().toString();
    }
}

class StringCapitalizer {
    static String capitalize(String input) {
        return input.toUpperCase();
    }
}

public class ReflectionExample {
    public static void main(String[] args) throws ReflectiveOperationException {
        String className = args[0];
        String methodName = args[1];
        String input = args[2];

        Class<?> clazz = Class.forName(className);
        Method method = clazz.getDeclaredMethod(methodName, String.class);
        Object result = method.invoke(null, input);
        System.out.println(result);
    }
}

首先,在 VS 代码中创建终端。这是从 Terminal(终端)> New Terminal(新建终端)完成的。

接下来,让我们构建代码。在 shell 中,从 VS 代码中运行以下命令:

javac ReflectionExample.java

ReflectionExample 中的 main 方法可加载其名称作为参数传入的类,一个非常动态的用例!类的第二个参数是动态加载的类中应调用的方法名称。

让我们来运行一下,看看它的作用是什么。

java ReflectionExample StringReverser reverse "hello"

正如我们所期望的那样,通过反思找到了类 StringReverser 上的方法 reverse。该方法被调用,反转了输入字符串“hello”。到目前为止还不错

好的,但是如果尝试在程序之外构建一个本机映像,会发生什么情况?我们来试一试。在 shell 中运行以下命令:

native-image --no-fallback ReflectionExample

注意:如果 native-image--no-fallback 选项无法构建独立的本机执行文件,则会导致生成失败。

现在,让我们运行生成的本机可执行文件并查看它的作用:

./reflectionexample StringReverser reverse "hello"

Exception in thread "main" java.lang.ClassNotFoundException: StringReverser
	at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
	at java.lang.Class.forName(DynamicHub.java:1214)
	at ReflectionExample.main(ReflectionExample.java:21)

这里发生了什么情况?我们的本地可执行文件似乎找不到类 StringReverser。这是怎么发生的?到现在,我想我们也许知道为什么封闭世界假设。

在执行 native-image 工具的分析期间,无法确定曾经使用过类 StringReverser。因此,它将类从生成的本机可执行文件中删除。注:通过从独立可执行文件中删除不需要的类,该工具仅通过包括已知要使用的类来收缩构建的代码。正如我们刚才所看到的,这会引起人们的反思,但幸运的是,有办法处理。

STEP 4:引入本地映像反射配置

可以通过特殊配置文件告知 native-image 构建工具有关反射实例。这些文件在 JSON 中写入,可以通过使用标志传递到 native-image 工具。

因此,我们可以将哪些其他类型的配置信息传递到 native-image 构建工具?该工具当前支持读取包含以下各项详细信息的文件:

我们只研究如何在此实验室中进行反思,因此我们会重点关注这一点。

以下是这些文件的外观示例(从此处获取):

[
  {
    "name" : "java.lang.Class",
    "queryAllDeclaredConstructors" : true,
    "queryAllPublicConstructors" : true,
    "queryAllDeclaredMethods" : true,
    "queryAllPublicMethods" : true,
    "allDeclaredClasses" : true,
    "allPublicClasses" : true
  },
  {
    "name" : "java.lang.String",
    "fields" : [
      { "name" : "value" },
      { "name" : "hash" }
    ],
    "methods" : [
      { "name" : "<init>", "parameterTypes" : [] },
      { "name" : "<init>", "parameterTypes" : ["char[]"] },
      { "name" : "charAt" },
      { "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
    ]
  },
  {
    "name" : "java.lang.String$CaseInsensitiveComparator",
    "queriedMethods" : [
      { "name" : "compare" }
    ]
  }
]

由此可见,需要配置通过反射 API 访问的类和方法。我们可以手动执行此操作,但生成这些配置文件的最方便的方法是使用辅助配置 javaagent

STEP 5:本机映像,辅助配置:输入 Java 代理

可以从头开始编写完整的反射配置文件,但是 GraalVM Java 运行时会提供一个 java 跟踪代理 (javaagent),该代理将在您运行应用程序时自动为您生成该文件。

我们来试一试。

在启用了跟踪代理的情况下运行应用程序。在 shell 中,运行以下命令:

# Note: the tracing agent parameter must come before the classpath and jar params on the command ine
java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"

跟踪代理配置

让我们看看创建的配置:

cat META-INF/native-image/reflect-config.json
[
    {
    "name":"StringReverser",
    "methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
    }
]

您可以多次运行此进程,如果指定了 native-image-agent=config-merge-dir,则会合并运行,如下例所示:

java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"

构建独立可执行文件现在将利用提供的配置。我们来构建:

native-image --no-fallback ReflectionExample

让我们看看效果是否更好:

./reflectionexample StringReverser reverse "hello"

确实!

结论

使用 GraalVM Native Image 构建独立可执行文件依赖于已关闭的世界假设,即我们需要在构建独立可执行文件时提前知道代码中可能出现的任何反射案例。

GraalVM 平台提供了一种在使用反射时指定 native-image 构建工具的方法。注:对于某些简单情况,native-image 工具可以自行发现这些内容。

GraalVM 平台还提供了一种通过 Java 跟踪代理搜索反射和其他动态行为的方法,并且可以自动生成 native-image 工具所需的配置文件。

使用跟踪代理时,应注意以下几点:

我们希望您已学习过本教程,并了解在使用 Native Image 时如何处理反思。

了解更多

更多学习资源

docs.oracle.com/learn 上浏览其他实验室,或者在 Oracle Learning YouTube 渠道上访问更多免费学习内容。此外,访问 education.oracle.com/learning-explorer 以成为 Oracle Learning Explorer。

有关产品文档,请访问 Oracle 帮助中心