🍃 Spring Boot 多模块项目中 Parent / BOM / Starter 的正确分工

avatar
莫雨IP属地:上海
02026-01-31:12:49:29字数 10006阅读 18

摆脱“依赖地狱”:一张图厘清职责,三步构建可维护的模块化架构


🌪️ 引言:当你的 pom.xml 出现这些“症状”...

<!-- 症状1:父POM继承链混乱 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>
<!-- 但团队要求必须继承公司统一父POM... -->

<!-- 症状2:依赖版本冲突 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <!-- 到底该写哪个版本? -->
</dependency>

<!-- 症状3:自研Starter被误用为BOM -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.mycompany</groupId>
            <artifactId>my-starter</artifactId> <!-- ❌ 错误! -->
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

💡 核心真相
Parent ≠ BOM ≠ Starter
混淆三者职责,是90%多模块项目依赖混乱的根源!
本文用架构图+代码实操+避坑清单,彻底厘清分工边界


📐 一、本质定位:三者职责全景图

flowchart TD
    A[项目根POM] -->|继承| B(Parent POM<br/>“项目骨架”)
    A -->|导入| C(BOM<br/>“依赖字典”)
    D[业务模块] -->|引入| E(Starter<br/>“功能料理包”)
    
    B --> B1[定义<br/>- 模块结构<br/>- 插件配置<br/>- 通用属性]
    C --> C1[管理<br/>- 依赖版本号<br/>- 传递性依赖]
    E --> E1[封装<br/>- 功能所需依赖集<br/>- 自动配置类]
    
    style B fill:#e6f7ff,stroke:#1890ff
    style C fill:#f6ffed,stroke:#52c41a
    style E fill:#fff7e6,stroke:#fa8c16

🔑 一句话定义

组件本质Maven角色核心职责类比
Parent POM项目骨架<parent> 继承统一构建配置、模块聚合🏗️ 建筑蓝图(规定楼层结构、水电标准)
BOM依赖字典<dependencyManagement> 导入仅管理版本号,不引入依赖📚 采购清单(规定“螺丝用M6",但不提供螺丝)
Starter功能料理包<dependency> 引入一站式引入功能所需全部依赖+自动配置🍱 料理包(含调料+菜谱,开袋即用)

黄金法则

  • Parent 管 “怎么构建”
  • BOM 管 “用什么版本”
  • Starter 管 “实现什么功能”

🧱 二、正确分工实战:企业级多模块项目结构

🌰 项目结构示例

my-enterprise-app/
├── pom.xml                          # 【根POM】聚合模块 + 导入BOM
├── bom/                             # 【BOM模块】依赖版本中枢
│   └── pom.xml
├── starter-common/                  # 【自研Starter】封装通用能力
│   ├── pom.xml
│   └── src/main/java/...AutoConfig
├── service-user/                    # 【业务模块】专注业务逻辑
│   └── pom.xml
└── service-order/
    └── pom.xml

✅ 正确配置三步走

步骤1:根POM(聚合 + 导入BOM)

<!-- my-enterprise-app/pom.xml -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycompany</groupId>
    <artifactId>my-enterprise-app</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    
    <!-- ✅ 关键:不继承spring-boot-starter-parent! -->
    <!-- 原因:企业项目需继承公司统一父POM(含安全扫描/发布流程等) -->
    
    <modules>
        <module>bom</module>
        <module>starter-common</module>
        <module>service-user</module>
    </modules>
    
    <dependencyManagement>
        <dependencies>
            <!-- 1. 导入Spring Boot官方BOM(管理Spring生态版本) -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.2.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- 2. 导入自研BOM(管理内部模块+第三方依赖版本) -->
            <dependency>
                <groupId>com.mycompany</groupId>
                <artifactId>my-bom</artifactId>
                <version>1.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

步骤2:BOM模块(依赖版本中枢)

<!-- bom/pom.xml -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.mycompany</groupId>
        <artifactId>my-enterprise-app</artifactId>
        <version>1.0.0</version>
    </parent>
    
    <artifactId>my-bom</artifactId>
    <packaging>pom</packaging>
    
    <dependencyManagement>
        <dependencies>
            <!-- 内部模块版本 -->
            <dependency>
                <groupId>com.mycompany</groupId>
                <artifactId>starter-common</artifactId>
                <version>${project.version}</version>
            </dependency>
            
            <!-- 第三方依赖版本(覆盖Spring Boot BOM) -->
            <dependency>
                <groupId>io.minio</groupId>
                <artifactId>minio</artifactId>
                <version>8.5.0</version> <!-- 公司统一版本 -->
            </dependency>
            
            <!-- 内部中间件SDK -->
            <dependency>
                <groupId>com.mycompany</groupId>
                <artifactId>auth-sdk</artifactId>
                <version>2.1.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

步骤3:业务模块(专注功能)

<!-- service-user/pom.xml -->
<project>
    <parent>
        <groupId>com.mycompany</groupId>
        <artifactId>my-enterprise-app</artifactId>
        <version>1.0.0</version>
    </parent>
    
    <artifactId>service-user</artifactId>
    
    <dependencies>
        <!-- ✅ 仅声明依赖,版本由BOM管理 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId> <!-- 无version! -->
        </dependency>
        
        <!-- 引入自研Starter(含自动配置) -->
        <dependency>
            <groupId>com.mycompany</groupId>
            <artifactId>starter-common</artifactId> <!-- 无version! -->
        </dependency>
        
        <!-- 内部SDK(版本由BOM锁定) -->
        <dependency>
            <groupId>com.mycompany</groupId>
            <artifactId>auth-sdk</artifactId>
        </dependency>
    </dependencies>
</project>

🚫 三、高频误区避坑指南(附修复方案)

误区现象根本原因✅ 修复方案
父POM继承链断裂公司要求继承company-parent,但Spring Boot配置丢失强行替换spring-boot-starter-parent导入BOM替代继承<scope>import</scope>导入spring-boot-dependencies
BOM被当Starter用业务模块引入BOM后仍需手动写版本号误将BOM放入<dependencies>BOM必须放在<dependencyManagement>中导入
Starter含业务代码my-starter模块包含Service/ControllerStarter职责混淆Starter仅含:- spring.factories/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports- 配置属性类- 依赖声明
版本号硬编码每个模块重复写<version>3.2.0</version>未建立BOM中枢所有版本收归BOM,业务模块禁止写version
循环依赖BOM依赖starter-common,starter-common又依赖BOM模块职责错位BOM只管理版本,不依赖任何业务模块

🌟 四、高阶实践:打造企业级依赖治理体系

🔒 场景1:公司强制父POM + Spring Boot 双继承

<!-- 根POM -->
<parent>
    <groupId>com.company</groupId>
    <artifactId>company-parent</artifactId> <!-- 含安全扫描/发布流程 -->
    <version>2.0.0</version>
</parent>

<dependencyManagement>
    <dependencies>
        <!-- 导入Spring Boot BOM(替代继承) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 导入公司BOM(覆盖冲突版本) -->
        <dependency>
            <groupId>com.company</groupId>
            <artifactId>company-bom</artifactId>
            <version>2.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

💡 原理:Maven依赖管理按声明顺序覆盖,后导入的BOM可覆盖前者的版本

🧩 场景2:自研Starter最佳实践

// starter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.mycompany.starter.common.LogAutoConfiguration
com.mycompany.starter.common.MetricsAutoConfiguration

// LogAutoConfiguration.java
@Configuration
@ConditionalOnClass(LoggerFactory.class)
@EnableConfigurationProperties(LogProperties.class)
public class LogAutoConfiguration {
    // 自动注册Bean...
}
<!-- starter-common/pom.xml -->
<dependencies>
    <!-- 仅声明依赖,版本由BOM管理 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
    </dependency>
</dependencies>

Starter设计原则

  • 命名规范:xxx-spring-boot-starter(官方) / xxx-starter(内部)
  • 无业务逻辑:仅封装基础设施能力(日志/监控/认证)
  • 条件化加载:用@ConditionalOn...避免污染环境

📋 五、决策 Checklist(发布前必查)

检查项正确做法错误示例
根POM是否继承spring-boot-starter-parent?❌ 企业项目应导入BOM强行替换公司父POM
业务模块是否出现?✅ 全部由BOM管理手动写<version>3.2.0</version>
BOM是否放在中?❌ 必须在<dependencyManagement>直接引入BOM导致无效果
Starter是否包含@Service/@Controller?❌ 仅含自动配置类把业务Service塞进Starter
自研BOM是否覆盖关键冲突?✅ 显式声明覆盖版本依赖Spring Boot默认版本导致冲突

💡 六、灵魂三问:快速定位问题

  1. “我的模块需要统一构建配置(如编译插件)吗?”
    → 需要:通过Parent POM继承
    → 不需要:仅导入BOM

  2. “我需要管理依赖版本但不想继承父POM吗?”
    → 是:用BOM导入<scope>import</scope>
    → 否:直接写版本号(不推荐)

  3. “我想封装一个开箱即用的功能包吗?”
    → 是:创建Starter(含自动配置+依赖声明)
    → 否:普通工具模块即可


🌱 结语:架构的优雅,在于职责的清晰

Parent 是骨架,BOM 是字典,Starter 是积木

  • 骨架支撑结构,字典规范用词,积木构建功能
  • 混淆三者,如同用字典当积木、用骨架查字典——系统必然崩坏

真正的工程素养,体现在:
🔹 克制:不在Starter塞业务代码
🔹 抽象:将版本管理收归BOM中枢
🔹 尊重:理解Maven设计哲学而非“能跑就行”

📌 行动建议
1️⃣ 打开你的多模块项目,检查根POM是否误用继承
2️⃣ 创建bom模块,将所有<version>迁移至其中
3️⃣ 重构一个自研Starter,剥离业务逻辑,仅保留自动配置

清晰的依赖治理,是大型项目可维护性的第一道护城河。
今日厘清Parent/BOM/Starter,明日拥抱从容架构。 🌿


📎 附录:速查表

场景推荐方案Maven片段
企业项目(需继承公司父POM)导入Spring Boot BOM<scope>import</scope>
纯Spring Boot新项目继承spring-boot-starter-parent<parent>...</parent>
管理内部模块版本创建自研BOM模块<dependencyManagement>
封装通用能力创建自研Starterxxx-starter + 自动配置
解决版本冲突在自研BOM中显式覆盖声明在Spring Boot BOM之后

🔗 权威参考

总资产 0
暂无其他文章

热门文章

暂无热门文章