架构设计时,如何通过 maven or Gradle 来制作一个 BOM 管理jar依赖版本
一个中大型的 Java 基础框架项目往往包含许多 JAR 包,如果将这些 JAR 包单独发布给业务开发使用,这些不同版本 JAR 包间的兼容性维护将变得十分麻烦。为了解决这个问题,可以让一个特殊的模块引用这些 JAR 包,将一组 JAR 包兼容的版本号定义在这个模块中,对外发布时只发布这个特殊模块。这个特殊模块就是 BOM(Bill Of Materials)。
著名的 Spring Boot 就使用了这种方式来管理版本号,这个模块就是 spring-boot-dependencies,用户在使用 Spring Boot Starter 相关依赖时引入特定版本的 spring-boot-dependencies,然后在引入其它依赖时只需要声明 group 和 name 即可,不需要再指定版本号了。当然,在 Gradle 中使用 Spring Boot 插件,或者在 Maven 中使用 spring-boot-starter-parent 作为父模块也能够达到类似的效果。
本文将介绍如何通过 Gradle 来制作一个 BOM 以及如何在 Gradle 中使用 BOM。作为 Maven 中的一个概念,也可以使用 Maven 也可以制作和使用 BOM,但本文不涉及。
1. BOM 介绍
BOM (Bill Of Material) 是 Maven 仓库中的一个概念,它本质也是一个可被引用的包,但不包含代码,只是声明了一系列其它包。例如:Maven 中央仓库中的 spring-boot-dependencies](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/2.4.4/) 包。它只有一个 .pom 文件。
下面是 Maven 官网上的一个简单的 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 文件中的声明不同的是, 节点外面还包含了一层 节点。以上就是 BOM 包中最核心的文件的基本结构了;基于 Gradle 发布 BOM 包的本质就是生成这样的一个文件。
2. 使用 Gradle 制作一个 BOM
这里我们假定要创建一个 BOM,用来统一管理三方 Java 包,其它业务模块通过引用这个 BOM 来间接引用需要使用的第三方 Java 包。工程完整代码:https://github.com/Robothy/gradle-bom-example
2.1 创建 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>
<!--开源协议...-->
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<!--开发者信息-->
<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>
<!--发布到中央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 里面。这表示如果使用了其中的包,优先使用 constraints 中声明的版本。
BOM 项目中声明包的方式有两种:
api 表示包在编译期可见。
runtime 表示包在运行期间可见。
2.2 BOM 的发布
BOM 的发布需要使用 maven-publish 插件,其发布配置如下:
publishing {
publications {
thirdPartPlatform(MavenPublication){
from components.javaPlatform
artifactId = "third-part-dependencies"
}
}
repositories {
mavenLocal()
}
}
BOM 的命名一般以 -dependencies 结尾,这里我们取名为third-part-dependnecies。
执行./gradlew.bat publish 就可以将 BOM 发布到本地的 Maven 仓库了。发布的 artifacts 包含两个主要文件(.pom 和 .module)和若干校验文件。其中 .pom 的文件内容为 Maven 官方定义的 BOM 的标准格式,而 .module 文件内容是 Gradle 描述元数据的一种格式。
2.3 BOM 的使用
普通的 Java 应用或者 Java 库使用 BOM 的时候需要先添加 BOM 依赖,然后使用其它的库。例如:
// 引入 BOM
implementation platform("org.example:third-part-dependencies:1.0")
// 引入包,这时不需要再指定版本号
implementation "org.apache.kafka:kafka-clients"
当然,BOM 工程或者说 platform 工程也可以使用 BOM。
使用的时候需要在 dependencies 下面引入 BOM,然后在 constraints 下面声明要使用的库,声明的时候无须指定版本。另外,需要在 configurations 中调用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 {
// 详细信息参考 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 {
//详细信息参考 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>
<!-- 配置打包插件(设置主类,并打包成胖包) -->
<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 函数类) -->
<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知路
欢迎关注微信公众号 :java知路