提起BOM,可能一部分很多小伙伴会感到陌生,没关系,开始之前,我会先对Maven BOM做一个简单的科普。
Maven BOM科普
TL;DR: Maven BOM (Bill of Materials POMs)是一种POM,引用它,可以对项目的依赖版本、插件等其他构建配置做统一管理。
BOM,是Bill of Materials POMs的缩写。所以,它本质上也是一种Maven 的POM定义。严格的说,在Maven领域里,我们应该叫他BOM POM,但是为了简便,在下面的文章中,我们还是直接叫他BOM。 说起BOM的用途,其实也很简单,按字面意思理解就行,即材料清单。
下面,我以一个响当当的实际应用来解释BOM的用途。相信所有使用过Spring Boot的小伙伴,都会很熟悉一个东西,就是spring-boot-starter-parent:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
</parent>
包括Spring Boot官方的教程在内的大部分教程,开篇先讲的,就会是把这个依赖,作为项目的parent。我们来简单的看一下这个依赖中都包括哪些内容:
<?xml version="1.0" encoding="utf-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.8.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<name>Spring Boot Starter Parent</name>
<description>Parent pom providing dependency and plugin management for applications
built with Maven</description>
<url>https://projects.spring.io/spring-boot/#/spring-boot-starter-parent</url>
<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<maven.compiler.source>${java.version}</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<build>
<resources>
<!--省略资源管理定义-->
</resources>
<pluginManagement>
<plugins>
<!--省略插件管理定义-->
</plugins>
</pluginManagement>
</build>
</project>
简单地说,这个spring-boot-starter-parent主要有以下功能:
- 为项目提供统一的依赖版本管理(提供
Maven BOM来实现) - 为项目提供其他的构建配置(
插件管理、资源目录定义等)
后者我们在本文中就暂时不做介绍了,各位小伙伴可以自行在网上查阅资料。重点说下这个parent具备的第一个功能:即通过提供Maven BOM,为项目提供统一的依赖版本管理。
从这个spring-boot-starter-parent的POM内容,其实我们可以看到,它还有了一个*<parent>*,即spring-boot-dependencies,这个parent,其实就是一个BOM了。查看它的内容,我们可以看到它定义的所有依赖版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<!--省略余下的其他依赖-->
</dependencies>
</dependencyManagement>
Spring家族发展到今天,已经包含了很多项目,例如spring-aop,spring-web等。对于Spring Boot项目来说,还包括很多的starter。通常来说,一个Spring Boot项目,需要依赖若干的starter包。为了保证所有的starter正常工作,不罢工的话:我们就需要保证他们的版本,以及他们所有的传递依赖的版本,都能完美契合。因此,Spring给我们提供了一个现成的BOM,我们把他作为Parent的话,就可以让我们愉快的基于Spring Boot,开发自己应用。
使用Gradle生成Maven BOM
上一小节我们已经对BOM有了初步认识,他的功能很多。但我们今天会把注意力,集中在依赖版本管理上。假如,我们需要提供这样一个BOM给组织内的其他项目使用,用于管理依赖版本。直接新建一个Maven模块,并增加一个pom.xml发布到私服,供大家引用当然是可以的。不过,我们需要手写整个xml。(相信没人会喜欢手写反人类的xml吧...)
这个事,假如我们使用Gradle来做,事情会简单很多。首先,新建一个Gradle项目,下面我以Kotlin DSL为例:
我们需要借助Gradle的java-platform插件,在build.gradle.kts中写入:
plugins {
`java-platform`
}
为第三方依赖发布BOM
对于第三方依赖,使用dependencies.constraints定义依赖版本即可:
// 引入插件
plugins {
`java-platform`
}
group = "cn.hongyu"
version = "3.1.1-RELEASE"
// 定义依赖版本
dependencies{
constraints {
api("com.external:web-starter:2.0.1")
api("com.external:web-commons:1.2.0")
api("com.external:web-document-spring-boot-starter:1.0.0")
}
}
// 定义发布配置
publishing {
// 定义发布项目
publications {
create<MavenPublication>("externalDepPom") {
from(components["javaPlatform"])
}
}
// 仓库定义(可以发布到Nexus私服等)
repositories {
// 省略仓库配置
}
}
大功告成,运行gradle publishToMavenLocal我们就可以在本地仓库找到生成的Maven BOM文件。
为多模块项目发布BOM
还有一种场景是,我们有一个多模块项目,该项目会有多个library包提供给别人使用。为了保证别人在使用这些library时,不会因为其他依赖的传递依赖导致问题。我们最好提供一个BOM文件供别人引用。
为了让大家理解在不提供BOM时可能导致的问题,这里举一个例子简单说明下。假设我有一个多模块项目,它的结构如下:
MyProject-2.0
├─interface-2.0
├─lib1-2.0
项目A依赖了该项目的lib1-2.0,而lib1-2.0要依赖interface-2.0中新增的一个类才能正常工作。刚刚好,因为A项目依赖的某个依赖,传递依赖了interface-1.0版本。最终,A项目可能会正常打包,却在运行时抛出了ClassNotFound异常。因为项目A最终依赖的是interface-1.0,而不是lib1-2.0所需的interface-2.0!
实际的项目中,会比这个情况更复杂。让库的使用者,去知道并定义所有的传递依赖版本,显然不现实。因此,我们可以提供一个BOM来简化库的使用。
使用Gradle,我们可以很方便的做到这一点。我们可以在项目中加入一个bom项目,现在,项目的结构为:
MyProject-2.0
├─interface-2.0
├─lib1-2.0
├─bom
在bom项目的build.gradle.kts文件中,我们在dependencies.constraints中直接定义:
dependencies{
constraints {
api(project(":interface"))
api(project(":lib1"))
}
}
OK了,我们直接引用了本项目中的其他两个子模块。生成的BOM中,将会直接使用这两个子模块的版本。
因为build.gradle.kts文件是一个脚本,所以甚至我们还可以进一步简化为:
dependencies{
constraints {
rootProject.allprojects
// 忽略根项目
.filterNot { it.path == ":" }
// 将其余项目都加到BOM中
.forEach { api(project(it.path)) }
}
}
我们直接从rootProject.allprojects中获取到了所有项目,并将他们批量加入了BOM定义中!之后,再添加新的模块,我们都不需要更改配置了,即可再新发布的BOM文件中自动加入该模块的依赖版本定义。
总结
Gradle是非常强大的构建工具,灵活的使用它,将可以给我们的工作带来很多便利。在本次的应用中,我们使用它为我们生成了可以被Maven消费的BOM文件,比起我们手写BOM来说,节省的时间不是一点点!另外,生成的BOM也是可以被Gradle项目消费的,可谓是一举两得。
如果大家喜欢我的分享,欢迎给我点赞关注。我将在未来分享更多原创的编程经验 ~