高级原生图像主题
嵌套配置属性
Spring 预置引擎会自动为配置属性创建反射提示。
然而,非内类的嵌套配置属性必须注释为@NestedConfigurationProperty否则它们不会被检测到,也无法绑定。
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;
}
}
哪里嵌 套是:
-
Java
-
Kotlin
public class Nested {
private int number;
// getters / setters...
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
}
class Nested {
}
上述示例产生了 的配置性质my.properties.name和my.properties.nested.number.
没有@NestedConfigurationProperty关于嵌 套字段,my.properties.nested.number属性在原生图片中无法绑定。
你也可以对getter方法进行注释。
使用构造函数绑定时,你必须对字段进行注释@NestedConfigurationProperty:
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:
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:
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
)
| 请在所有情况下使用公开的获取者和设定者,否则属性将无法绑定。 |
转换 Spring Boot 可执行 jar
只要jar包含AOT生成的资产,就可以将Spring Boot的可执行文件jar转换为原生镜像。 这有多种用途,包括:
-
你可以保留常规的JVM流水线,把JVM应用转为CI/CD平台上的原生镜像。
-
如
原生图像不支持交叉编译,你可以保留一个作系统中立的部署工件,之后再转换到不同的作系统架构。
你可以用Cloud Native Buildpacks将Spring Boot的可执行文件jar转换成原生镜像,或者使用原生图像该工具随GraalVM一同发布。
| 你的可执行 jar 必须包含 AOT 生成的资源,比如生成的类和 JSON 提示文件。 |
使用 Buildpack
Spring Boot 应用程序通常通过 Maven 使用 Cloud Native 构建包(MVN Spring-boot:build-image)或Gradle(gradle bootBuildImage)整合。
不过,你也可以使用包将AOT处理的Spring Boot可执行文件jar转换为本地容器镜像。
| 你必须至少用 JDK 25 构建你的应用,因为构建包使用的是和编译用的 Java 版本相同的 GraalVM 原生映像版本。 |
首先,确保有 Docker 守护进程可用(详情请参见获取 Docker )。如果你用的是Linux,可以设置成允许非root用户访问。
你还需要安装包通过按照 buildpacks.io 上的安装指南作。
假设AOT处理了Spring Boot可执行文件jar,构建为myproject-0.0.1-SNAPSHOT.jar属于目标目录,运行:
$ 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 安装就能以这种方式生成映像。 |
一次包完成后,你可以用以下方式启动应用Docker 运行:
$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT
使用 GraalVM 原生映像
将AOT处理的Spring Boot可执行文件jar转换为原生可执行文件的另一种方法是使用GraalVM原生图像工具。
要实现这一点,你需要在你的机器上安装 GraalVM 发行版。
你可以在Liberica原生图像套件页面手动下载,或者使用像SDKMAN!这样的下载管理器。
假设AOT处理了Spring Boot可执行文件jar,构建为myproject-0.0.1-SNAPSHOT.jar属于目标目录,运行:
$ 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可能不会装在你的罐子里。
只有在需要覆盖可达元数据时才会包含此功能。 |
这原生图像 -cp旗帜不接受万能牌。
你需要确保所有罐子都被列出(上面的命令是找到和TR做到这一点)。 |
使用 Tracing Agent
GraalVM原生图像追踪代理允许你拦截JVM上的反射、资源或代理使用情况,以生成相关的提示。 Spring 应该会自动生成大部分线索,但追踪工具可以快速识别缺失的条目。
在使用代理生成原生图像提示时,有几种方法:
-
直接启动应用并运行它。
-
运行应用程序测试以锻炼应用程序。
第一种方法很有意思,可以当Spring无法识别某个库或图案时,识别缺失的提示。
第二个选项听起来更适合重复设置,但默认生成的提示会包含测试基础设施所需的所有内容。 当应用真正运行时,其中一些将不再必要。 为解决这个问题,代理支持一个访问过滤文件,该文件会将某些数据排除在生成输出中。
直接启动应用
请使用以下命令启动带有本地图像追踪代理的应用程序:
$ 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.
应用关闭时,本地图像追踪代理会将提示文件写入给定的配置输出目录。
你可以手动检查这些文件,或者将它们作为本地图像构建过程的输入。
要用作输入,复制到src/main/resources/META-INF/native-image/目录。
下次你构建原生镜像时,GraalVM 会考虑这些文件。
本地图像追踪代理上还可以设置更高级的选项,例如按调用类过滤录制提示等。 如需进一步阅读,请参阅官方文档。
自定义提示
如果你需要为反射、资源、序列化、代理使用等提供自己的提示,你可以使用RuntimeHintsRegistrar应用程序接口。
创建一个实现RuntimeHintsRegistrar然后对所提供的 进行适当调用。运行提示实例:
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);
}
}
然后你可以使用@ImportRuntimeHints在任意@Configuration类(例如你的@SpringBootApplication注释应用类)来激活这些提示。
如果你有需要绑定的类(主要是序列化或反序列化 JSON 时需要),你可以用@RegisterReflectionForBinding任何一颗豆子。
大多数提示都是自动推断的,例如在接受或返回来自@RestController方法。
但当你与Web客户端,Rest客户端或Rest模板直接来说,你可能需要使用@RegisterReflectionForBinding.
测试自定义提示
如果你用的是 AssertJ,你的测试会是这样的:
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/在元-INF/原生图像/*/*/目录。
AOT处理过程中生成的提示会写入一个名为META-INF/native-image/{groupId}/{artifactId}/.
将静态提示文件放置在与该位置不冲突的目录中,例如META-INF/native-image/{groupId}/{artifactId}-additional-hints/.
已知的局限性
GraalVM 原生镜像是一项不断发展的技术,并非所有库都支持。 GraalVM 社区通过为尚未发布的项目提供可达元数据来提供帮助。 Spring本身不包含第三方库的提示,而是依赖可达性元数据项目。
如果您在为 Spring Boot 应用程序生成原生镜像时遇到问题,请查看 Spring Boot 维基中的 Spring Boot with GraalVM 页面。 你也可以向GitHub上的spring-aot-smoke-tests项目贡献问题,该项目用于确认常见应用类型是否按预期运行。
如果你发现某个库不支持 GraalVM,请在可达性元数据项目中提出问题。