Spring Boot 多模块项目最佳实践:打造清晰、可维护的微服务骨架
在中大型 Java 项目中,单体应用拆分为多模块(multi-module)结构已成为标准实践。它不仅能提升代码复用性,还能明确职责边界、加速编译、支持团队并行开发。
但很多团队在搭建 Spring Boot 多模块项目时,常陷入混乱:模块划分不合理、依赖循环、打包失败、版本冲突……
本文将从 模块划分、依赖管理、构建配置、Root POM 职责 四个维度,分享一套经过生产验证的 Spring Boot 多模块最佳实践。
一、模块划分:按职责而非技术分层
❌ 常见反模式(按技术分层):
project/
├── controller/
├── service/
├── repository/
└── model/
✅ 推荐模式(按业务能力 + 公共组件):
my-order-service/ ← 整个项目根目录(聚合工程)
├── pom.xml
│
├── order-api/ ← 对外暴露的接口契约(DTO + Feign Client)
├── order-domain/ ← 核心领域模型与业务逻辑(充血模型)
├── order-infrastructure/ ← 技术实现(DB、MQ、外部调用)
├── order-application/ ← Spring Boot 启动入口 + 编排层
│
└── common/ ← 跨服务复用的通用组件(非业务专属)
├── common-core/ ← 工具类、异常、枚举、注解
└── common-starter/ ← 自定义 Spring Boot Starter
✨ 模块职责说明:
|
模块 |
职责 |
打包类型 |
|
*-api |
定义对外接口(DTO、Feign Client、OpenAPI 注解) |
jar |
|
*-domain |
领域模型、业务规则、领域服务(纯 Java,无 Spring 依赖) |
jar |
|
*-infrastructure |
技术细节实现(MyBatis、Redis、第三方 SDK 封装) |
jar |
|
*-application |
Spring Boot 主启动类、Web 层、事务编排 |
jar(可执行) |
|
common-* |
跨项目复用的基础能力 |
jar 或 starter |
💡 核心原则:上层模块依赖下层,下层模块绝不反向依赖上层。
二、如果有多个业务模块怎么办?
✅ 核心原则:按业务能力划分 Maven 模块(module),在模块内部按分层组织 Java 包(package)
也就是说:
每个业务能力 → 一个独立的 Maven 模块(如 order-service) 每个模块内部 → 按 controller / service / dao 分包
📁 推荐结构示例
假设你有 订单(Order) 和 用户(User) 两个核心业务:
my-app/ ← Root POM (packaging=pom)
│
├── order-service/ ← Maven 模块(业务能力:订单)
│ └── src/main/java/com/example/order/
│ ├── controller/ ← OrderController
│ ├── service/ ← OrderService, OrderServiceImpl
│ ├── repository/ ← OrderRepository (或 dao/)
│ └── OrderApplication.java ← Spring Boot 启动类(可选)
│
├── user-service/ ← Maven 模块(业务能力:用户)
│ └── src/main/java/com/example/user/
│ ├── controller/
│ ├── service/
│ ├── repository/
│ └── UserApplication.java
│
└── common/ ← 公共组件模块
└── src/main/java/com/example/common/
├── exception/
├── util/
└── dto/
💡 注意:order-service 和 user-service 都是 独立的可执行 JAR(各自有 @SpringBootApplication),部署时是两个微服务。
❓ 为什么这样做?
|
优势 |
说明 |
|
部署独立 |
订单服务升级不影响用户服务 |
|
团队自治 |
订单团队只维护 order-service 模块 |
|
依赖清晰 |
order-service 可依赖 common,但绝不依赖 user-service(避免循环) |
|
技术异构 |
未来订单模块可改用 Kotlin,用户模块保持 Java |
⚠️ 常见误区:把所有业务塞进一个模块
❌ 错误结构(单模块多业务):
monolith-service/
└── src/main/java/com/example/
├── order/
│ ├── controller/
│ └── service/
└── user/
├── controller/
└── service/
问题:
- 无法独立部署(改订单代码要全量发布)
- 依赖容易混乱(order.service 直接调 user.service)
- 编译慢、JAR 大、启动慢
🌟 微服务 ≠ 必须拆成多个进程,但多模块是迈向微服务的第一步。
三、依赖管理:统一版本,避免冲突
多模块项目最大的挑战之一是 依赖版本一致性。推荐使用 BOM(Bill of Materials)机制。
🔍 补充:spring-boot-dependencies vs spring-boot-starter-parent
很多开发者误以为必须继承 spring-boot-starter-parent,其实二者本质都是“声明式定义”,只是作用范围不同:
|
组件 |
类型 |
作用范围 |
内容 |
|
spring-boot-dependencies |
BOM(Bill of Materials) |
仅依赖管理 |
声明所有 Spring Boot 官方支持的第三方库版本(如 Jackson、Tomcat、Hibernate),通过 <dependencyManagement> 生效 |
|
spring-boot-starter-parent |
Parent POM |
依赖 + 构建全栈管理 |
继承 spring-boot-dependencies,并额外提供: • Maven 插件版本(<pluginManagement>) • 编译配置(Java 版本、编码) • 资源过滤规则 • 默认打包行为 |
💡 一句话总结: spring-boot-dependencies 是“依赖版本字典”,而 spring-boot-starter-parent 是“开箱即用的项目模板”。
因此,在多模块项目中,推荐 Root POM 导入 BOM,而非直接继承 starter-parent——这样既能统一依赖,又能避免构建逻辑污染非应用模块。
✅ 在 Root POM 中统一管理依赖版本
<!-- my-order-service/pom.xml -->
<dependencyManagement>
<dependencies>
<!-- 导入 Spring Boot 官方 BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.13</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 导入 Spring Cloud Alibaba BOM -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子模块中无需指定版本:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 无 version,自动使用 BOM 中的版本 -->
</dependency>
✅ 优势:
避免 NoSuchMethodError、ClassNotFoundException 等版本冲突 升级 Spring Boot 只需改 Root POM 一处
四、构建配置:按需启用插件
不是所有模块都需要 Spring Boot 的构建逻辑!
✅ 只在 *-application 模块配置 Spring Boot 插件
<!-- order-application/pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 无需写 version,由 pluginManagement 提供 -->
<configuration>
<mainClass>com.example.order.OrderApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
✅ 其他模块保持轻量
- order-api、order-domain、common-core 等模块无需任何特殊插件
- 它们就是普通的 Java JAR,可被其他服务直接引用
🌟 这样做的好处:
编译更快(无 repackage 开销) JAR 更小(无嵌入式 Tomcat) 可作为普通库发布到 Nexus/Maven Central
五、Root POM 的职责边界:管什么?不管什么?
Root POM(聚合工程)是整个项目的“宪法”,它的核心职责是统一规范、消除重复、保障一致性,但绝不应越俎代庖。
✅ 必须管理的内容
|
类别 |
具体内容 |
说明 |
|
依赖版本 |
通过 <dependencyManagement> 导入 BOM |
包括 Spring Boot、Spring Cloud、公司内部组件等 |
|
插件版本 |
通过 <pluginManagement> 声明插件元数据 |
如 maven-compiler-plugin、spring-boot-maven-plugin 等,只声明版本,不启用 |
|
全局属性 |
Java 版本、编码、测试覆盖率阈值等 |
例如 <java.version>17</java.version> |
|
模块聚合 |
<modules> 列出所有子模块 |
控制构建顺序 |
❌ 不应包含的内容
|
类别 |
原因 |
|
具体依赖(在 <dependencies> 中) |
会导致所有子模块强制引入,违反“按需依赖”原则 |
|
启用插件(在 <build><plugins> 中) |
会让非应用模块(如 common)继承不必要的构建逻辑 |
|
应用专属配置 |
如 mainClass、数据库连接等,应下放到具体模块 |
六、避免循环依赖:架构红线
循环依赖是多模块项目的“癌症”。常见场景:
order-service → user-api
user-service → order-api ❌(如果 order-api 依赖了 user-api)
✅ 防御措施:
- API 模块只包含 DTO 和接口,不包含业务逻辑
- 禁止 API 模块依赖其他服务的 API 模块 如需关联,通过 ID 引用(如 userId: Long),而非嵌入对象
- 使用 ArchUnit 或 Maven Enforcer Plugin 在 CI 中检测循环依赖
七、结语
一个优秀的多模块项目,不是简单地把代码拆成多个文件夹,而是通过清晰的边界、统一的规范、合理的依赖流向,构建出易于理解、易于维护、易于扩展的系统骨架。
Spring Boot 本身并不强制你如何组织代码,但它提供了足够的灵活性,让我们能结合领域驱动设计(DDD)、分层架构、BOM 管理等理念,打造出真正生产就绪的工程结构。
好的架构,是让新人第一天就能看懂代码流向。
原文链接:https://juejin.cn/post/7597695531218468914
更多推荐
所有评论(0)