使用 Spring Boot 覆盖依赖版本

工程 | Dave Syer | 2016 年 4 月 13 日 | ...

本文解释了一些可用于创建库和应用程序的依赖管理技巧,这些库和应用程序依赖于比 Spring BootSpring IO Platform 等平台管理的传递性依赖项更新的版本。下面的示例使用 Reactor 作为此类依赖项的示例,因为它即将发布一个重要的新版本 (2.5.0),而现有的依赖管理平台 (Spring Boot 1.3.xq) 声明依赖于旧版本 (2.0.7)。如果您想编写一个通过库的传递性依赖项依赖于 Reactor 新版本的应用程序,这就是您将面临的情况。

这样做是合理的,但应谨慎进行,因为传递性依赖项的新版本很容易破坏 Spring Boot 中依赖旧版本的功能。当您这样做并应用以下修复方法之一时,您就脱离了 Spring Boot 的依赖管理,相当于说“嘿,我知道我在做什么,相信我。”不幸的是,有时您需要这样做才能利用第三方库中的新特性。如果您不需要 Reactor 的新版本(或您需要的任何其他外部传递性依赖项),则不要这样做,只需遵循常规路径,让 Spring Boot 管理依赖项即可。

本文中的示例代码的现实生活对应情况是一个明确更改了 spring-boot-dependencies 中列出的某个依赖项版本的库。其他基于 Boot 的项目,例如 Spring Cloud 的各个部分,定义了它们自己的 *-dependencies BOM,您可以使用它来管理外部依赖项。在大多数情况下,这些项目不需要与 Spring Boot 冲突的传递性依赖项的新版本。如果它们需要,它们就必须这样声明,而这就是您可以选择使用它们版本的依赖管理的方式。引发本文的例子是 spring-cloud-cloudfoundry-deployer,它通过对新的 Cloud Foundry Java client 的传递性依赖需要 Reactor 2.5。它的父项有(或者应该有)一个 *-dependencies BOM,可以在下面列出的修复方法需要时使用。

注意:下面所有的代码示例都在 github 仓库 中。您应该在顶层执行 mvn install 以完成所有设置。Maven 项目都布局为一个 Reactor 构建,但这只是为了方便。原则上,所有项目都可以独立构建和安装(如果它们的父项目的相对路径是固定的)。

问题所在

我们有一个父 pom,它对 Reactor (2.0.7) 进行了依赖管理。它通过一个 Maven 属性来实现这一点,即在父 pom 中

	<properties>
		<reactor.version>2.0.7.RELEASE</reactor.version>
	</properties>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>io.projectreactor</groupId>
				<artifactId>reactor-core</artifactId>
				<version>${reactor.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

然后我们有一个使用此父 pom 的库,它想使用较新版本的 Reactor,所以它这样做

	<properties>
		<reactor.version>2.5.0.BUILD-SNAPSHOT</reactor.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-core</artifactId>
		</dependency>
	</dependencies>

一切顺利。以下是 artifact 之间关系的摘要

parent (manages reactor:2.0.7)
\(child)- library
   \(depends on)- reactor:2.5.0

以及来自 Maven (3.3) 的实际 dependency:tree 输出

[INFO] com.example:example-library:jar:0.0.1-SNAPSHOT
[INFO] \- io.projectreactor:reactor-core:jar:2.5.0.BUILD-SNAPSHOT:compile
[INFO]    \- org.reactivestreams:reactive-streams:jar:1.0.0:compile

然后用户想编写一个依赖于该库的应用程序,并希望重用该父 pom,例如用于我们尚未包含在简单示例中的其他功能。他这样做了,然后发现(唉,哎呀),Reactor 版本出错了。该应用程序的关系可以总结如下

parent
\(child)- app
  \(depends on)- library

以及 Maven (3.3) 依赖报告如下所示

[INFO] com.example:example-app:jar:0.0.1-SNAPSHOT
[INFO] \- com.example:example-library:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- io.projectreactor:reactor-core:jar:2.0.7.RELEASE:compile
[INFO]       +- org.reactivestreams:reactive-streams:jar:1.0.0:compile
[INFO]       \- org.slf4j:slf4j-api:jar:1.7.12:compile

(即它的 Reactor 版本错误)。这是因为父 pom 对 Reactor 进行了依赖管理,而应用程序本身没有对 Reactor 进行显式依赖或依赖管理。在这种情况下,父 pom 总是优先,并且添加包含正确 Reactor 版本的 BOM(至少使用 Maven 3.3)也无济于事 - 只有明确指定 Reactor 本身的版本才能解决问题。

这与从 initializr 生成的用户应用程序具有相同的结构(层级更少),该应用程序依赖于使用 Reactor 2.5.0 的库。它面临所有相同的问题,以及相同的变通方法和修复选项。

变通方法和修复

选项 1: 在应用程序中显式管理 Reactor 依赖

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>io.projectreactor</groupId>
				<artifactId>reactor-core</artifactId>
				<version>2.5.0.BUILD-SNAPSHOT</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

这很繁琐,需要很多行 XML。请注意,在使用 Maven 3.3 时,使用包含此代码的 BOM 无济于事(但使用 Maven 3.4 时可以,请参见下文)。

$ cd app-1
$ ../mvn dependency:tree
...
[INFO] com.example:example-app-1:jar:0.0.1-SNAPSHOT
[INFO] \- com.example:example-library:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- io.projectreactor:reactor-core:jar:2.5.0.BUILD-SNAPSHOT:compile
[INFO]       \- org.reactivestreams:reactive-streams:jar:1.0.0:compile
...

注意:相同数量的 XML(实际上可能略少)也可用于在 POM 的 <dependencies> 部分中显式列出相同的依赖项。

选项 2: 通过属性在应用程序中仅显式管理 Reactor 版本

	<properties>
		<reactor.version>2.5.0.BUILD-SNAPSHOT</reactor.version>
	</properties>

这似乎相当可行,并且是一个简单的规则:如果您的项目或您的某个依赖项需要覆盖由父 POM 管理的传递性依赖项的版本,只需为该依赖项添加一个版本属性即可。要使此规则生效,父 POM 必须为其管理的所有依赖项定义版本属性(spring-boot-starter-parent 就这样做了)。

$ cd app-2
$ ../mvnw dependency:tree
...
[INFO] com.example:example-app-2:jar:0.0.1-SNAPSHOT
[INFO] \- com.example:example-library:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- io.projectreactor:reactor-core:jar:2.5.0.BUILD-SNAPSHOT:compile
[INFO]       \- org.reactivestreams:reactive-streams:jar:1.0.0:compile
...

选项 3: 使用包含新 Reactor 版本和 Maven 3.4 的 BOM。

即在应用程序中

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>com.example</groupId>
				<artifactId>example-bom</artifactId>
				<version>0.0.1-SNAPSHOT</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

Maven 3.4 尚未发布,但您可以从 仓库 获取,例如通过编辑应用程序项目中的 wrapper.properties 文件。此策略很好,因为它非常符合 Maven 依赖管理模型,但仅适用于尚未发布的 Maven 版本。

$ cd app-3
$ ./mvnw dependency:tree # N.B. Maven 3.4
...
[INFO] com.example:example-app-3:jar:0.0.1-SNAPSHOT
[INFO] \- com.example:example-library:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- io.projectreactor:reactor-core:jar:2.5.0.BUILD-SNAPSHOT:compile
[INFO]       \- org.reactivestreams:reactive-streams:jar:1.0.0:compile
...

注意:此项目的 wrapper.properties 已在撰写本文时配置为可用。您可能需要编辑版本标签以使其与最新的 snapshot 版本一起使用。

选项 4: 使用 Gradle(而不是 Maven)和包含新 Reactor 版本的 BOM。Gradle 没有父 pom,因为那是 Maven 的概念,可以使用 spring.io 插件应用可用 BOM 的依赖管理。

$ cd app-4
$ ./gradlew dependencies
...
compile - Dependencies for source set 'main'.
\--- com.example:example-library:0.0.1-SNAPSHOT
     \--- io.projectreactor:reactor-core:2.5.0.BUILD-SNAPSHOT
          \--- org.reactivestreams:reactive-streams:1.0.0
...

选项 5: 不使用该父 pom,并采用 Spring Cloud 使用的 *-dependencies 模型。如果该父 pom 包含您想重用的一些插件和属性声明,请将它们复制到一个新的父 pom 中,并将其用作您的应用程序父 POM。依赖管理可以在一个独立的 BOM 中声明,然后可以在您的应用程序 POM 的 <dependencyManagement> 部分中使用,如果它首先声明,它将优先于其他 BOM 中明确声明的任何内容。

示例代码中的 simple-parentbom 是以此方式拆分父 pom 的一个示例。然后 app-5 使用 simple-parent 作为父 pom,使用 bom 作为 BOM。

$ cd app-5
$ ../mvnw dependency:tree
...
[INFO] com.example:example-app-5:jar:0.0.1-SNAPSHOT
[INFO] \- com.example:example-library:jar:0.0.1-SNAPSHOT:compile
[INFO]    \- io.projectreactor:reactor-core:jar:2.5.0.BUILD-SNAPSHOT:compile
[INFO]       \- org.reactivestreams:reactive-streams:jar:1.0.0:compile
...

获取 Spring 新闻通讯

订阅 Spring 新闻通讯,保持连接

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获取支持

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

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部