使用Gradle轻松生成可被Maven消费的版本管理BOM

3,065 阅读5分钟

提起BOM,可能一部分很多小伙伴会感到陌生,没关系,开始之前,我会先对Maven BOM做一个简单的科普。

Maven BOM科普

TL;DR: Maven BOM (Bill of Materials POMs)是一种POM,引用它,可以对项目的依赖版本插件等其他构建配置做统一管理。

BOM,是Bill of Materials POMs的缩写。所以,它本质上也是一种MavenPOM定义。严格的说,在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项目消费的,可谓是一举两得。

如果大家喜欢我的分享,欢迎给我点赞关注。我将在未来分享更多原创的编程经验 ~