Spring干掉原生JVM?

电子说

1.3w人已加入

描述

 


Spring 团队日前发布了 Spring Native Beta 版。通过 Spring Native,Spring 应用将有机会与 GraalVM 原生镜像的方式运行。为了更好地支持原生运行,Spring Native 提供了 Maven 和 Gradle 插件,并且提供了优化原生配置的注解。

最近,Spring 发布了 Spring Native 的 beta 版本,该功能已经在 start.spring.io 上可用了。

https://github.com/spring-projects-experimental/spring-native

https://start.spring.io/

这意味着,除了 Spring 诞生以来就支持的 Java 虚拟机,官方添加了使用 GraalVM 将 Spring 应用编译成原生镜像的 beta 支持,这样的话,就能提供一种新的方式来部署 Spring 应用。Spring Native 支持 Java 和 Kotlin。

这些原生的 Spring 应用可以作为一个独立的可执行文件进行部署(不需要安装 JVM),并且还能提供有趣的特征,包括几乎瞬时的启动(一般会小于 100 毫秒)、瞬时的峰值性能以及更低的资源消耗,其代价是比 JVM 更长的构建时间和更少的运行时优化。

JVM

通过简单的 mvn spring-boot:build-image 或 gradle bootBuildImage 命令,就能生成一个优化的容器镜像,它包含了一个最小的操作系统层和一个小的原生可执行文件,该文件只包含了必需的东西即 JDK、Spring 以及应用中所使用的依赖。

请看下面这个最小的容器镜像,它是一个 50MB 的可执行文件,包含了 Spring Boot、Spring MVC、Jackson、Tomcat、JDK 和应用本身。

JVM

这种原生方式,在很多场景下都会对 Spring 应用产生价值:

  • 使用 Spring Cloud Function 的 Serverless 应用
  • 更廉价、更可持续地托管 Spring 微服务
  • 与 VMware Tanzu 这样的 Kubernetes 平台有很好的契合性
  • 想要最优的容器镜像,以打包 Spring 应用和服务

在使用场景上,比如 Piotr Mińkowski 提供了一个非常棒的指南,介绍了如何在 Knative 上使用 Spring Boot 和 GraalVM 构建原生微服务。

https://piotrminkowski.com/2021/03/05/microservices-on-knative-with-spring-boot-and-graalvm/

1. 团队协作

Spring Native beta 是整个 Spring 团队及其家族项目广泛合作的结果:Spring Framework、Spring Boot 还包括 Spring Data、Spring Security、Spring Cloud 和 Spring Initializr。

据悉,原生功能的工作范围比 Spring 更广,因为原生涉及到更广泛的 JVM 生态系统,所以官方一直在与 GraalVM 团队合作,以改善原生镜像的兼容性和资源消耗。

以下是来自 GraalVM 团队的 Vojin Jovanovic 的一段话。

“与 Spring 团队协作打造原生 JVM 生态系统是一件非常愉快的事情:他们深厚的技术知识,再加上对社区的敏感触觉,总是能带来最好的解决方案。最新的 Spring Native 版本,以及它在 JVM 生态系统中的众多用法,为原生编译的广泛采用铺平了道路。”

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2. 支持的范围

现在,Spring Native已经从alpha过渡到beta,那么很重要的一点就是明确他弄所支持的功能范围。

Alpha是第一步,我们进行了大量试验并完善了Spring Native(以前称为Spring GraalVM Native)的体系结构,兼容性和对一系列样本进行了重大更改的封装。我们还报告了GraalVM团队修复的许多问题,目的是缩小JVM与Spring应用程序的本机之间的差距。

虽然它仍被认为是实验性的,但 beta 版意味着 Spring 现在在 Spring 生态系统的一个子集上提供了对原生的支持。如果你的应用正在使用业已支持的依赖,那么你可以试用它,在出现问题时可以提 bug 或贡献 pull request。在最新的 Spring Boot 2.x 小版本的每个补丁发布时,都会有一个新的 Spring Native 版本。Spring Native 0.9.0 支持 Spring Boot 2.4.3,Spring Native 0.9.1 将支持 Spring Boot 2.4.4 等。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

3. start.spring.io

Stéphane Nicoll 在对 http://start.spring.io 和相关 IDE 的集成中,引入了对 Spring Native 的支持,所以现在这是探索如何使用 Spring 构建原生应用最简单的方式。

JVM

添加 Spring Native 依赖后将会使用所需的依赖和插件自动配置 Maven 或 Gradle 项目,以便于支持原生。应用代码本身没有变化。

请检查自动生成的 HELP.md 文件,该文件包含了有用的链接和文档,同时它还能标记出来你是否选择了一些在原生环境下不支持的依赖。

4. 预先转换

原生与JVM有说不同:类路径在构建时是固定的,反射或资源需要进行配置,这里没有类的懒加载(可执行文件中包含的所有内容在启动的时候都会加载进来)并且有些代码可以在构建期调用。

为了充分拥抱这些特性,并且能让 Spring 应用以最大的兼容性和最小的资源消耗运行在原生环境中,Brian Clozel 在这个版本中引入了 Spring 预先(ahead-of-time,AOT)转换的 Maven 和 Gradle 插件,这个插件会对 Spring 应用执行预先转换。

第一种转换的目的是生成 GraalVM 原生配置(反射、资源、代理、原生镜像选项),这是通过由 Andy Clement 设计和实现的一个特别棒的推断引擎做到的,该引擎能够理解 Spring 编程模型和基础设施。例如,每个带有 @Controller 注解的类,都会在生成的 reflect-config.json 文件中添加一个条目。

有些原生配置是无法推断的,对于这些情况,Spring Native 引入了原生线索(native hint)注解(参见 Javadoc 以了解详情),这些注解允许 Spring Native 支持原生配置,这种方式比常规的基于 JSON 的原生镜像配置更加可维护、类型安全和灵活。例如,Spring Native 对 MySQL 驱动支持就提供了线索注解,它们会在原生镜像配置 reflect-config.json、resource-config.json 和 native-image.properties 中生成正确的条目,如下所示:

@NativeHint(
    trigger = Driver.class,
    options "--enable-all-security-services",
    types = @TypeHint(types = {
       FailoverConnectionUrl.class,
       FailoverDnsSrvConnectionUrl.class,
       // ...
    }), resources = {
    @ResourceHint(patterns = "com/mysql/cj/TlsSettings.properties"),
    @ResourceHint(patterns = "com.mysql.cj.LocalizedErrorMessages",
                      isBundle = true)
})
public class MySqlHints implements NativeConfiguration {}

NativeConfiguration和其他动态配置机制允许实现更加强大和动态化的配置生成,但是需要注意它们的 API 在未来的版本中可能会有很大变化。

Spring开发人员也可以直接在 @Configuration 或 @SpringBootApplication 类上添加应用特定的原生线索注解,例如,对于使用 RestTemplate 或 WebClient 这样的编程 API 序列化一个 Book 类为 JSON:

@TypeHint(types = Book.class)
@SpringBootApplication
public class WebClientApplication {
    // ...
}

在使用预先转换系统时,最后一个,可能也是最强大的一个机制就是根据 Spring Boot 部署模型和 GraalVM 原生镜像特征所引入的封闭世界(closed-world)假设,它能够自动生成针对原生环境进行优化的代码。这里的目标就是限制所需的外部原生配置的数量,从而提高兼容性,这是通过原生镜像编译器对代码结构的分析实现的,同时还能通过减少反射、资源或代理所需的配置,降低资源占用。一个具体的例子就是对各种 spring.factory(Spring Boot 背后的扩展机制)的预先转换,从而实现一个优化过的程序版本,该版本不需要反射并且会过滤掉应用上下文中不必要的条目。

对 Spring AOT来说,这只是一个开始,我们计划添加更加强大的转换,比如将 @Configuration 替换为函数式配置,从而通过预先分析替换运行时反射,能够自动生成使用像 lambda 表达式和方法引用这种程序构造的配置类。这样的话,就能允许 GraalVM 原生镜像编译器立即理解 Spring 配置,无需任何的反射配置或 *.class 资源。

需要记住的一个关键点是,在使用 Spring Native 时,这个 AOT 生成的代码在 JVM 上也会默认使用,这样的话能够通过 JVM 允许的短反馈循环(short feedback loop),用调试器和所有常规工具实现“原生友好的代码路径”。

尽管Spring AOT转换目前主要是由原生场景需求驱动,但是有很多转换并不是特定于原生场景的,有一些可能为 JVM 上运行的 Spring Boot 应用提供优化。和往常一样,对于这种主题,重要的是要以数据为驱动,所以我们会衡量效率和性能来驱动我们的决策。

我们很可能会完善 IDE 集成,目前请务必阅读相关文档,了解潜在的手动配置步骤,以便在 IDE 中运行应用程序之前更新生成的源码。

5. 结论

在支持原生方面,Spring 有两个支柱性的策略。第一个是在不需要对现有的数百万个 Spring Boot 应用进行重大改动的情况下,对 Spring 基础架构进行调整以适应原生。这包括在 Spring 顶层项目中为实现原生友好而做出的改变,像 @NativeHint 这样的基础架构,以及在 Spring Native 中逐渐成熟的 Spring AOT 构建插件。

第二个支柱比Spring本身的范围更广,原生是一个与 JVM 特性有所差异的平台,但 Java 生态系统需要尽可能地保持一致,以避免出现两种截然不同的 Java 风格,如果这样的话,将会是维护上的一个挑战。

 


审核编辑 :李倩

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分