第一章 · Maven 多模块工程结构与依赖治理
本章解剖工程的”骨架”:一个 Spring Boot 应用是怎么被拆成多个 Maven 工程,又如何被重新拼装成一个可启动的应用。这是读懂后续所有章节的前提——因为每个 Starter、每个业务模块的”存在方式”都由这套结构决定。
1. 模块概述
RuoYi-Vue-Pro 单仓库即是一个完整的 Maven parent POM。它没有把所有代码堆进一个工程,而是按”通用基础层 / 组装层 / 业务模块层“三个层级拆成了几十个 Maven 模块。根 pom.xml(源码根/pom.xml)用 <modules> 声明哪些子工程参与构建。
📍 源码:pom.xml:10-29
1 | <modules> |
📌 结构要点:
yudao-server是唯一可运行的 Spring Boot 应用;yudao-module-*是没有 main 方法的”业务库”,被yudao-server依赖;yudao-framework是不依赖任何业务模块的通用能力库。
1.1 三个关键工程的职责边界
graph TD
subgraph "yudao-dependencies(BOM)"
D1["只做 dependencyManagement
不写业务代码"]
end
subgraph "yudao-framework(内核)"
F1["yudao-common 基础类"]
F2["*-starter-* 各类 Starter"]
end
subgraph "yudao-module-*(业务)"
M1["*-api 接口工程(跨模块边界)"]
M2["*-biz 实现工程(Controller+Service+DAL)"]
end
subgraph "yudao-server(组装)"
S1["YudaoServerApplication 启动类"]
S2["application-*.yaml 配置"]
end
D1 --> F1
D1 --> F2
F2 --> M1
F2 --> M2
M1 --> M2
M1 --> S1
M2 --> S1
各工程规模参考(Java 文件数与它们在整体中的作用):
| 工程 | 文件数 | 作用 |
|---|---|---|
yudao-framework/yudao-common |
56 | 纯基础类,零 Spring 依赖的地基 |
yudao-framework/yudao-spring-boot-starter-web |
62 | Web 层:全局异常、请求包装、MVC 配置 |
yudao-system-module-system-biz |
387 | 系统业务最大实现体 |
yudao-module-infra-biz |
201 | 基础设施:代码生成、API 日志、配置 |
2. 命名体系与易混淆结构对比
2.1 命名规律拆解
Maven 模块的命名承载了清晰的职责信号,拆解如下:
| 前缀 | 中心词 | 后缀 | 含义 |
|---|---|---|---|
yudao- |
dependencies |
(无) | 版本 BOM 工程 |
yudao- |
framework |
(无) | 通用框架父工程 |
yudao-spring-boot-starter- |
web/security/mybatis/... |
(无) | 一个独立能力 Starter |
yudao-module- |
system/infra/bpm/... |
-api |
业务模块的对外接口工程 |
yudao-module- |
system/infra/bpm/... |
-biz |
业务模块的实现工程 |
yudao- |
server |
(无) | 服务端组装工程 |
命名规律总结:
- 业务模块一律拆为
-api(接口)与-biz(实现)两个子工程,接口供其他模块依赖。 - 通用能力以
yudao-spring-boot-starter-<能力>命名,符合 Spring Boot Starter 的约定(可通过@AutoConfiguration自动装配)。 - 「框架」与「业务」通过命名空间物理隔离:
yudao-framework.*永远不 importyudao.module.*。
2.2 易混淆结构对比:yudao-framework vs yudao-module-*
| 对比维度 | yudao-framework |
yudao-module-* |
|---|---|---|
| 定位 | 通用、可复用的横切能力 | 具体业务领域 |
| 是否依赖业务模块 | 否(单向) | 是(通过 xxxApi 依赖其他模块) |
| 装配方式 | @AutoConfiguration 自动装配 |
被 yudao-server 扫描进容器 |
| 典型内容 | GlobalExceptionHandler、TokenAuthenticationFilter |
XxxController、XxxServiceImpl |
| 一句话区分 | 是所有模块共享的”地基”;业务模块是可插拔的”房间”。 |
3. 依赖版本治理:yudao-dependencies(BOM)
工程采用 Spring Boot 经典的 BOM + dependencyManagement 方式统一掌控第三方依赖版本,避免”各模块各写各的版本号”导致冲突。
📍 源码内容示意(源码根/yudao-dependencies/pom.xml):
1 | <dependencyManagement> |
关键版本约束(来自根 pom.xml 的 properties 与 BOM):
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8 | master 分支限定 |
| Spring Boot | 2.7.18 | 应用开发框架 |
| Lombok / MapStruct | 1.18.36 / 1.6.3 | 编译期代码生成 |
| MyBatis-Plus | 见 BOM | ORM |
runtime-Dependencies 版本变量 |
${revision} |
所有自身模块版本号统一由 revision 属性管控 |
💡
${revision}机制:根 POM 用<revision>2.4.2-jdk8-SNAPSHOT</revision>配合flatten-maven-plugin让所有子模块共享同一个版本号,避免”升级版本要改几十处”的痛点。这在复刻自建多模块项目时是很值得借鉴的手法。
4. 数据结构深度解析:Config 类与 AutoConfiguration
Spring Boot Starter 的自动装配核心是 @AutoConfiguration + 配置文件绑定。此处以框架层的两个配置类为例,展示”如何用一个属性前缀驱动一整套能力”。
4.a 结构体存在的理由
没有
@ConfigurationProperties与@AutoConfiguration,框架能力(如 security、web)就必须在每个模块里手工 new Bean 并逐个配置,无法做到”加一个依赖、写几行配置就生效”。属性类把可调项集中化,自动配置类把”默认一套、可覆盖”的装配收敛起来。
4.b 代码:SecurityProperties
1 |
|
4.c 字段三层分析表
| 字段 | 设计动机(为什么需要) | 反事实(如果去掉会怎样) | 替代方案(还能怎么做) |
|---|---|---|---|
tokenHeader |
定义访问令牌放在哪个 Header,默认为 Authorization |
令牌无处携带,每次请求无法鉴权 | 固定死字符串,但失去可配置性 |
tokenParameter |
WebSocket 握手不能带 Header,需要退化为 URL 参数 | WS 场景无法鉴权 | 单独为 WS 造一套机制,代价更大 |
mockEnable |
开发期免真实 Token 快速调试(mockSecret+userId 即登录) |
需反复走登录流程,降低联调效率 | 仅 dev profile 生效,但易误开 |
permitAllUrls |
外放开回调、验证码等免登录地址 | 回调接口需登录才能访问,集成失败 | 硬编码进代码,不够灵活 |
passwordEncoderLength |
控制 BCrypt 加密强度(越强越慢) | 固定强度无法在安全与性能间取舍 | 无法在运行期调整 |
⚠️ 安全提醒:
mockSecret默认值只是”开发便利”,线上必须关闭mockEnable或更换密钥(源码注释也明确警告”线上一定要关闭”)。
5. 装配机制:自动配置的落地
YudaoSecurityAutoConfiguration 通过 @AutoConfiguration 把上面这些 Bean 注册进容器。它的注册动作(关键代码)大致为:
1 |
|
@ConditionalOnMissingBean 是 Starter 可定制性的灵魂:框架给默认实现,业务方一旦自定义同名 Bean,框架自动退让。
6. 关键机制剖析:Spring Boot 自动装配(AutoConfiguration)
算法/机制核心:spring.factories 或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件里列出 @AutoConfiguration 类的全限定名,Spring Boot 启动时按序实例化它们。
1 | // 每个 Starter 的 resources/META-INF/ 下都有类似文件 |
边界处理:
- 通过
@AutoConfigureOrder(-1)控制装配顺序(security 先于 Spring Security 默认配置)。 - 通过
@ConditionalOnBean/@ConditionalOnMissingBean/@ConditionalOnProperty控制”什么条件下装配”。
7. 设计决策分析
- 为什么用多模块而非单体一个工程? 权衡:拆模块带来清晰的依赖边界、编译隔离与并行开发,代价是维护更复杂。项目用「模块内高度自治 + 模块间仅依赖
xxxApi接口」来中和复杂度。 - 为什么通用能力要下沉到
yudao-framework? 让业务模块”减负”,同一个能力(如鉴权、数据权限)在所有模块复用同一份实现,修一个 bug 处处生效。 - 为什么每个业务模块拆
-api/-biz? 对比单工程方案:-api是稳定契约,-biz是可替换实现。若直接依赖对方的 Service 实现类,会形成编译期强耦合、无法拆分部署(云原生版yudao-cloud正依赖此边界做微服务化)。
8. 学习检查点
📝 本章小结
yudao-server是唯一可启动的 Spring Boot 应用,业务模块被它依赖、框架层被它扫描。- 业务模块统一拆为
-api/-biz两工程,模块间只依赖接口。 yudao-dependenciesBOM +${revision}统一治理版本号。- Starter 用
@AutoConfiguration+@ConfigurationProperties实现”加依赖即生效、属性可覆盖”。
🤔 思考题
为什么
yudao-framework内部绝对不允许 importcn.iocoder.yudao.module.*?(提示:想想依赖方向与模块复用)参考答案
因为
yudao-framework是”通用内核”,被所有yudao-module-*依赖;一旦内核反向依赖某个业务模块,就形成循环依赖,且内核失去通用性——任何复用它的新项目都会被迫带上那个模块。如需跨模块能力,内核只面向”接口”(如 security 依赖module/system/api/permission/PermissionApi),具体实现在业务模块侧。@ConditionalOnMissingBean在这套架构里承担什么角色?(提示:框架默认 vs 业务定制)参考答案
它的语义是”容器中还没有这个类型的 Bean 时才装配当前 Bean”。这让框架能提供默认实现,同时允许业务方通过自己定义同名 Bean 来覆盖。框架给默认、业务可覆盖,是 Starter 可定制性的根基(例如
PasswordEncoder默认 BCrypt 强度 4,业务可自定义)。如果把
yudao-module-ai加入<modules>,但没有做任何其他改动,会发生什么?(提示:回顾根 pom 的注释与 第 10 章/第 11 章)参考答案
ai模块对 JDK 版本有要求(根 pom 注释说明 “AI 大模型的开启,请参考 …/ai/build/ 文档,对 JDK 版本有要求”),在 JDK 8 下可能编译失败;同时需要导入对应数据库表、配置 API Key,否则启动或运行时会出现缺表/连接失败。开启一个模块不是”加进 modules 就行”,往往是”开编译 + 导表 + 配 key + 配外部服务”四件事。