[spring-projects/spring-boot]为Spring Boot自带的模块发布一个Gradle版本目录

2024-07-08 450 views
6

Gradle 现在支持版本目录,它的优点是可以生成类型安全的访问器来引用依赖项,例如

testImplementation(libs.mockk)

支持自动完成。

此类版本目录可以在构建本身中定义,但也可以发布并由其他项目使用。例如,Micronaut 就是这样做的,允许在设置文件中执行

    versionCatalogs {
        create("mn") {
            from("io.micronaut:micronaut-bom:3.3.0")
        }
    }

然后在构建文件中

dependencies {
    implementation(mn.picocli)
}

也支持覆盖导入目录中的版本。

与 Spring Boot BOM 一起发布这样的版本目录将是一个很好的功能,它将允许从 spring 依赖项插件迁移出去,现在 gradle 似乎支持其功能(使用版本目录和平台插件),并受益于依赖项类型安全访问器的额外优势。

回答

2

感谢您的建议,@jnizet。

我过去查看版本目录时遇到的主要障碍是,我认为目录不可能导入第三方 bom 或其他目录。除非我上次查看后情况有所改变,而查看 API 时我看不出它们有改变,否则我们必须停止使用第三方库的 bom,而是在我们自己的目录中手动声明该库中的每个模块。这当然是可能的,但这需要相当多的额外工作。然后,我们可能需要编写一些验证任务,以确保我们的目录涵盖了我们之前使用的每个第三方 bom 中的所有内容。

也就是说,依赖管理插件的工作量也不是零。如果要继续使用,它确实需要现代化(可能基于 Gradle 的约束支持),因为当前代码库仅使用 Gradle 2.x 中可用的 API。它继续工作证明了 Gradle 的向后兼容性,但我们不能永远依赖它。我会在团队会议上讨论这个问题,以便我们可以决定如何最好地利用我们的时间在这个领域。

5

太好了,谢谢@wilkinsona。

1

遗憾的是,版本目录无法有效替代依赖管理插件,原因与 Gradle 也有自己的 bom 支持platformenforcedPlatform依赖项相同。依赖管理插件有两个关键功能:

  1. 管理传递依赖项的版本
  2. 简单的单行版本覆盖

Gradle 的平台支持提供 1,版本目录提供 2。遗憾的是,即使结合使用平台和版本目录,也无法轻松覆盖传递依赖项的版本。有些遗憾的是,这意味着我们必须继续维护和推荐使用依赖项管理插件。我们的计划是在今年年底 Boot 3.0 版本发布时及时对其进行现代化改造。

我们会将此问题保留,因为 Boot 的版本目录仍会带来一些好处。例如,Boot 启动器的自动完成功能将是一个不错的开发者体验改进。不过,这是一个相当小的改进,因此我们预计不会很快解决它。

2
  1. 在 Gradle 的许多地方都提供了,只需添加 即可dependencies { implementation("group:module:version-override") }。版本目录在这里不起作用,因为它们只是用于声明版本,然后可以以类型安全的方式在构建脚本中使用。解析与此完全脱钩。我们长期以来一直只使用没有插件的 Spring BOM,没有任何问题,我们正在通过简单地声明我们自己的依赖项来升级我们需要升级的依赖项(无论如何这都是正确的方法,因为如果我的代码依赖于它,那么我应该声明对它的直接依赖)。

也许我遗漏了什么?

8

对于单模块库来说,声明依赖关系确实相当简洁。当库有多个模块时,事情会变得更加冗长。

举个例子,如果你对 Tomcat 有依赖关系spring-boot-starter-web并且想要使用特定版本,那么使用依赖管理插件你可以进行配置tomcat.version,然后就完成了:

ext["tomcat.version"] = "9.0.56"

如果没有依赖管理插件,您可以声明单个依赖项,但您必须知道要声明哪些依赖项以及要排除哪些依赖项才能保留启动器的行为。它可能看起来像这样:

implementation("org.apache.tomcat.embed:tomcat-embed-core:9.0.56") {
    exclude group: "org.apache.tomcat", module: "tomcat-annotations-api"
}
implementation("org.apache.tomcat.embed:tomcat-embed-el:9.0.56")
implementation("org.apache.tomcat.embed:tomcat-embed-websocket:9.0.56") {
    exclude group: "org.apache.tomcat", module: "tomcat-annotations-api"
}

另一种选择是针对三个 Tomcat 模块定义一些约束或定义一个解析策略来设置它们的版本,但都不如设置版本属性那样简洁或具有声明性。

无论如何,这都是正确的做法,因为如果我的代码依赖于它,那么我应该声明对它的直接依赖

我认为这不是简单地认为一种方法是正确的,而其他方法不正确。我认为这是主观的,取决于团队的个人偏好。我们知道许多 Spring Boot 用户喜欢各种启动模块的简洁性和易用性,并故意避免直接依赖他们的代码所依赖的每个模块,而倾向于使用一个可以满足他们所有需求的依赖项。

6

同意,您举的例子的真正问题是供应商不提供平台 (BOM),否则它又会归结为单一依赖声明。这种情况确实存在于大多数 JVM 生态系统中。一些供应商甚至通过以与其他具有不同版本控制方案的供应商相同的名称发布工件(看看 Confluent),从而使情况变得更糟。对于您向供应商举的例子,一个很好的问题是,如果大多数用户显然不需要注释(如果 Spring 排除注释,可以肯定地说大多数用户不需要注释),为什么它会强迫我们使用注释。

我认为这不是简单地认为一种方法是正确的,而其他方法不正确。我认为这是主观的,取决于团队的个人偏好。我们知道许多 Spring Boot 用户喜欢各种启动模块的简洁性和易用性,并故意避免直接依赖他们的代码所依赖的每个模块,而倾向于使用一个可以满足他们所有需求的依赖项。

我们还大量使用信任传递函数并使用传递函数来满足我们的需求。我的意思实际上是在回答这个问题的上下文中,我想要控制传递依赖项,因为我的代码需要它的特殊版本。在这种情况下,使用插件的依赖项扩展或直接声明依赖项归结为完全相同的事情。我们只是使用了两个不同的 API。

6

如果您为所管理的依赖项创建了一个没有版本的版本目录,并且使用平台来处理版本,结果会怎样?

那么,您是否获得了由平台/spring 插件管理的依赖项和版本的类型安全?

3

@hakonph 当然,我想大多数人都是这么做的。如果 spring 本身提供了目录,那么会非常方便,这样成千上万的开发人员就不必为所有不同的 spring 项目重复定义目录条目 - 而只需在上游导入后即可完成。这不是一个主要需求,但它是一个很好的“生活质量”改进。

5

我赞同 @hakonph 的想法。如果 Spring-Boot 发布版本目录,则除-BOM 和 Spring-Boot-Gradle-Plugin的条目外,所有依赖项都应在不带版本的情况下定义spring-boot-dependencies。这样,可以强制用户将 BOM 作为(可能的enforcedplatform依赖项(或使用spring-dependency-management插件):

dependencies {
  implementation(enforcedPlatform(springBootLibs.bom))
  implementation(springBootLibs.starter.web)

我还认为版本目录不应该复制完整的 Spring-Boot-BOM。它应该只包含 maven 组的库org.springframework

话虽如此:Micronaut 通过转换其 BOM 自动生成其版本目录。我对此也表示满意。

5

这真是太好了!

8

我还询问了 Gradle 团队关于 BOM 支持的问题,网址为https://github.com/gradle/gradle/issues/26048,他们也拒绝了我的想法……所以我正在开发一个设置插件austinarbor/version-catalog-generator,它会根据您libs.versions.toml(或其他目录文件)中的依赖项或通过指定您想要的任何其他 GAV 坐标自动生成版本目录。它为来自指定 BOM 的直接依赖项创建一个库,并以 BFS 方式从所有其他传递 BOM 依赖项创建库条目。例如,Spring BOM 导入 Mockito BOM,因此 Mockito BOM 中的依赖项也将包括在内。还会根据版本属性自动创建捆绑包。例如,在 spring bom 中,将创建一个包含所有共享属性的依赖项的捆绑包activemq.version。此功能是否有用尚待确定,但目前存在。

它仍处于 alpha 阶段,因此有些粗糙,可以使用更多自定义选项,但总体而言,它似乎已经运行得很好了。Kotlin DSL 中的 API 更好,但我正在尝试思考如何改进 Groovy DSL。我认为最大的缺失部分是轻松强制使用不同版本(如ext['property']='version')的功能,但我计划在未来为此实现一些东西。

抱歉,我自我推销了一番,但我偶然发现了这个问题,并认为我正在开发的插件可能在这里有用。如果违反规则,我会删除此评论。如果有人发现该插件有用,或者能想到如何让它更好地适用于此处描述的用例,我将很高兴听到您的反馈!

0

感谢分享,@austinarbor。你的评论非常好。祝项目顺利。