此版本仍在开发中,尚未视为稳定版。如需最新稳定版本,请使用 Spring Boot 4.0.4spring-doc.cadn.net.cn

高级原生镜像主题

嵌套配置属性

反射提示由 Spring 预编译引擎自动为配置属性创建。 然而,非内部类的嵌套配置属性必须使用 @NestedConfigurationProperty 进行注解,否则它们将无法被检测到且不可绑定。spring-doc.cadn.net.cn

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

@ConfigurationProperties("my.properties")
public class MyProperties {

	private String name;

	@NestedConfigurationProperty
	private final Nested nested = new Nested();

	// getters / setters...

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Nested getNested() {
		return this.nested;
	}

}

其中 Nested 为:spring-doc.cadn.net.cn

public class Nested {

	private int number;

	// getters / setters...

	public int getNumber() {
		return this.number;
	}

	public void setNumber(int number) {
		this.number = number;
	}

}
class Nested {

	var number: Int = 0
}

上述示例为 my.properties.namemy.properties.nested.number 生成了配置属性。 如果 nested 字段上没有 @NestedConfigurationProperty 注解,则 my.properties.nested.number 属性将无法在原生镜像中进行绑定。 您也可以注解 getter 方法。spring-doc.cadn.net.cn

使用构造函数绑定时,您必须使用 @NestedConfigurationProperty 注解该字段:spring-doc.cadn.net.cn

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

@ConfigurationProperties("my.properties")
public class MyPropertiesCtor {

	private final String name;

	@NestedConfigurationProperty
	private final Nested nested;

	public MyPropertiesCtor(String name, Nested nested) {
		this.name = name;
		this.nested = nested;
	}

	// getters / setters...

	public String getName() {
		return this.name;
	}

	public Nested getNested() {
		return this.nested;
	}

}

使用记录时,您必须使用 @NestedConfigurationProperty 注解参数:spring-doc.cadn.net.cn

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

@ConfigurationProperties("my.properties")
public record MyPropertiesRecord(String name, @NestedConfigurationProperty Nested nested) {

}

使用 Kotlin 时,您需要使用 @NestedConfigurationProperty 注解数据类的参数:spring-doc.cadn.net.cn

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.NestedConfigurationProperty

@ConfigurationProperties("my.properties")
data class MyPropertiesKotlin(
	val name: String,
	@NestedConfigurationProperty val nested: Nested
)
在所有情况下请使用公共的 getter 和 setter 方法,否则属性将无法被绑定。

转换 Spring Boot 可执行 Jar

只要 Spring Boot 可执行 jar 包含 AOT 生成的资源,就可以将其转换为原生镜像。 这在许多场景下都非常有用,例如:spring-doc.cadn.net.cn

你可以使用 Cloud Native Buildpacks,或者使用 GraalVM 自带的 native-image 工具,将 Spring Boot 可执行 JAR 转换为原生镜像。spring-doc.cadn.net.cn

您的可执行 JAR 必须包含 AOT 生成的资源,例如生成的类和 JSON 提示文件。

使用 Buildpacks

Spring Boot 应用程序通常通过 Maven (mvn spring-boot:build-image) 或 Gradle (gradle bootBuildImage) 集成使用云原生构建包(Cloud Native Buildpacks)。 不过,您也可以使用 pack 将经过 AOT 处理的 Spring Boot 可执行 JAR 转换为原生容器镜像。spring-doc.cadn.net.cn

首先,请确保已安装 Docker 守护进程(更多详情请参见获取 Docker)。 如果您使用的是 Linux 系统,请配置 Docker 以允许非 root 用户spring-doc.cadn.net.cn

你还需要按照buildpacks.io 上的安装指南来安装https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/#installspring-doc.cadn.net.cn

假设一个经过 AOT 处理的 Spring Boot 可执行 JAR 文件(构建为 myproject-0.0.1-SNAPSHOT.jar)位于 target 目录中,请运行:spring-doc.cadn.net.cn

$ pack build --builder paketobuildpacks/builder-noble-java-tiny \
    --path target/myproject-0.0.1-SNAPSHOT.jar \
    --env 'BP_NATIVE_IMAGE=true' \
    my-application:0.0.1-SNAPSHOT
你无需在本地安装 GraalVM 即可通过这种方式生成镜像。

一旦 pack 完成后,你可以使用 docker run 启动该应用程序:spring-doc.cadn.net.cn

$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT

使用 GraalVM native-image

将经过 AOT 处理的 Spring Boot 可执行 JAR 转换为原生可执行文件的另一种方法是使用 GraalVM 的 native-image 工具。 要实现这一点,您需要在机器上安装一个 GraalVM 发行版。 您可以手动从Liberica Native Image Kit 页面下载,也可以使用 SDKMAN! 等下载管理器。spring-doc.cadn.net.cn

假设一个经过 AOT 处理的 Spring Boot 可执行 JAR 文件(构建为 myproject-0.0.1-SNAPSHOT.jar)位于 target 目录中,请运行:spring-doc.cadn.net.cn

$ rm -rf target/native
$ mkdir -p target/native
$ cd target/native
$ jar -xvf ../myproject-0.0.1-SNAPSHOT.jar
$ native-image -H:Name=myproject @META-INF/native-image/argfile -cp .:BOOT-INF/classes:`find BOOT-INF/lib | tr '\n' ':'`
$ mv myproject ../
这些命令适用于 Linux 或 macOS 系统,但您需要针对 Windows 系统进行相应调整。
@META-INF/native-image/argfile 可能不会被打包到您的 JAR 文件中。 仅在需要可达性元数据覆盖时才会包含该文件。
native-image-cp 参数不接受通配符。 你需要确保列出所有 JAR 文件(上述命令使用了 findtr 来实现这一点)。

使用追踪代理

GraalVM 原生镜像追踪代理允许你在 JVM 上拦截反射、资源或代理的使用,从而生成相关的提示(hints)。 Spring 应该能自动生成其中大部分提示,但你可以使用追踪代理快速识别缺失的条目。spring-doc.cadn.net.cn

在使用代理为原生镜像生成提示时,有几种方法:spring-doc.cadn.net.cn

第一种选项在识别 Spring 无法识别某个库或模式时所缺失的提示方面很有用。spring-doc.cadn.net.cn

第二种选项在可重复的设置中听起来更具吸引力,但默认情况下,生成的提示(hints)会包含测试基础设施所需的所有内容。 其中一些内容在应用程序实际运行时是不必要的。 为了解决这个问题,该代理(agent)支持一个访问过滤器(access-filter)文件,可用于将某些数据从生成的输出中排除。spring-doc.cadn.net.cn

直接启动应用程序

使用以下命令启动应用程序,并附加原生镜像追踪代理:spring-doc.cadn.net.cn

$ java -Dspring.aot.enabled=true \
    -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \
    -jar target/myproject-0.0.1-SNAPSHOT.jar

现在,您可以执行您希望为其生成提示的代码路径,然后使用 ctrl-c 停止应用程序。spring-doc.cadn.net.cn

在应用程序关闭时,原生镜像追踪代理会将提示文件写入指定的配置输出目录。 您可以手动检查这些文件,也可以将它们作为原生镜像构建过程的输入。 若要将其用作输入,请将这些文件复制到 src/main/resources/META-INF/native-image/ 目录中。 下次构建原生镜像时,GraalVM 将会考虑这些文件。spring-doc.cadn.net.cn

可以对原生镜像追踪代理(native image tracing agent)设置更高级的选项,例如按调用方类(caller classes)过滤所记录的提示(hints)等。 如需进一步了解,请参阅官方文档spring-doc.cadn.net.cn

自定义提示

如果您需要提供自己的反射、资源、序列化、代理使用等提示,可以使用 RuntimeHintsRegistrar API。 创建一个实现 RuntimeHintsRegistrar 接口的类,然后对提供的 RuntimeHints 实例进行适当的调用:spring-doc.cadn.net.cn

import java.lang.reflect.Method;

import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.util.ReflectionUtils;

public class MyRuntimeHints implements RuntimeHintsRegistrar {

	@Override
	public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
		// Register method for reflection
		Method method = ReflectionUtils.findMethod(MyClass.class, "sayHello", String.class);
		hints.reflection().registerMethod(method, ExecutableMode.INVOKE);

		// Register resources
		hints.resources().registerPattern("my-resource.txt");

		// Register serialization
		hints.serialization().registerType(MySerializableClass.class);

		// Register proxy
		hints.proxies().registerJdkProxy(MyInterface.class);
	}

}

然后,您可以在任何 @Configuration 类(例如您的 @SpringBootApplication 注解的应用程序类)上使用 @ImportRuntimeHints 来激活这些提示。spring-doc.cadn.net.cn

如果您有需要绑定的类(主要在序列化或反序列化 JSON 时需要),可以在任何 Bean 上使用 @RegisterReflectionForBinding。 大多数提示会自动推断,例如在从 @RestController 方法接收或返回数据时。 但是,当您直接使用 WebClientRestClientRestTemplate 时,可能需要使用 @RegisterReflectionForBindingspring-doc.cadn.net.cn

测试自定义提示

RuntimeHintsPredicates API 可用于测试您的提示。 该 API 提供了构建 Predicate 的方法,该方法可用于测试 RuntimeHints 实例。spring-doc.cadn.net.cn

如果你使用的是 AssertJ,你的测试代码将如下所示:spring-doc.cadn.net.cn

import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.docs.packaging.nativeimage.advanced.customhints.MyRuntimeHints;

import static org.assertj.core.api.Assertions.assertThat;

class MyRuntimeHintsTests {

	@Test
	void shouldRegisterHints() {
		RuntimeHints hints = new RuntimeHints();
		new MyRuntimeHints().registerHints(hints, getClass().getClassLoader());
		assertThat(RuntimeHintsPredicates.resource().forResource("my-resource.txt")).accepts(hints);
	}

}

静态提供提示

如果需要,可以在一个或多个 GraalVM JSON 提示文件中静态地提供自定义提示。 这些文件应放置在 src/main/resources/ 目录下的 META-INF/native-image/*/*/ 子目录中。 在 AOT 处理过程中生成的提示 会被写入名为 META-INF/native-image/{groupId}/{artifactId}/ 的目录中。 请将您的静态提示文件放置在不与该位置冲突的目录中,例如 META-INF/native-image/{groupId}/{artifactId}-additional-hints/spring-doc.cadn.net.cn

已知限制

GraalVM 原生镜像是一项不断发展的技术,并非所有库都提供支持。 GraalVM 社区正在通过为尚未自带元数据的项目提供可达性元数据(reachability metadata)来提供帮助。 Spring 本身并不包含第三方库的提示信息,而是依赖于可达性元数据项目。spring-doc.cadn.net.cn

如果你在为 Spring Boot 应用程序生成原生镜像时遇到问题,请查阅 Spring Boot Wiki 中的 Spring Boot 与 GraalVM 页面。 你也可以向 GitHub 上的 spring-aot-smoke-tests 项目提交问题,该项目用于验证常见类型的应用程序是否按预期正常工作。spring-doc.cadn.net.cn

如果您发现某个库无法与 GraalVM 配合使用,请在可达性元数据项目中提交问题。spring-doc.cadn.net.cn