高階原生映象主題
巢狀配置屬性
Spring AOT 引擎會自動為配置屬性建立反射提示。但是,非內部類的巢狀配置屬性**必須**使用 @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;
}
}
其中 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 生成配置屬性。如果 nested 欄位沒有 @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
)
| 在所有情況下,請使用公共的 getter 和 setter,否則屬性將無法繫結。 |
轉換 Spring Boot 可執行 Jar
只要 jar 包含 AOT 生成的資源,就可以將 Spring Boot 可執行 jar 轉換為原生映象。這在許多情況下都很有用,包括:
-
你可以保留常規的 JVM 管道,並在 CI/CD 平臺上將 JVM 應用程式轉換為原生映象。
-
由於
native-image不支援交叉編譯,你可以保留一個與作業系統無關的部署工件,以後再將其轉換為不同的作業系統架構。
你可以使用 Cloud Native Buildpacks 或 GraalVM 附帶的 native-image 工具將 Spring Boot 可執行 jar 轉換為原生映象。
| 你的可執行 jar 必須包含 AOT 生成的資源,例如生成的類和 JSON 提示檔案。 |
使用 Buildpacks
Spring Boot 應用程式通常透過 Maven (mvn spring-boot:build-image) 或 Gradle (gradle bootBuildImage) 整合使用 Cloud Native Buildpacks。但是,你也可以使用 pack 將 AOT 處理過的 Spring Boot 可執行 jar 轉換為原生容器映象。
| 你必須使用至少 JDK 25 構建應用程式,因為 Buildpacks 使用與用於編譯的 Java 版本相同的 GraalVM native-image 版本。 |
首先,請確保 Docker 守護程序可用(有關詳細資訊,請參閱 獲取 Docker)。如果你在 Linux 上,請將其配置為允許非 root 使用者。
你還需要按照 buildpacks.io 上的安裝指南 安裝 pack。
假設一個 AOT 處理過的 Spring Boot 可執行 jar (myproject-0.0.1-SNAPSHOT.jar) 位於 target 目錄中,執行
$ 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 啟動應用程式
$ 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! 等下載管理器。
假設一個 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 標誌不接受萬用字元。你需要確保所有 jar 都已列出(上面的命令使用 find 和 tr 來實現此目的)。 |
使用跟蹤代理
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 API。建立一個實現 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);
}
}
然後,你可以在任何 @Configuration 類(例如你的 @SpringBootApplication 註解的應用程式類)上使用 @ImportRuntimeHints 來啟用這些提示。
如果你有需要繫結的類(主要在序列化或反序列化 JSON 時需要),你可以在任何 bean 上使用 @RegisterReflectionForBinding。大多數提示都是自動推斷的,例如當從 @RestController 方法接受或返回資料時。但是當你直接使用 WebClient、RestClient 或 RestTemplate 時,你可能需要使用 @RegisterReflectionForBinding。
測試自定義提示
RuntimeHintsPredicates API 可用於測試你的提示。該 API 提供了構建 Predicate 的方法,該 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 本身不包含第三方庫的提示,而是依賴可達性元資料專案。
如果你在為 Spring Boot 應用程式生成原生映象時遇到問題,請檢視 Spring Boot Wiki 的 Spring Boot 與 GraalVM 頁面。你還可以向 GitHub 上的 spring-aot-smoke-tests 專案提交問題,該專案用於確認常見的應用程式型別是否按預期工作。
如果你發現某個庫不適用於 GraalVM,請在可達性元資料專案上提出問題。