高階原生映象主題
巢狀配置屬性
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
屬性將無法繫結。您也可以註解 getter 方法。
使用建構函式繫結時,您必須用 @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;
}
}
使用記錄 (records) 時,您必須用 @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
只要 Spring Boot 可執行 Jar 包含 AOT 生成的資產,就可以將其轉換為原生映象。這樣做有很多原因,其中包括
-
您可以保留常規的 JVM 管道,並在 CI/CD 平臺上將 JVM 應用程式轉換為原生映象。
-
由於
native-image
不支援交叉編譯,您可以保留一個與作業系統無關的部署 Artifact,稍後再將其轉換為不同的作業系統架構。
您可以使用雲原生 Buildpacks 或 GraalVM 附帶的 native-image
工具將 Spring Boot 可執行 Jar 轉換為原生映象。
您的可執行 Jar 必須包含 AOT 生成的資產,例如生成的類和 JSON 提示檔案。 |
使用 Buildpacks
Spring Boot 應用程式通常透過 Maven (mvn spring-boot:build-image
) 或 Gradle (gradle bootBuildImage
) 整合來使用雲原生 Buildpacks。但是,您也可以使用 pack
將經過 AOT 處理的 Spring Boot 可執行 Jar 轉換為原生容器映象。
首先,請確保 Docker daemon 已可用(更多詳情請參見獲取 Docker)。如果您使用的是 Linux,請配置它以允許非 root 使用者。
您還需要按照 buildpacks.io 上的安裝指南 安裝 pack
。
假設一個經過 AOT 處理並構建為 myproject-0.0.1-SNAPSHOT.jar
的 Spring Boot 可執行 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 native-image
將經過 AOT 處理的 Spring Boot 可執行 Jar 轉換為原生可執行檔案的另一個選項是使用 GraalVM native-image
工具。要實現這一點,您的機器上需要有 GraalVM 發行版。您可以從Liberica Native Image Kit 頁面手動下載,也可以使用 SDKMAN! 等下載管理器。
假設一個經過 AOT 處理並構建為 myproject-0.0.1-SNAPSHOT.jar
的 Spring Boot 可執行 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 with GraalVM 頁面。您還可以向 GitHub 上的 spring-aot-smoke-tests 專案貢獻問題,該專案用於確認常見的應用程式型別按預期工作。
如果您發現某個庫無法與 GraalVM 一起使用,請在可達性元資料專案中提出問題。