借助于 GraalVM 和 springboot 3 ,我们可以将 java 应用编译成 native image,脱离 jre 单独运行。
需要安装 GraalVM 和 C++编译环境
下载GraalVM,然后解压到相应目录,配置环境变量:
JAVA_HOME
D:\app\java\graalvm-ce-java17-22.3.0
Path
%JAVA_HOME%\bin
接着验证一下
> java -versionopenjdk version "17.0.5" 2022-10-18OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)
最后安装native-image
,由于功能网络环境限制,先下载,然后使用离线安装:
gu install -L native-image-installable-svm-java17-windows-amd64-22.3.0.jar
在 windows 中使用 native-image 需要安装 msvc2017-15.9 或以上版本,可使用 vs 安装工具安装所需组件,vs 下载地址,选择社区版即可。
经实践,安装以下组件即可:
环境变量配置
WK10_INCLUDEC:\Program Files (x86)\Windows Kits\10\Include\10.0.20348.0WK10_LIBC:\Program Files (x86)\Windows Kits\10\Lib\10.0.20348.0WK10_BINC:\Program Files (x86)\Windows Kits\10\bin\10.0.20348.0INCLUDE%WK10_INCLUDE%\ucrt;%WK10_INCLUDE%\um;%WK10_INCLUDE%\shared;%MSVC%\include;LIB%WK10_LIB%\um\x64;%WK10_LIB%\ucrt\x64;%MSVC%\lib\x64;Path下新增D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\bin\Hostx64\x64%WK10_BIN%\x64
在 springboot 3 版本已经支持通过 aot 技术生成 GraalVM 所需要的配置信息:
Resource hints (resource-config.json)Reflection hints (reflect-config.json)Serialization hints (serialization-config.json)Java Proxy Hints (proxy-config.json)JNI Hints (jni-config.json)
最小可运行 demo,和之前版本保持一样,使用最新版就可以实现,这里放一下 pom 文件。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.0-RC1</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></pluginRepository></pluginRepositories></project>
代码:
@SpringBootApplication@RestControllerpublic class DemoApplication {@RequestMapping("/")public String hello() {return "Hello Word";}public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}
生成 native image 命令:
mvn -Pnative native:compile -DskipTests
最后就可以直接运行 native 应用
PS E:\demo\native-demo> .\target\native-demo. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v3.0.0-RC1)2022-11-02T13:58:16.011+08:00 INFO 11432 --- [ restartedMain] t.t.nativedemo.NativeDemoApplication : Starting AOT-processed NativeDemoApplication using Java 17.0.5 on WXMIS121 with PID 11432 (E:\demo\native-demo\target\native-demo.exe started by td20 in E:\demo\native-demo)2022-11-02T13:58:16.011+08:00 INFO 11432 --- [ restartedMain] t.t.nativedemo.NativeDemoApplication : No active profile set, falling back to 1 default profile: "default"2022-11-02T13:58:16.011+08:00 INFO 11432 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable2022-11-02T13:58:16.011+08:00 INFO 11432 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'2022-11-02T13:58:16.133+08:00 INFO 11432 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)2022-11-02T13:58:16.134+08:00 INFO 11432 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]2022-11-02T13:58:16.134+08:00 INFO 11432 --- [ restartedMain] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.0.27]2022-11-02T13:58:16.146+08:00 INFO 11432 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext2022-11-02T13:58:16.146+08:00 INFO 11432 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 134 ms2022-11-02T13:58:16.150+08:00 WARN 11432 --- [ restartedMain] i.m.c.i.binder.jvm.JvmGcMetrics : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM2022-11-02T13:58:16.182+08:00 INFO 11432 --- [ restartedMain] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator'2022-11-02T13:58:16.192+08:00 INFO 11432 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''2022-11-02T13:58:16.208+08:00 INFO 11432 --- [ restartedMain] t.t.nativedemo.NativeDemoApplication : Started NativeDemoApplication in 0.229 seconds (process running for 0.238)
使用docker不需要第一步中的环境搭建,只用保证安装docker即可。
这里没有使用官方镜像,因为官方镜像没有maven等软件。推荐使用 https://hub.docker.com/r/vegardit/graalvm-maven
$docker run -it --rm -v $PWD:/app/ vegardit/graalvm-maven:latest-java21 bash$mvn -Pnative native:compile -DskipTests
@RegisterReflectionForBinding(ReflectionRunner.Customer.class)
Demo:
@Component@RegisterReflectionForBinding(ReflectionRunner.Customer.class)class ReflectionRunner implements ApplicationRunner {private final ObjectMapper objectMapper;ReflectionRunner(ObjectMapper objectMapper) {this.objectMapper = objectMapper;}record Customer(Integer id, String name) {}@Overridepublic void run(ApplicationArguments args) throws Exception {var json = """[{ "id" : 2, "name": "Dr. Syer"} ,{ "id" : 1, "name": "Jürgen"} ,{ "id" : 4, "name": "Olga"} ,{ "id" : 3, "name": "Violetta"}]""";var result = this.objectMapper.readValue(json, new TypeReference<List<Customer>>() {});System.out.println("there are " + result.size() + " customers.");result.forEach(System.out::println);}}
还有一种 RuntimeHintsRegistrar 方式
@ImportRuntimeHints(DemoControllerRuntimeHints.class)static class DemoControllerRuntimeHints implements RuntimeHintsRegistrar {@Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {hints.reflection().registerConstructor(SimpleHelloService.class.getConstructors()[0], ExecutableMode.INVOKE).registerMethod(ReflectionUtils.findMethod(SimpleHelloService.class, "sayHello", String.class), ExecutableMode.INVOKE);}}
@ImportRuntimeHints(DemoControllerRuntimeHints.class)static class DemoControllerRuntimeHints implements RuntimeHintsRegistrar {@Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {hints.resources().registerPattern("hello.txt");}}
@ImportRuntimeHints(Edge2Application.Hints.class)static class Hints implements RuntimeHintsRegistrar {@Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {hints.proxies().registerJdkProxy(com.example.edge2.CrmClient.class, org.springframework.aop.SpringProxy.class,org.springframework.aop.framework.Advised.class, DecoratingProxy.class);}}