聊一聊BOM:多模块的依赖版本管理

366 阅读6分钟

大家好,我是G探险者!

最近项目上在整理各个组件的POM依赖,试图通过一个父级POM来管理整个项目上使用到的各种开源的,和自研的依赖。目的是为了规范使用我们框架的应用,不私自在自身项目里面乱定版本,导致各种版本冲突不兼容的问题。

在这个整理的过程中我们也遇到了一些坑,比如有的组件里面使用了netty,中的某个依赖包,另外的组件使用到了netty的另外的依赖包,那么我们在统一管理netty这些依赖的时候,维护了的netty的依赖版本就有点五花八门,导致应用在使用我们的这个父级POM时,出现了一些奇奇怪怪的版本不兼容问题。

于是引入了BOM这个概念,今天我们就来聊一聊BOM是如何进行多模块项目的依赖管理的。

BOM 的由来

BOM(Bill of Materials,材料清单)最早的概念来源于制造业,用于描述构建产品所需的组件清单。在软件开发领域,BOM 被用于列出某个项目或工具的所有依赖及其版本信息,确保这些依赖模块能够兼容协作。

在 Java 的依赖管理工具中,如 Maven 和 Gradle,BOM 解决了模块化项目中版本管理的复杂性问题。许多大型项目(如 Spring Framework、Jetty、netty,springcloud)随着功能的扩展和模块化设计,逐步拆分为多个子模块。每个子模块可能独立演进,但它们之间需要确保版本一致和兼容性。于是引入了 BOM 来统一管理这些模块的版本。

BOM 的作用

  1. 统一版本管理

    • BOM 是一个特殊的 Maven POM 文件(pom.xml),它列出了多个相关模块及其推荐的版本。
    • 使用 BOM 后,开发者无需在每个依赖声明中显式指定版本,避免版本冲突和错误。

    示例

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-server</artifactId>
                <version>11.0.15</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-servlet</artifactId>
                <version>11.0.15</version>
            </dependency>
            <!-- 其他模块省略 -->
        </dependencies>
    </dependencyManagement>
    
  2. 避免版本冲突

    • 在大型项目中,不同模块可能依赖同一个库的不同版本。BOM 确保所有模块使用相同版本,从而避免潜在冲突。
  3. 简化依赖声明

    • 在 Maven 中,通过引入 BOM 后,子模块的版本可以被自动解析。

    示例

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-bom</artifactId>
                <version>11.0.15</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    

    子模块中使用依赖时不再需要显式声明版本:

    <dependencies>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
        </dependency>
    </dependencies>
    
  4. 提高维护效率

    • BOM 集中管理所有模块的版本,可以在 BOM 文件中一次性升级所有相关依赖版本,降低手动管理的复杂性。
  5. 明确兼容性

    • BOM 通常由项目开发团队发布,确保其中列出的模块版本是经过测试、互相兼容的。

BOM 的使用场景

  1. 多模块项目

    • 适用于一个大项目分拆成多个模块(如 Jetty),开发团队可以提供 BOM 供用户引用,避免用户在不同模块间手动匹配版本。
  2. 集成框架

    • 像 Spring Boot 提供了自己的 BOM,管理 Spring 框架及常用第三方库的版本。开发者只需选择一个 BOM 文件即可。

    Spring Boot 示例

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  3. 企业级项目

    • 企业内部开发的框架或工具库,可以用 BOM 提供统一的依赖管理,提升团队协作效率。

家族式依赖和非家族式依赖

非家族式依赖顾名思义,就是它没有过多的相关联的依赖包,在维护时只需要一个依赖即可,比如mysql的驱动包

所谓家族式依赖就是指的是,像jetty,netty,springcloud等这样的依赖,比如jetty家族:

依赖包功能描述依赖模块
核心组件
jetty-util提供通用工具库(线程池、集合工具、日志等)。无直接依赖
jetty-io异步 IO 操作支持,底层 IO 模块。jetty-util
jetty-http处理 HTTP 协议解析和生成。jetty-io
jetty-serverJetty 核心服务器模块,处理 HTTP 请求与响应。jetty-http, jetty-io, jetty-util
jetty-servlet提供 Servlet 容器支持。jetty-server
高级功能模块
jetty-security提供认证和授权功能。jetty-server, jetty-util
jetty-proxy实现反向代理功能。jetty-server
jetty-websocket-serverWebSocket 服务端支持。jetty-server, jetty-websocket-common
jetty-websocket-clientWebSocket 客户端支持。jetty-client, jetty-websocket-common
集成模块
jetty-servlets提供扩展的 servlet 功能和过滤器支持。jetty-servlet
jetty-jndi提供 JNDI 支持。jetty-server, jetty-util
jetty-alpn支持 HTTP/2 中的 ALPN 协议。jetty-server, jetty-io
测试与开发工具
jetty-annotations支持基于注解的配置(如 Servlet 3.0 注解)。jetty-servlet
jetty-test-helper提供测试工具,便于编写测试用例。jetty-server
jetty-distribution提供完整的 Jetty 服务器分发包,适合快速运行与调试。包含多个核心模块

netty家族:

模块artifactId功能描述
Netty BOMnetty-bom包含所有 Netty 相关模块的版本定义,用于依赖版本管理
Netty Commonnetty-common提供 Netty 的常用工具类,例如线程池、日志、时间处理等
Netty Transportnetty-transport提供高性能的网络传输功能,包括 NIO、EPOLL、KQueue 等支持
Netty Codecnetty-codec提供对各种协议的编码解码支持(如 HTTP、HTTP/2、WebSocket 等)
Netty Handlernetty-handler提供常见的 I/O 事件处理程序(例如 HTTP 请求处理、SSL/TLS 加密、WebSocket 等)
Netty Buffernetty-buffer提供 Netty 的内存缓冲区 API
Netty Codecs HTTPnetty-codec-http提供 HTTP 协议的编码解码器支持
Netty Codecs SSLnetty-codec-ssl提供 SSL/TLS 编码解码支持
Netty Resolvernetty-resolver提供 DNS 解析、服务发现等功能
Netty Handler Proxynetty-handler-proxy提供代理协议(如 HTTP 代理)的支持
Netty Handler SSLnetty-handler-ssl提供 SSL/TLS 协议的处理程序
Netty Transport Epollnetty-transport-epoll支持基于 EPOLL 的传输机制(Linux 环境)
Netty Transport KQueuenetty-transport-kqueue支持基于 KQueue 的传输机制(macOS 环境)

这些家族式的依赖如果维护到一个父级pom里,应该如何更好的维护到一个POM里面呢,这就要发挥BOM的作用了。

以下我提供一个示例:

<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.example</groupId>
    <artifactId>parent-pom</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <properties>
        <!-- 家族依赖的版本管理 -->
        <spring.boot.version>2.6.3</spring.boot.version>
        <spring.cloud.version>2021.0.0</spring.cloud.version>
        <netty.version>4.1.72.Final</netty.version>
        <jetty.version>11.0.1</jetty.version>
        <hutool.version>5.8.5</hutool.version>

        <!-- 非家族依赖的版本管理 -->
        <mysql.version>8.0.27</mysql.version>
        <commons.lang.version>3.12.0</commons.lang.version>
        <log4j.version>2.17.0</log4j.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- 引入 Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 引入 Spring Cloud BOM -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 引入 Netty BOM -->
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-bom</artifactId>
                <version>${netty.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 引入 Jetty BOM -->
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-bom</artifactId>
                <version>${jetty.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 其他家族性质依赖管理 -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>

            <!-- 非家族依赖 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons.lang.version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
                <version>${log4j.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
  1. 非家族依赖的版本管理:在 <dependencyManagement> 中加入了 mysql-connector-javacommons-lang3log4j-api 这些非家族性质的依赖。这样可以确保所有模块使用一致的版本,无需每个子模块都指定版本。

  2. BOM 和常规依赖统一管理:现在无论是家族依赖(如 Spring Boot、Spring Cloud 等),还是常规依赖(如 MySQL、Commons、Log4j 等),都在 dependencyManagement 中进行了版本管理。

  3. 集中版本管理:所有的版本号都通过 ${property.name} 统一管理,保证了依赖的版本一致性,方便在不同模块间共享和更新版本。

以上这种方式不仅集中管理了所有依赖的版本,还确保了无论是家族性质的依赖,还是其他常规依赖,都能在一个地方进行统一的版本控制。