走在前沿
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 客户端 的传递依赖需要 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-parent
和bom
是按这种方式拆分父级的示例。然后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
...