架構(gòu)設(shè)計時,如何通過 maven or Gradle 來制作一個 BOM 管理jar依賴版本

      一個中大型的 Java 基礎(chǔ)框架項目往往包含許多 JAR 包,如果將這些 JAR 包單獨發(fā)布給業(yè)務(wù)開發(fā)使用,這些不同版本 JAR 包間的兼容性維護(hù)將變得十分麻煩。為了解決這個問題,可以讓一個特殊的模塊引用這些 JAR 包,將一組 JAR 包兼容的版本號定義在這個模塊中,對外發(fā)布時只發(fā)布這個特殊模塊。這個特殊模塊就是 BOM(Bill Of Materials)。

      著名的 Spring Boot 就使用了這種方式來管理版本號,這個模塊就是 spring-boot-dependencies,用戶在使用 Spring Boot Starter 相關(guān)依賴時引入特定版本的 spring-boot-dependencies,然后在引入其它依賴時只需要聲明 group 和 name 即可,不需要再指定版本號了。當(dāng)然,在 Gradle 中使用 Spring Boot 插件,或者在 Maven 中使用 spring-boot-starter-parent 作為父模塊也能夠達(dá)到類似的效果。

本文將介紹如何通過 Gradle 來制作一個 BOM 以及如何在 Gradle 中使用 BOM。作為 Maven 中的一個概念,也可以使用 Maven 也可以制作和使用 BOM,但本文不涉及。

1. BOM 介紹

BOM (Bill Of Material) 是 Maven 倉庫中的一個概念,它本質(zhì)也是一個可被引用的包,但不包含代碼,只是聲明了一系列其它包。例如:Maven 中央倉庫中的 spring-boot-dependencies](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/2.4.4/) 包。它只有一個 .pom 文件。

下面是 Maven 官網(wǎng)上的一個簡單的 BOM 的.pom文件:

<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>
  <groupId>com.test</groupId>
  <artifactId>bom</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>
  <properties>
    <project1Version>1.0.0</project1Version>
    <project2Version>1.0.0</project2Version>
  </properties>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project1</artifactId>
        <version>${project1Version}</version>
      </dependency>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project2</artifactId>
        <version>${project2Version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
 
  <modules>
    <module>parent</module>
  </modules>
</project>

這個文件聲明了兩個包(project1 和 project2)及其版本號,和一般 .pom 文件中的聲明不同的是, 節(jié)點外面還包含了一層 節(jié)點。以上就是 BOM 包中最核心的文件的基本結(jié)構(gòu)了;基于 Gradle 發(fā)布 BOM 包的本質(zhì)就是生成這樣的一個文件。

2. 使用 Gradle 制作一個 BOM

這里我們假定要創(chuàng)建一個 BOM,用來統(tǒng)一管理三方 Java 包,其它業(yè)務(wù)模塊通過引用這個 BOM 來間接引用需要使用的第三方 Java 包。工程完整代碼:https://github.com/Robothy/gradle-bom-example

2.1 創(chuàng)建 BOM 工程

Gradle 中的 BOM 工程需要使用java-platform插件,這樣的工程是一個不包含源代碼,只包含包聲明的特殊的組件,也被稱為平臺(platform)。

build.gradle 部分代碼

plugins {
    id 'java-platform'
}

dependencies {
    constraints {
        // 聲明一些三方包及其版本號
        api "org.apache.kafka:kafka-clients:2.6.0"
        api "redis.clients:jedis:3.5.2"
    }
}
maven 代碼

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.github.stylesmile</groupId>
    <artifactId>fastboot-parent</artifactId>
    <version>0.1.8-m1</version>
    <name>fastboot-parent</name>
    <packaging>pom</packaging>
    <properties>
        <java.version>1.8</java.version>
        <!--        <fastboot.version>0.1.1-snapshots</fastboot.version>-->
        <fastboot.version>0.1.8-m1</fastboot.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.github.stylesmile</groupId>
                <artifactId>fastboot-core</artifactId>
                <version>0.1.8-m1</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.9</version>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.9.0</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.24</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>compile</scope>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <description>java web project for Fast Boot</description>
    <modules>
        <!--        <module>fastboot-parent</module>-->
        <module>fastboot-core</module>
    </modules>
    <!--項目信息...-->
    <url>https://github.com/stylesmile/fastboot</url>

    <!--開源協(xié)議...-->
    <licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>

    <!--開發(fā)者信息-->
    <developers>
        <developer>
            <id>stylesmile</id>
            <name>Stylesmile</name>
            <email>3239866994@qq.com</email>
            <roles>
                <role>Project Manager</role>
                <role>Architect</role>
            </roles>
            <timezone>+8</timezone>
        </developer>
    </developers>

    <!--項目在github或其它托管平臺的地址-->
    <scm>
        <connection>https://github.com/stylesmile/fastboot.git</connection>
        <developerConnection>scm:git:ssh://git@github.com:stylesmile/fastboot.git</developerConnection>
        <url>https://github.com/stylesmile/fastboot</url>
    </scm>
    <profiles>
        <profile>
            <!--注意,此id必須與setting.xml中指定的一致,不要自作聰明改它名字-->
            <id>ossrh</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <!--發(fā)布到中央SNAPSHOT倉庫插件-->
                <plugins>

                    <!-- GPG -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.5</version>
                        <executions>
                            <execution>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>


                    <!-- Source -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <version>3.0.1</version>
                        <configuration>
                            <attach>true</attach>
                        </configuration>
                        <executions>
                            <execution>
                                <phase>compile</phase>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <!-- Javadoc -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <version>2.10.3</version>
                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                                <configuration>
                                    <additionalparam>-Xdoclint:none</additionalparam>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.sonatype.plugins</groupId>
                        <artifactId>nexus-staging-maven-plugin</artifactId>
                        <version>1.6.7</version>
                        <extensions>true</extensions>
                        <configuration>
                            <serverId>ossrh</serverId>
                            <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
                            <autoReleaseAfterClose>true</autoReleaseAfterClose>
                        </configuration>
                    </plugin>
                </plugins>
            </build>

            <distributionManagement>
                <snapshotRepository>
                    <!--注意,此id必須與setting.xml中指定的一致-->
                    <id>ossrh</id>
                    <name>snapshots</name>
                    <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
                    <!--                    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>-->

                </snapshotRepository>
                <repository>
                    <id>ossrh</id>
                    <name>releases</name>
                    <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
                    <!--                    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>-->
                </repository>
            </distributionManagement>
        </profile>

    </profiles>
    <repositories>
        <repository>
            <id>sonatype-snapshots</id>
            <name>snapshots</name>
            <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
        </repository>
        <repository>
            <id>sonatype-releases</id>
            <name>snapshots</name>
            <url>https://s01.oss.sonatype.org/content/repositories/releases/</url>
        </repository>
        <repository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo2.maven.org/maven2/</url>
        </repository>
        <repository>
            <id>ali</id>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>ali</id>
            <url>https://maven.aliyun.com/repository/public</url>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <compilerArgument>-parameters</compilerArgument>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

上面代碼中,三方包的聲明沒有放在 dependencies中,而是放在了 constraints 里面。這表示如果使用了其中的包,優(yōu)先使用 constraints 中聲明的版本。

BOM 項目中聲明包的方式有兩種:

api 表示包在編譯期可見。

runtime 表示包在運行期間可見。






2.2 BOM 的發(fā)布

BOM 的發(fā)布需要使用 maven-publish 插件,其發(fā)布配置如下:

publishing {
    publications {
        thirdPartPlatform(MavenPublication){
            from components.javaPlatform
            artifactId = "third-part-dependencies"
        }
    }
    repositories {
        mavenLocal()
    }
}

BOM 的命名一般以 -dependencies 結(jié)尾,這里我們?nèi)∶麨閠hird-part-dependnecies。

執(zhí)行./gradlew.bat publish 就可以將 BOM 發(fā)布到本地的 Maven 倉庫了。發(fā)布的 artifacts 包含兩個主要文件(.pom 和 .module)和若干校驗文件。其中 .pom 的文件內(nèi)容為 Maven 官方定義的 BOM 的標(biāo)準(zhǔn)格式,而 .module 文件內(nèi)容是 Gradle 描述元數(shù)據(jù)的一種格式。

2.3 BOM 的使用

普通的 Java 應(yīng)用或者 Java 庫使用 BOM 的時候需要先添加 BOM 依賴,然后使用其它的庫。例如:

// 引入 BOM

implementation platform("org.example:third-part-dependencies:1.0")
// 引入包,這時不需要再指定版本號

implementation "org.apache.kafka:kafka-clients"

當(dāng)然,BOM 工程或者說 platform 工程也可以使用 BOM。

使用的時候需要在 dependencies 下面引入 BOM,然后在 constraints 下面聲明要使用的庫,聲明的時候無須指定版本。另外,需要在 configurations 中調(diào)用javaPlatform.allowDependencies(),否則會報錯。

gradle 方式1

plugins {
    id 'java'
//    id 'java-platform'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    maven {url "https://s01.oss.sonatype.org/content/repositories/releases/"}
    maven {url "https://repo2.maven.org/maven2/"}
    mavenCentral()
}

jar {
    // 詳細(xì)信息參考 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html
    archivesBaseName = 'fastboot'//基本的文件名
    archiveVersion = '0.0.3' //版本
    manifest { //配置jar文件的manifest
        attributes(
                "Manifest-Version": 1.0,
                'Main-Class': 'com.example.Application' //指定main方法所在的文件
        )
    }
    //打包依賴包
    from {
        (configurations.runtimeClasspath).collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
}

dependencies {
    // 引入 BOM
    implementation platform("io.github.stylesmile:fastboot-parent:0.1.8-m1")
    implementation 'io.github.stylesmile:fastboot-core'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

test {
    useJUnitPlatform()
}
gradle 方式2

plugins {
    id 'java'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

group 'org.example'
version '1.0-SNAPSHOT'
repositories {
    maven {url "https://s01.oss.sonatype.org/content/repositories/releases/"}
    maven {url "https://repo2.maven.org/maven2/"}
    mavenCentral()
}
jar {
    //詳細(xì)信息參考 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html
    archivesBaseName = 'fastboot-demo'//基本的文件名
    archiveVersion = '0.0.1' //版本
    manifest { //配置jar文件的manifest
        attributes(
                "Manifest-Version": 1.0,
                'Main-Class': 'com.example.Application' //指定main方法所在的文件
        )
    }
    //打包依賴包
    from {
        (configurations.runtimeClasspath).collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
}

dependencies {
    implementation 'io.github.stylesmile:fastboot-core'
}
dependencyManagement {
    imports {
        mavenBom 'io.github.stylesmile:fastboot-parent:0.1.8-m1'
    }
}
tasks.named('test') {
    useJUnitPlatform()
}

maven引用bom方式

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.stylesmile</groupId>
    <artifactId>fastboot-example</artifactId>
    <version>0.1.6</version>
    <name>fastboot-example</name>
    <description>Demo project for Fast Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
<!--        <parent>-->
<!--            <groupId>io.github.stylesmile</groupId>-->
<!--            <artifactId>fastboot-parent</artifactId>-->
<!--            <version>0.1.6</version>-->
<!--        </parent>-->

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.github.stylesmile</groupId>
                <artifactId>fastboot-parent</artifactId>
                <version>0.1.7-M2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.github.stylesmile</groupId>
            <artifactId>fastboot-core</artifactId>
        </dependency>
    </dependencies>
    <repositories>
<!--        <repository>-->
<!--            <id>ali</id>-->
<!--            <url>https://maven.aliyun.com/repository/public/</url>-->
<!--        </repository>-->
        <repository>
            <id>maven1</id>
            <url>https://s01.oss.sonatype.org/content/repositories/releases/</url>
        </repository>
        <repository>
            <id>maven2</id>
            <url>https://repo1.maven.org/maven2/</url>
        </repository>
    </repositories>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <!-- 配置編譯插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <compilerArgument>-parameters</compilerArgument>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <!-- 配置打包插件(設(shè)置主類,并打包成胖包) -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <finalName>${project.artifactId}</finalName>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <!-- 此處,要改成自己的程序入口(即 main 函數(shù)類) -->
                        <manifest>
                            <mainClass>com.example.Application</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

代碼地址

https://gitee.com/stylesmile/fastboot/tree/master/fastboot-example

作者:java知路


歡迎關(guān)注微信公眾號 :java知路