使用 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 客户端 的传递依赖需要 Reactor 2.5。它的父级拥有(或应该拥有)一个*-dependencies BOM,可以在下面列出的修复中需要的地方使用。

注意:以下所有代码示例均位于 github 存储库 中。您应该在顶层执行mvn install以完成所有设置。Maven 项目都以反应堆构建的形式布局,但这仅仅是为了方便。原则上,所有项目都可以独立构建和安装(如果其父级的相对路径已固定)。

问题

我们有一个父 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>

然后我们有一个带有此父级的库,它希望使用更新版本的 Reactor,所以它这样做

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

每个人都很开心。以下是工件之间关系的总结

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

以及 Maven (3.3) 的实际依赖关系树

[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

然后用户想要编写一个依赖于该库的应用,并希望重新使用父级,例如,对于我们未包含在简单示例中的其他功能。他这样做了,发现(糟糕,糟糕),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)。这是因为父级对 Reactor 进行了依赖管理,并且应用本身没有显式依赖或对 Reactor 进行依赖管理。在这种情况下,父级始终优先,并且添加具有正确 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 行。请注意,使用包含此代码的 BOM 与 Maven 3.3 无济于事(但与 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 版本的 BOM 和 Maven 3.4。

即在应用中

	<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已设置为在编写时工作。您可能需要编辑版本标签才能使其与最新的快照一起使用。

选项 4:使用 Gradle(而不是 Maven)和具有新 Reactor 版本的 BOM。没有父级,因为那是 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:不要使用该父级,并采用 Spring Cloud 使用的*-dependencies模型。如果父级有一些您想要重用的插件和属性声明,请将它们复制到一个新的父级中,并将其用作您的应用父 POM。依赖管理可以在一个独立的 BOM 中声明,然后可以在应用 POM 的<dependencyManagement>部分中使用,并且如果它首先声明,它将优先于它显式声明的任何其他 BOM。

示例代码中的simple-parentbom是按这种方式拆分父级的示例。然后app-5使用simple-parent作为父级,使用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 社区中所有即将举行的活动。

查看全部