SpringNative简介
我们都知道,传统的Spring应用程序都是必须依赖于Java虚拟机(JVM)运行的,SpringNative的诞生就是无需JVM,它提供了另外一种运行和部署Spring应用的方式(目前只支持Java和Kotlin),通过GraalVM将Spring应用程序编译成原生镜像。
SpringNative特点
1、无需JVM环境,SpringNative应用程序可以作为一个可执行文件独立部署;
2、应用即时启动,一般情况下应用启动时间ms;
3、即时的峰值性能;
4、更少的内存消耗;
SpringNative缺点
SpringNative应用启动那么快也是有代价的,和JVM应用相比:
1、构建更笨重、构建时间更长;
2、更少的运行时优化;
3、很多Java功能受限;
4、很多特性还很不成熟;
SpringNative应用场景
1、SpringCloud无服务器化(Serverless);
2、以更廉价持久的方式运行Spring微服务;
3、非常适合Kubernetes平台,如:VMwareTanzu;
4、为Spring应用创建更佳的容器镜像;
SpringNative和JVM的区别
1、SpringNative构建时会进行应用程序静态分析;
2、SpringNative构建时会移除未被使用的组件;
3、SpringNative反射、资源、动态代理需要配置化;
4、SpringNative构建时的classpath是固定不变的;
5、SpringNative没有类延迟加载,可执行文件包含所有内容都在启动时加载到内存;
6、SpringNative构建时会运行一些代码;
7、SpringNative对于Java应用程序还存在一些局限性;
GraalVM简介
SpringNative的核心就是Oracle的黑科技:GraalVM。
GraalVM是一个由Oracle开发的全栈通用虚拟机,拥有高性能、跨语言交互等逆天特性,不仅支持了Java、Scala、Groovy、Kotlin等基于JVM的语言,以及C、C++等基于LLVM的语言,还支持其他像JavaScript、Ruby、Python和R语言等,可提高多种语言的运行速度和吞吐量。
GraalVM有以下几个特性。
更加高效快速的运行代码
能与大多数编程语言直接交互
使用GraalSDK嵌入多语言
创建预编译的原生镜像
提供一系列工具来监视、调试和配置所有代码
具体就不介绍了,阅读我之前分享的这篇文章:Oracle发布了一个全栈虚拟机GraalVM
重点来看原生镜像功能:
1.$javacHelloWorld.java
2.$timejavaHelloWorld
3.user0.s
4.$native-imageHelloWorld
5.$time./helloworld
6.user0.s
GraalVM可以预编译成原生镜像,从而极大提速了启动时间,并能减少JVM应用的内存占用。现在你知道为什么SpringNative启动那么快的原因了!
SpringNative正是通过GraalVM提供了对传统Spring应用程序的轻量级运行方式,在不用修改任何传统应用程序代码的情况下,通过集成SpringNative项目就能轻松实现。
开始尝鲜
构建SpringNative应用的两种方式:
1、使用SpringBootBuildpacks来生成一个包含原生可执行文件的轻量级容器;
2、使用GraalVMnativeimageMaven插件来生成一个包含原生可执行文件;
本文使用第一种方式进行尝鲜!
1、环境要求
这种方式需要安装Docker环境:
Linux需要配置非root用户可运行
Mac需要配置最大内存为8G或以上
2、添加依赖
SpringNative在start.spring.io上面已经可以开始使用了,在页面上添加一个SpringNative依赖进去就好,如下所示:
SpringBoot:
parent
groupIdorg.springframework.boot/groupId
artifactIdspring-boot-starter-parent/artifactId
version2.4.5/version
relativePath/
/parent
SpringNative:
dependencies
dependency
groupIdorg.springframework.experimental/groupId
artifactIdspring-native/artifactId
version${spring-native.version}/version
/dependency
/dependencies
注意依赖版本:
SpringNative最新版本为:0.9.2,只支持SpringBoot2.4.5
3、添加SpringAOT插件
添加SpringAOT插件:
build
plugins
plugin
groupIdorg.springframework.experimental/groupId
artifactIdspring-aot-maven-plugin/artifactId
version0.9.2/version
executions
execution
idtest-generate/id
goals
goaltest-generate/goal
/goals
/execution
idgenerate/id
goalgenerate/goal
/executions
/plugin
/plugins
/build
SpringAOT插件执行所需的提前转换,以提升原生镜像的兼容性。
4、开启原生镜像支持
在SpringBootMaven插件中增加以下配置:
plugin
artifactIdspring-boot-maven-plugin/artifactId
configuration
image
builderpaketobuildpacks/builder:tiny/builder
env
BP_NATIVE_IMAGEtrue/BP_NATIVE_IMAGE
/env
/image
/configuration
/plugin
5、添加Maven仓库支持
SpringNative依赖和插件需要在Spring仓库中下载,需要添加以下配置。
repositories
repository
idspring-release/id
nameSpringrelease/name
/repository
/repositories
pluginRepositories
pluginRepository
/pluginRepository
/pluginRepositories
如果不能正常下载Native依赖和插件,需要检查Maven的settings.xml文件:
mirror
idnexus-aliyun/id
mirrorOf*,!spring-release/mirrorOf
nameNexusaliyun/name
/mirror
把mirrorOf值由*修改为:*,!spring-release
6、添加测试接口
添加一个测试接口,原生应用启动后,方便测试下可行性。
/**
*/
SpringBootApplicationRestControllerpublicclassApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(Application.class);
}
RequestMapping(/native/hi)ResponseBodypublicStringhiNative(){
returnhinativeapplication...;
}
7、构建原生应用
Maven插件构建命令:
mvnspring-boot:build-image
这个会创建一个Linux容器,使用GraalVM原生镜像编译器构建出原生应用程序,容器镜像默认只安装在本地。
在IDEA插件中运行:
配置好后开始构建:
会看到大量这样的错误,不用理会,这个会在未来移除。
最终构建完成,一个简单的SpringBoot应用程序,这个构建却过程花了我4分钟。。
8、运行原生应用
使用平常运行Docker镜像的方式就能运行原生应用:
dockerrun--rm-p:
一般情况下,运行原生应用程序只需要毫秒以下,而运行基于JVM的应用程序大概需要15秒左右。
事实是否如此呢,一起来看看!
我天,82毫秒就启动了,启动确实快
再来访问我们之前写的接口:
输出正常,原生应用验证完成。
另外,在target目录中也生成了可执行的jar包:
然后我们用传统JVM环境来运行下:
java-jarspring-boot-native-1.0.jar
启动时间:1.秒,虽然看起来差距不大,但原生应用启动时间(0.秒)也比JVM快了23倍,在不同的代码量面前可能会有较大差距的体现。
当然这只是我测试的参考时间,但可以说明的原生应用运行确实要比JVM快不少!
我们再来比对下包的大小
查看刚生成的Docker镜像:
dockerimagels
查看基于JVM的可执行jar包:
Docker镜像大小:80.7M,而基于JVM运行的可执行jar包却只有不到20M。
这是因为原生镜像不仅包含了应用程序中所使用到的来自JDK、Spring中的必须项,还包含了一个最小化的OS系统层,所以肯定是要比之前的要大不少。
总结
本文介绍了SpringNative的特点,及演示了基于Docker镜像的原生应用。
感兴趣的都可以Star下该仓库,包含了之前写的SpringBoot教程及示例源码。
当然除了基于Docker镜像,还可以使用原生镜像Maven插件的方式,那种方式不需要Docker,但需要安装原生镜像编译器GraalVM,道理是一样的,这里就不再演示了
如果有使用Docker,那第一种肯定是更好的方式,所有的依赖都打包到一个镜像中了,避免了环境污染。
最后总结一下就是,SpringNative可以无需JVM运行,构建慢、启动快、内存占用少、运行优化少,另外还有很多Java特性受限,比如:反射、动态代理等都需要通过提前配置化,因为Java是一种动态链接的语言,原生应用都要提前编译,这个像反射、动态代理这种特性就会受限。