[关闭]
@levinzhang 2021-03-20T13:15:32.000000Z 字数 4724 阅读 537

Spring Native Beta发布

摘要

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


本文最初发表于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-imagegradle bootBuildImage命令,我们就能生成一个优化的容器镜像,它包含了一个最小的操作系统层和一个小的原生可执行文件,该文件只包含了必需的东西即JDK、Spring以及应用中所使用的依赖。请看下面这个最小的容器镜像,它是一个50MB的可执行文件,包含了Spring Boot、Spring MVC、Jackson、Tomcat、JDK和应用本身。

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

我相信优秀的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等。破坏性的变更可能会出现,但我们会提供迁移路径文档。文档质量已经达到了一个新的水平:参考文档提供了html单页pdf格式,我们还发布了公开的原生线索(hint)API的Javadoc

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.jsonresource-config.jsonnative-image.properties中生成正确的条目,如下所示:

  1. @NativeHint(
  2. trigger = Driver.class,
  3. options = "--enable-all-security-services",
  4. types = @TypeHint(types = {
  5. FailoverConnectionUrl.class,
  6. FailoverDnsSrvConnectionUrl.class,
  7. // ...
  8. }), resources = {
  9. @ResourceHint(patterns = "com/mysql/cj/TlsSettings.properties"),
  10. @ResourceHint(patterns = "com.mysql.cj.LocalizedErrorMessages",
  11. isBundle = true)
  12. })
  13. public class MySqlHints implements NativeConfiguration {}

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

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

  1. @TypeHint(types = Book.class)
  2. @SpringBootApplication
  3. public class WebClientApplication {
  4. // ...
  5. }

在使用预先转换系统时,最后一个,可能也是最强大的一个机制就是根据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风格,如果这样的话,将会是维护上的一个挑战。这就是为什么我们与GraalVM团队进行深入合作,以减少这种差距。在接下来的几个月里,我们的这种合作将专注于改进原生测试和原生配置,以适应更广泛的JVM生态系统。

Spring开发者可以通过我们提供的各种样例了解更多关于原生的信息,访问start.spring.io测试对原生的支持,阅读最新的参考指南,阅读发布说明(当你从以前的版本升级时更应如此),甚至可以为你喜欢的依赖关系贡献原生环境的支持。如果你想了解更多相关Spring商业支持的信息,也可以联系我们。

最后,我想感谢业已提供了很多有用反馈和贡献的Spring社区,感谢GraalVM团队的这种杰出的合作,并感谢范围更广泛的Spring团队,他们一直在努力让Spring开发者更容易地采用原生功能。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注