领先一步
VMware 提供培训和认证,助您加速进步。
了解更多本文解释了一些可用于创建库和应用程序的依赖管理技巧,这些库和应用程序依赖于比 Spring Boot 或 Spring 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-parent
和 bom
是以此方式拆分父 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
...