高级本机映像主题
嵌套配置属性
Spring 预配置引擎会自动为 configuration 属性创建反射提示。
但是,不是内部类的嵌套配置属性必须使用@NestedConfigurationProperty
,否则它们将不会被检测到且不可绑定。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@ConfigurationProperties(prefix = "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
是:
public class Nested {
private int number;
// getters / setters...
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
}
上面的示例生成了my.properties.name
和my.properties.nested.number
.
如果没有@NestedConfigurationProperty
注解nested
字段、my.properties.nested.number
属性在本机映像中不可绑定。
您还可以对 getter 方法进行 Comments。
使用构造函数绑定时,您必须使用@NestedConfigurationProperty
:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@ConfigurationProperties(prefix = "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(prefix = "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(prefix = "my.properties")
data class MyPropertiesKotlin(
val name: String,
@NestedConfigurationProperty val nested: Nested
)
请在所有情况下使用 public getter 和 setter,否则属性将不可绑定。 |
转换 Spring Boot 可执行 Jar
只要 Spring Boot 可执行 jar 包含 AOT 生成的资产,就可以将 Spring Boot 可执行 jar 转换为本机映像。 这可能很有用,原因有很多,包括:
-
您可以保留常规的 JVM 管道,并将 JVM 应用程序转换为 CI/CD 平台上的本机映像。
-
如
native-image
不支持交叉编译,您可以保留一个 OS 中立的部署工件,稍后将其转换为不同的 OS 架构。
您可以使用 Cloud Native Buildpacks 或使用native-image
GraalVM 附带的工具。
您的可执行 jar 必须包含 AOT 生成的资产,例如生成的类和 JSON 提示文件。 |
使用 Buildpack
Spring Boot 应用程序通常通过 Maven(mvn spring-boot:build-image
) 或 Gradle (gradle bootBuildImage
) 集成。
但是,您也可以使用pack
将 AOT 处理的 Spring Boot 可执行 jar 转换为本机容器映像。
首先,确保 Docker 守护程序可用(有关更多详细信息,请参阅获取 Docker)。如果您使用的是 Linux,请将其配置为允许非 root 用户。
您还需要安装pack
按照 buildpacks.io.
假设 AOT 处理的 Spring Boot 可执行 jar 构建为myproject-0.0.1-SNAPSHOT.jar
位于target
目录中,运行:
$ pack build --builder paketobuildpacks/builder-jammy-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
:
$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT
使用 GraalVM 本机映像
将 AOT 处理的 Spring Boot 可执行 jar 转换为本机可执行文件的另一种选择是使用 GraalVMnative-image
工具。
为此,您的机器上需要一个 GraalVM 发行版。
您可以在 Liberica Native Image Kit 页面上手动下载它,也可以使用 SDKMAN! 等下载管理器。
假设 AOT 处理的 Spring Boot 可执行 jar 构建为myproject-0.0.1-SNAPSHOT.jar
位于target
目录中,运行:
$ 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 flag 不接受通配符。
您需要确保列出所有 jar(上面的命令使用find 和tr 执行此作)。 |
使用跟踪代理
GraalVM 原生图像跟踪代理允许您拦截 JVM 上的反射、资源或代理使用情况,以生成相关提示。 Spring 应该自动生成大部分这些提示,但是可以使用跟踪代理来快速识别缺少的条目。
使用代理为本机映像生成提示时,有几种方法:
-
直接启动应用程序并执行它。
-
运行应用程序测试以执行应用程序。
第一个选项对于当 Spring 无法识别库或模式时识别缺少的提示很有趣。
第二个选项听起来更吸引可重复的设置,但默认情况下,生成的 Importing 将包括测试基础结构所需的任何内容。 当应用程序真正运行时,其中一些是不必要的。 为了解决这个问题,代理支持一个访问过滤器文件,这将导致某些数据被排除在生成的输出之外。
直接启动应用程序
使用以下命令启动附加了本机图像跟踪代理的应用程序:
$ 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
.
在应用程序关闭时,本机映像跟踪代理会将提示文件写入给定的 config 输出目录。
您可以手动检查这些文件,也可以将它们用作本机映像构建过程的输入。
要将它们用作输入,请将它们复制到src/main/resources/META-INF/native-image/
目录。
下次构建原生镜像时,GraalVM 将考虑这些文件。
可以在本机图像跟踪代理上设置更高级的选项,例如,按调用方类过滤记录的提示等。 如需进一步阅读,请参阅官方文档。
自定义提示
如果需要提供自己的反射、资源、序列化、代理使用等提示,可以使用RuntimeHintsRegistrar
应用程序接口。
创建一个实现RuntimeHintsRegistrar
接口,然后对提供的RuntimeHints
实例:
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
annotated application 类)来激活这些提示。
如果您有需要绑定的类(主要是在序列化或反序列化 JSON 时需要),则可以使用@RegisterReflectionForBinding
在任何 bean 上。
大多数提示都是自动推断的,例如,当接受或返回来自@RestController
方法。
但是,当您使用WebClient
,RestClient
或RestTemplate
直接,您可能需要使用@RegisterReflectionForBinding
.
测试自定义提示
这RuntimeHintsPredicates
API 可用于测试您的提示。
API 提供了构建Predicate
可用于测试RuntimeHints
实例。
如果您使用的是 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/
在META-INF/native-image/*/*/
目录。
AOT 处理过程中生成的提示将写入名为META-INF/native-image/{groupId}/{artifactId}/
.
将静态提示文件放在不与此位置冲突的目录中,例如META-INF/native-image/{groupId}/{artifactId}-additional-hints/
.
已知限制
GraalVM 原生映像是一项不断发展的技术,并非所有库都提供支持。 GraalVM 社区通过为尚未发布自己的项目提供可访问性元数据来提供帮助。 Spring 本身不包含 3rd 方库的提示,而是依赖于可访问性元数据项目。
如果您在为 Spring Boot 应用程序生成原生映像时遇到问题,请查看 Spring Boot wiki 的 Spring Boot with GraalVM 页面。 您还可以将问题贡献给 GitHub 上的 spring-aot-smoke-tests 项目,该项目用于确认常见应用程序类型是否按预期工作。
如果您发现某个库无法与 GraalVM 一起使用,请在可访问性元数据项目上提出问题。