進階原生映像檔主題
巢狀組態屬性
Spring 預先編譯引擎會自動為組態屬性建立反射提示。然而,並非內部類別的巢狀組態屬性**必須**使用 @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
)
在所有情況下,請使用 public 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 可用 (請參閱 Get 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 修改它們。 |
@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
的方法,該方法可用於測試 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 原生映像檔是一項不斷發展的技術,並非所有程式庫都提供支援。GraalVM 社群正在透過為尚未提供自己 可達性中繼資料 的專案提供可達性中繼資料來提供協助。Spring 本身不包含用於協力廠商程式庫的提示,而是依賴可達性中繼資料專案。
如果您在為 Spring Boot 應用程式產生原生映像檔時遇到問題,請查看 Spring Boot Wiki 的 Spring Boot with GraalVM 頁面。您也可以向 GitHub 上的 spring-aot-smoke-tests 專案提交問題,該專案用於確認常見應用程式類型是否如預期般運作。
如果您發現某個程式庫無法與 GraalVM 搭配使用,請在 可達性中繼資料專案 上提出問題。