深圳網絡公司做網站臺州網站seo
1. 介紹
Java應用程序可以通過Graalvm Native Image提前編譯生成與本地機器相關的可執(zhí)行文件。與在JVM執(zhí)行java程序相比,Native Image占用內存更小和啟動速度更快。
從spring boot3開始支持GraalVM Native Image,因此要使用此特性,需要把spring boot升級到3.0.0以上, 其中,JDK也要升級到17以上。
官方提供的示例見Developing Your First GraalVM Native Application。示例代碼與普通spring boot一樣,可以忽略,繼續(xù)看下面內容。
1.1 maven插件配置
示例中與spring boot項目一樣,不同之處在于打包配置,比如maven pom.xml文件中需要加上graalvm的native-maven-plugin
插件,如下:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.0</version><relativePath/>
</parent><!--省略--><dependencies>
<!--省略-->
</dependencies><build><plugins><plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!--其它插件--></plugins>
</build>
pom說明:<parent>
項指定為spring-boot-starter-parent
,這樣你就可以減少許多配置,就像上面一樣。
在spring-boot-starter-parent
中為生成Native Image定義了名為native
的profile,打包生成可執(zhí)行文件使用如下maven命令
mvn clean package -Pnative
1.2 maven插件完整配置
如果pom的<parent>
項不是spring-boot-starter-parent
,你就需要像如下配置
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.3.0</version><configuration><archive><manifestEntries><Spring-Boot-Native-Processed>true</Spring-Boot-Native-Processed></manifestEntries></archive></configuration>
</plugin>
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.3.0</version><configuration><image><builder>paketobuildpacks/builder-jammy-tiny:latest</builder><env><BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE></env></image></configuration><executions><execution><id>process-aot</id><goals><goal>process-aot</goal></goals></execution></executions>
</plugin>
<plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId><version>0.10.2</version><configuration><classesDirectory>${project.build.outputDirectory}</classesDirectory></configuration><executions><execution><id>add-reachability-metadata</id><goals><goal>add-reachability-metadata</goal></goals></execution></executions>
</plugin>
2. 開發(fā)注意事項
Spring Boot對自身提供的bean自動注入、AOP配置、factories等特性做了Native支持,在靜態(tài)編譯期間相關類都可達,
但我們的項目中還有些不受spring AOT支持的代碼,比如業(yè)務代碼中的反射、JSON和對象轉換、以及三方jar包中動態(tài)代碼等。
我們需要手動配置提供hint,graalvm才能把這些動態(tài)代碼編譯到可執(zhí)行文件中去,否則執(zhí)行時會拋出異常。
2.1 項目中動態(tài)代碼支持
提供動態(tài)代碼的hint配置,有三種方式:
- 第一種遵從graalvm規(guī)范提供相應的json配置,這個可以見往期分享Graalvm配置文件與Feature和Substitute機制介紹
- 第二種擴展Spring接口,即實現(xiàn)
RuntimeHintsRegistrar
接口,通過代碼指定哪些類、資源需要hint。 - 第三種反射類可以使用
@RegisterReflectionForBinding
由spring自動綁定反射配置。
下面介紹后面兩種方式
2.1.1 擴展Spring接口RuntimeHintsRegistrar
- 示例代碼如下:
public class MyAOTRuntimeHints implements RuntimeHintsRegistrar {@Overridepublic 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);// 反射注冊hints.reflection().registerType(UserModel.class, typeHint -> {typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,MemberCategory.INVOKE_DECLARED_METHODS,MemberCategory.DECLARED_FIELDS);});// 資源文件注冊hints.resources().registerPattern("resource_hint.properties");// Serialization類注冊hints.serialization().registerType(Email.class);// 代理注冊// hints.proxies().registerJdkProxy(MyInterface.class);} }
- 注冊Hint類。MyAOTRuntimeHints還需要使用
@ImportRuntimeHints
注解到任何@Configuration
class,或者在META-INF/aot.factories
文件注冊- 使用
@ImportRuntimeHints
@Configuration @ImportRuntimeHints(value = {MyAOTRuntimeHints.class}) public class AOTConfiguration {}
- 注冊
META-INF/aot.factories
org.springframework.aot.hint.RuntimeHintsRegistrar=com.example.springnative.aot.hit.MyAOTRuntimeHints
- 使用
2.1.2 使用注解@RegisterReflectionForBinding
對于項目中使用反射的地方,可以直接通過注解來配置hint,把@RegisterReflectionForBinding
注解到任何@Configuration
class類上。
示例如下:
@Configuration
@RegisterReflectionForBinding(classes = {MyJSONBean.class})
public class AOTConfiguration {}
2.2 代碼中隱式使用反射的地方
2.2.1 JSON
json工具推薦使用jackson,spring中也對其做了支持。
把對象轉為json或把json轉為對象,對象中所有類都要注冊反射hint。
如UserModel在在reflect-config.json配置如下
{
"name": "com.example.springnative.model.UserModel",
"allPublicConstructors": true,
"allDeclaredFields": true,
"allPublicMethods": true
}
如果javabean實現(xiàn)了java.io.Serializable接口,可以在serialization-config.json中注冊,示例如下。
{
"name": "com.example.springnative.model.UserModel"
}
2.3 Configuration Properties類嵌套
如果Properties類嵌套了其它類型,則必須要使用@NestedConfigurationProperty
注解,否則spring AOT沒法識別到這個嵌套類,就不能為其注冊反射hint。如下示例。
@ConfigurationProperties(prefix = "my.properties")
public class MyProperties {@NestedConfigurationPropertyprivate final Nested nested = new Nested();// getters / setters...
}public class Nested {private int number;// getters / setters...}
2.4 Native Image執(zhí)行與JVM執(zhí)行區(qū)別
- 應用程序類路徑在生成時是固定的,不能更改。
- 沒有延遲類加載,可執(zhí)行文件中提供的所有內容都將在啟動時加載到內存中。
- 應用程序中定義的Bean不能在運行時更改,即bean創(chuàng)建相關的條件配置在編譯后就不能再更改。
如@Profile注解及類似配置不能更改,@ConditionalOnProperty中的條件值不能更改,即使更改也不會生效,因為bean已經創(chuàng)建了。
3. 原理簡述
- Spring AOT Processor會啟動應用main方法開始靜態(tài)分析,并生成BeanDefinition對象,但不會創(chuàng)建bean
- 分析階段不可達的代碼將被忽略,不會編譯到可執(zhí)行文件中。
- 在編譯前會生成java代碼來創(chuàng)建BeanDefinition對象。源碼存放到target/spring-aot/main/sources
- AOP原本在運行時動態(tài)生成的字節(jié)碼,但在編譯時就必須生成,動態(tài)字節(jié)碼存放在target/spring-aot/main/classes
- Spring AOT Processor生成native需要的hint文件,存放到target/spring-aot/main/resources
- Spring boot把所有生成的文件都編譯到jar包中,關于hint文件都放到jar包的
META-INF/native-image
目錄下,graalvm靜態(tài)編譯時會獲取該目錄下的hint配置文件。
4. 官方文檔閱讀
Known GraalVM Native Image limitations
Testing GraalVM Native Images
Introducing GraalVM Native Images
5. 總結
- maven插件配置有兩種方式,
- 一種是pom的parent指定為
spring-boot-starter-parent
后簡單引入spring的spring-boot-maven-plugin
和graalvm的native-maven-plugin
插件 - 另一種是完整的配置spring的
spring-boot-maven-plugin
插件指定process-aot
目標,配置graalvm的native-maven-plugin
插件指定add-reachability-metadata
目標
- 一種是pom的parent指定為
- 動態(tài)代碼需要由開發(fā)者指定hint配置,有3種方式:
- 使用graalvm原生支持的方式,在
META-INF/native-image
目錄下添加反射、代理、JNI等相關json配置。 - 實現(xiàn)
RuntimeHintsRegistrar
接口通過代碼方式注冊hint配置。 - 反射類可以使用
@RegisterReflectionForBinding
由spring自動綁定反射配置。
- 使用graalvm原生支持的方式,在
- 編譯后影響bean創(chuàng)建相關的配置在運行期間不起作用,如
@ConditionalOnProperty
中的條件。