将 Spring Boot 的构建迁移到 Gradle

工程 | Andy Wilkinson | 2020 年 6 月 8 日 | ...

我们在 Spring Boot 2.3.0.M1 中进行了一项相当重要的更改。这是该项目第一个使用 Gradle 而非 Maven 构建的版本。在 Twitter 上关于此次迁移的一个讨论串中,有不少人询问我们为什么切换,以及是否看到了任何好处。这篇博文旨在回答这些问题。

Spring 产品组合中的每个项目都以相当自主的方式运行。我们在用户最看重的地方(例如 API 设计)力求一致性,但在不太显眼的地方,我们会选择最能满足项目需求的工具。构建系统就是一个例子。构建系统的变化会影响项目的贡献者,但如果我们做得好,则不会影响用户。这导致了基于 Maven 和基于 Gradle 的构建并存。例如,Spring Framework 自 2012 年的 3.2.0.M1 版本以来一直使用 Gradle 构建,而 Spring Boot 在一年后开始,Spring Cloud 在那之后不久也开始了,两者都基于 Maven 构建。与 Spring Boot 不同,Spring Cloud 没有切换的计划,因为 Maven 继续满足他们的需求。简而言之,如果你从这篇博文中只记住一件事,那就是你应该选择最能满足你项目需求的工具。

我们为什么切换?

Spring Boot 团队考虑切换到 Gradle 的主要原因是减少项目的构建时间。我们对修改和测试更改时的反馈循环时长感到沮丧。等待构建完成的时间增加了修复 bug 和实现新功能所需的时间。我们在其他 Spring 项目中看到了 Gradle 的增量和并行构建的好处,以及在第三方项目中 Gradle 的构建缓存的好处。我们希望能在 Spring Boot 的构建中获得类似的益处。

过去我们曾尝试利用 Maven 对并行构建的支持。由于 Spring Boot 构建的复杂性,特别是其使用了 Invoker 插件,我们的尝试失败了。我们在 CI 上通过将构建拆分成四个部分来解决了这个问题。首先构建项目的主要核心部分,然后并行构建三个独立的部分。这种安排有所帮助,但 CI 构建仍然需要一个小时或更长时间。此外,由于这种拆分结构是针对 CI 构建的,因此并没有加快开发人员的本地构建速度。

Gradle 对构建结构有一个广泛的模型,它理解每个任务的输入、输出及其相互依赖关系。这种模型的优势在于,它允许任务并行运行,同时也可以是增量的、缓存的或完全跳过的。换句话说,Gradle 旨在最小化构建任何给定更改所需的工作量,并并行执行必要的工作。如果我们坚持不懈地对 Spring Boot 的构建进行大量重构,也许可以使用 Maven 实现并行构建。而且,如果我们使用 Gradle Enterprise 的 Maven 支持,我们也可以享受到构建缓存和避免的好处。然而,为了充分享受所有这四项益处,我们认为必须尝试切换到 Gradle。

我们如何切换?

我们看到对 Gradle 的一种批评是,它导致的构建比基于 Maven 的构建更难维护和理解。Gradle 的灵活性允许在同一构建中的不同模块之间以细微不同的方式做事。如果切换要成功,我们需要避免这种情况。在通过 Gradle 发布了四个 Spring Boot 2.3 里程碑版本、其候选发布版本和最终发布版本后,看起来我们已经成功了。我们核心团队或任何其他贡献者都没有遇到重大的构建问题。

Spring Boot 的一个关键特性是“约定优于配置”,我们将这种方法也应用到了构建中。遵循避免在 build.gradle 文件中编写命令式逻辑的建议,我们在项目的 buildSrc 中编写了几个小插件。例如,我们有一个 starter 插件,它应用于每个 Spring Boot starter 模块,确保它们都得到一致的配置、构建和发布。我们还有一个 约定插件,它会在其他插件被应用时做出反应,并配置诸如源代码编码、JUnit Platform 的使用以及使用 -parameters 进行编译等事项。

这种方法使得 build.gradle 文件几乎完全采用声明式。尽管我们编写了许多插件来应用我们的约定并弥补 Gradle 生态系统中的不足,但迁移到 Gradle 的提交从代码库中删除了近 9500 行代码。

这次切换有益处吗?

在减少项目构建时间方面,将构建迁移到 Gradle 无疑是成功的。如上所述,基于 Maven 的完整构建在 CI 和开发人员的机器上都需要一个小时或更长时间。在过去四周中,使用 Gradle 的平均成功构建时间为 9 分 22 秒,如下面截图所示。

我们从 JDK 8 CI 构建中发布快照。重点关注这些构建,在过去 4 周内成功了 183 次,平均构建时间为 19 分 37 秒。查看成功的本地构建,我们可以看到在过去 4 周内有 273 次成功构建,平均构建时间为 2 分 30 秒

另一个吸引我们转向 Gradle 的好处是,在为 Testcontainers 贡献时获得的经验。我们也希望 Spring Boot 的贡献者能够尽快克隆和构建项目。得益于远程构建缓存,一次干净的检出可以在 3 分钟内构建完成。这包括下载大量依赖项所花费的时间。

如果你对构建的性能有更多兴趣,我们的公共 Gradle Enterprise 实例上提供了大量数据。

除了性能改进之外,我们也开始关注一些其他可用数据。例如,我们早就知道我们有一些不稳定的(flaky)测试。构建因为这些测试失败的频率比我们希望的要高,现在我们在 测试仪表板 中看到了这一点。我们已经开始使用 Gradle 的不稳定测试缓解机制来识别 CI 上出现的任何不稳定测试,并帮助我们了解是否已成功解决或绕过了问题。

结论

我们对迁移过程和我们所看到的构建时间的减少感到非常满意。CI 构建现在平均大约需要 20 分钟,比以前快了 3-4 倍。本地构建平均需要 2 分 30 秒,比以前快了 20-30 倍。

我想借此机会感谢 Gradle 团队在迁移期间提供的帮助,以及慷慨地为我们的开源项目提供了 Gradle Enterprise 许可证。我们已经将它用于 Spring Framework、Spring Security 和 Spring Boot,其他团队也计划开始将它用于他们基于 Gradle 和 Maven 的构建。

我还要感谢我们使用的各种第三方插件的维护者。他们提出了建议性的更改并合并了拉取请求,以改进对增量构建和缓存的支持。没有他们,我们将无法实现我们所看到的构建时间的减少。

如果你正在考虑从 Maven 迁移到 Gradle,我希望了解 Spring Boot 团队的经验对你有所帮助。如果你是一个满意的 Maven 用户,请继续使用和支持那个为你工作的工具。

订阅 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,为你的进步加油助力。

了解更多

获取支持

Tanzu Spring 在一项简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部