本文最初发表于Spring博客网站,由 InfoQ 中文站翻译分享。
最近,Spring 发布了Spring Native的 beta 版本,该功能已经在start.spring.io上可用了。
这意味着,除了 Spring 诞生以来就支持的 Java 虚拟机,官方添加了使用GraalVM将 Spring 应用编译成原生镜像的 beta 支持,这样的话,就能提供一种新的方式来部署 Spring 应用。Spring Native 支持 Java 和 Kotlin。
这些原生的 Spring 应用可以作为一个独立的可执行文件进行部署(不需要安装 JVM),并且还能提供有趣的特征,包括几乎瞬时的启动(一般会小于 100 毫秒)、瞬时的峰值性能以及更低的资源消耗,其代价是比 JVM 更长的构建时间和更少的运行时优化。
通过简单的mvn spring-boot:build-image
或gradle bootBuildImage
命令,就能生成一个优化的容器镜像,它包含了一个最小的操作系统层和一个小的原生可执行文件,该文件只包含了必需的东西即 JDK、Spring 以及应用中所使用的依赖。
请看下面这个最小的容器镜像,它是一个 50MB 的可执行文件,包含了 Spring Boot、Spring MVC、Jackson、Tomcat、JDK 和应用本身。
这种原生方式,在很多场景下都会对 Spring 应用产生价值:
使用 Spring Cloud Function 的 Serverless 应用
更廉价、更可持续地托管 Spring 微服务
与VMware Tanzu这样的 Kubernetes 平台有很好的契合性
想要最优的容器镜像,以打包 Spring 应用和服务
在使用场景上,比如 Piotr Mińkowski 提供了一个非常棒的指南,介绍了如何在Knative上使用 Spring Boot 和 GraalVM 构建原生微服务。
团队协作
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 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 等。
start.spring.io
Stéphane Nicoll 在对start.spring.io和相关 IDE 的集成中,引入了对 Spring Native 的支持,所以现在这是探索如何使用 Spring 构建原生应用最简单的方式。
添加 Spring Native 依赖后将会使用所需的依赖和插件自动配置 Maven 或 Gradle 项目,以便于支持原生。应用代码本身没有变化。
请检查自动生成的HELP.md
文件,该文件包含了有用的链接和文档,同时它还能标记出来你是否选择了一些在原生环境下不支持的依赖。
预先转换
原生与 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
中生成正确的条目,如下所示:
NativeConfiguration 和其他的动态配置机制允许实现更加强大和动态化的配置生成,但是需要注意它们的 API 在未来的版本中可能会有很大变化。
Spring 开发人员也可以直接在 @Configuration 或 @SpringBootApplication 类上添加应用特定的原生线索注解,例如,对于使用RestTemplate
或WebClient
这样的编程 API 序列化一个Book
类为 JSON:
在使用预先转换系统时,最后一个,可能也是最强大的一个机制就是根据 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 中运行应用程序之前更新生成的源码。
结论
在支持原生方面,Spring 有两个支柱性的策略。第一个是在不需要对现有的数百万个 Spring Boot 应用进行重大改动的情况下,对 Spring 基础架构进行调整以适应原生。这包括在 Spring 顶层项目中为实现原生友好而做出的改变,像 @NativeHint 这样的基础架构,以及在 Spring Native 中逐渐成熟的 Spring AOT 构建插件。
第二个支柱比 Spring 本身的范围更广,原生是一个与 JVM 特性有所差异的平台,但 Java 生态系统需要尽可能地保持一致,以避免出现两种截然不同的 Java 风格,如果这样的话,将会是维护上的一个挑战。
原文链接:
https://spring.io/blog/2021/03/11/announcing-spring-native-beta
相关推荐:
评论 2 条评论