進階原生映像檔主題
巢狀組態屬性
Spring 預先 (AOT) 引擎會自動為組態屬性建立反射提示。然而,非內部類別的巢狀組態屬性**必須**使用 `@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` 的組態屬性。如果 `nested` 欄位沒有 `@NestedConfigurationProperty` 註釋,則 `my.properties.nested.number` 屬性在原生映像檔中將無法繫結。
使用建構子繫結時,您必須使用 `@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
)
請在所有情況下使用公開的 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 檔轉換為原生容器映像檔。
首先,請確保 Docker daemon 可用(詳情請參閱 取得 Docker)。如果您使用的是 Linux 系統,請將其設定為允許非 root 使用者。
您還需要按照 buildpacks.io 上的安裝指南安裝 pack
。
假設一個 AOT 處理過的 Spring Boot 可執行 jar 檔被建置為 myproject-0.0.1-SNAPSHOT.jar
並位於 target
目錄中,請執行
$ pack build --builder paketobuildpacks/builder-jammy-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 系統進行調整。 |
您的 jar 檔中可能沒有包含 @META-INF/native-image/argfile 。只有在需要覆寫可達性中繼資料時才會包含它。 |
native-image 的 -cp 旗標不接受萬用字元。您需要確保列出所有 jar 檔(上述指令使用 find 和 tr 來執行此操作)。 |
使用追蹤代理程式
GraalVM native image 追蹤代理程式 允許您攔截 JVM 上的反射、資源或代理使用情況,以便產生相關提示。Spring 應該會自動產生大部分的提示,但可以使用追蹤代理程式快速識別遺漏的項目。
使用代理程式產生 native image 提示時,有幾種方法
-
直接啟動應用程式並執行它。
-
執行應用程式測試以執行應用程式。
當 Spring 無法辨識程式庫或模式時,第一個選項有助於識別遺漏的提示。
第二個選項對於可重複的設定來說更具吸引力,但預設情況下,產生的提示將包含測試基礎架構所需的所有內容。當應用程式實際執行時,其中一些將是不必要的。為了應對這個問題,代理程式支援一個存取過濾檔案,該檔案將導致從產生的輸出中排除某些資料。
直接啟動應用程式
使用以下指令啟動應用程式,並附加 native image 追蹤代理程式
$ 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
停止應用程式。
應用程式關閉時,native image 追蹤代理程式會將提示檔案寫入指定的設定輸出目錄。您可以手動檢查這些檔案,或將它們用作 native image 建置程序的輸入。要將它們用作輸入,請將它們複製到 src/main/resources/META-INF/native-image/
目錄中。下次建置 native image 時,GraalVM 將會考慮這些檔案。
native image 追蹤代理程式還有更多進階選項可以設定,例如透過呼叫者類別過濾記錄的提示等。如需進一步閱讀,請參閱 官方文件。
自訂提示 (Custom Hints)
如果您需要為反射、資源、序列化、代理使用等提供自己的提示,您可以使用 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
的方法,可用於測試 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.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 原生映像是仍在發展中的技術,並非所有程式庫都提供支援。GraalVM 社群正在透過為尚未發佈自身原生映像支援的專案提供 可達性中繼資料 來提供協助。Spring 本身不包含第三方程式庫的提示,而是依賴於可達性中繼資料專案。
如果您在為 Spring Boot 應用程式產生原生映像時遇到問題,請查看 Spring Boot wiki 的 Spring Boot 與 GraalVM 頁面。您也可以在 GitHub 上為 spring-aot-smoke-tests 專案貢獻問題,該專案用於確認常見的應用程式類型是否按預期工作。
如果您發現無法與 GraalVM 搭配使用的程式庫,請在 可達性中繼資料專案 上提出問題。