Spring Boot 多模块项目最佳实践:打造清晰、可维护的微服务骨架

13 阅读6分钟

在中大型 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
*-applicationSpring Boot 主启动类、Web 层、事务编排jar(可执行)
common-*跨项目复用的基础能力jarstarter

💡 核心原则:上层模块依赖下层,下层模块绝不反向依赖上层。


二、如果有多个业务模块怎么办?

✅ 核心原则:按业务能力划分 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-serviceuser-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-dependenciesBOM(Bill of Materials)仅依赖管理声明所有 Spring Boot 官方支持的第三方库版本(如 Jackson、Tomcat、Hibernate),通过 <dependencyManagement> 生效
spring-boot-starter-parentParent 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>

✅ 优势:

  • 避免 NoSuchMethodErrorClassNotFoundException 等版本冲突
  • 升级 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-apiorder-domaincommon-core 等模块无需任何特殊插件
  • 它们就是普通的 Java JAR,可被其他服务直接引用

🌟 这样做的好处:

  • 编译更快(无 repackage 开销)
  • JAR 更小(无嵌入式 Tomcat)
  • 可作为普通库发布到 Nexus/Maven Central

五、Root POM 的职责边界:管什么?不管什么?

Root POM(聚合工程)是整个项目的“宪法”,它的核心职责是统一规范、消除重复、保障一致性,但绝不应越俎代庖。

✅ 必须管理的内容

类别具体内容说明
依赖版本通过 <dependencyManagement> 导入 BOM包括 Spring Boot、Spring Cloud、公司内部组件等
插件版本通过 <pluginManagement> 声明插件元数据maven-compiler-pluginspring-boot-maven-plugin 等,只声明版本,不启用
全局属性Java 版本、编码、测试覆盖率阈值等例如 <java.version>17</java.version>
模块聚合<modules> 列出所有子模块控制构建顺序

❌ 不应包含的内容

类别原因
具体依赖(在 <dependencies> 中)会导致所有子模块强制引入,违反“按需依赖”原则
启用插件(在 <build><plugins> 中)会让非应用模块(如 common)继承不必要的构建逻辑
应用专属配置mainClass、数据库连接等,应下放到具体模块

🌰 正确示例(Root POM 片段):

<build>
  <pluginManagement>
    <plugins>
      <!-- 声明版本,但不启用 -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

🚫 错误示例

<!-- Root POM 中直接启用插件(污染所有子模块) -->
<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <!-- 这会让 common 模块也尝试 repackage! -->
    </plugin>
  </plugins>
</build>

六、避免循环依赖:架构红线

循环依赖是多模块项目的“癌症”。常见场景:

order-service → user-api
user-service → order-api  ❌(如果 order-api 依赖了 user-api)

✅ 防御措施:

  1. API 模块只包含 DTO 和接口,不包含业务逻辑
  2. 禁止 API 模块依赖其他服务的 API 模块
    • 如需关联,通过 ID 引用(如 userId: Long),而非嵌入对象
  3. 使用 ArchUnit 或 Maven Enforcer Plugin 在 CI 中检测循环依赖

七、结语

一个优秀的多模块项目,不是简单地把代码拆成多个文件夹,而是通过清晰的边界、统一的规范、合理的依赖流向,构建出易于理解、易于维护、易于扩展的系统骨架。

Spring Boot 本身并不强制你如何组织代码,但它提供了足够的灵活性,让我们能结合领域驱动设计(DDD)、分层架构、BOM 管理等理念,打造出真正生产就绪的工程结构。

好的架构,是让新人第一天就能看懂代码流向。