7. [架构重构] 迈向企业级:Maven 多模块项目
摘要: 随着我们功能的不断累加,我们最初的单体项目正变得日益臃肿和混乱。本章,我们将进行一次意义深远的架构重构,借鉴 RuoYi 等企业级框架的设计思想,亲手将我们的项目改造为一个职责清晰、高度解耦的 Maven 多模块工程。这次重构将极大提升项目的可维护性和扩展性,为未来承载更复杂的业务打下坚实的基础。
7.1. 痛点分析与模块化思想
7.1.1. 现状:单体应用的困境
在开始重构之前,我们首先要清醒地认识到当前项目存在的“成长烦恼”。请看我们目前的项目包结构:
虽然我们遵循了 controller, service, mapper 等内部分层,但从整体工程角度看,它依然是一个 单体应用。随着项目规模的扩大,这种结构逐渐暴露出一系列问题:
- 包结构扁平化:
advice, common, config, controller, converter, dto, entity… 所有这些包都挤在同一个 src 目录下。当未来我们新增“订单模块”、“产品模块”时,这个列表将变得越来越长,不同业务模块的代码会混杂在一起,难以管理。 - 依赖关系混乱: 我们的
pom.xml 文件中,既有 spring-boot-starter-web 这样的 Web 框架依赖,也有 mybatis-plus 这样的数据持久化依赖,还有 hutool-all 这样的通用工具依赖。所有依赖都打包在一起,职责不清,我们很难说清楚哪个模块具体需要哪个依赖。 - 高度耦合: 所有代码都在一个模块中,理论上任何一个类的改动(即使是一个工具类),都可能需要对整个项目进行重新的编译、测试和部署,模块间的边界是模糊的。
- 复用性差: 如果我们想在另一个新项目中复用当前的
common 包或 util 包下的工具类,除了复制粘贴代码,没有更优雅的方式。
7.1.2. 蓝图:借鉴 RuoYi 的模块化设计
要解决以上问题,我们需要引入软件工程领域一个非常重要的思想——模块化,而在 Maven 项目中,实现模块化的最佳实践就是构建 **多模块项目 **。
我们将借鉴 RuoYi 这类成熟企业级框架的设计精髓,将我们的单体应用“分而治之”,拆分为以下四个核心模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 📂 spring-boot-demo (父模块, pom) ├── 📄 pom.xml (统一管理版本和模块) │ ├── 📂 demo-common (通用工具模块, jar) │ └── ... (存放 Result, BusinessException, JwtUtil 等) │ ├── 📂 demo-framework (框架核心模块, jar) │ └── ... (存放 WebConfig, MybatisPlusConfig, GlobalExceptionHandler, 拦截器等) │ ├── 📂 demo-system (系统业务模块, jar) │ └── ... (存放所有用户管理相关的 Controller, Service, Mapper, DTO, VO 等) │ └── 📂 demo-admin (启动与部署模块, jar) └── ... (仅存放 SpringBootDemoApplication.java 启动类)
|
各模块核心职责:
| 模块 | 核心职责 | 详细说明 |
|---|
demo-common | 通用工具与核心定义 | 存放与具体业务无关的、可被所有其他模块复用的公共类,如 Result 封装、自定义异常、Hutool 依赖、枚举、常量等。它是一个高度抽象的底层工具包。 |
demo-framework | 框架核心配置与增强 | 存放与 Spring 框架本身相关的配置和增强功能,如 WebMvcConfigurer (CORS, 拦截器)、MybatisPlusConfig、SpringDocConfig、全局异常处理器等。它为业务模块提供技术支撑。 |
demo-system | 具体业务模块 | 存放具体业务模块的代码。目前我们只有一个“用户管理”功能,所以所有 User 相关的 Controller, Service, Mapper, DTO, VO 都会放在这里。未来新增“订单模块”时,我们会创建一个新的 demo-order 业务模块。 |
demo-admin | 应用启动入口 | 这是一个非常轻量的“胶水”模块。它本身几乎没有代码,只包含 main 启动类。它的主要作用是通过 Maven 依赖,将 framework 和 system 等模块组装起来,最终打包成一个可运行的 Spring Boot 应用。 |
通过这样的拆分,我们的项目结构将变得异常清晰。每个模块都有明确的边界和单一的职责,实现了物理层面的解耦。这为我们后续的开发、测试和维护工作,奠定了坚实的企业级工程基础。
7.2. 实战:父工程改造与依赖管理
理论学习完毕,我们现在正式开始对项目进行“大手术”。第一步,也是最关键的一步,就是将我们当前的 spring-boot-demo 项目,从一个可运行的 jar 工程,改造为一个 只负责管理、不包含任何代码的 pom 父工程。
7.2.1. 修改 packaging 为 pom
请打开项目根目录下的 pom.xml 文件。我们需要做的第一件事,就是将 <packaging> 标签的内容,从默认的 jar 修改为 pom。
文件路径: pom.xml (修改)
1 2 3 4 5
| <groupId>com.example</groupId> <artifactId>spring-boot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging>a <name>spring-boot-demo</name>
|
这个小小的改动,从根本上改变了 pom.xml 的性质。它告诉 Maven:“我不再是一个需要被打成 jar 包的普通应用了,我的新身份是一个父工程,我的职责是管理我的子模块们”。
修改后,您可能会发现项目中的 src 目录在 IDE 中变成了灰色或普通文件夹图标,这是完全正常的,因为 pom 类型的父工程本身不包含业务代码。
7.2.2. 统一版本:使用 <properties> 和 <dependencyManagement>
目前,我们所有的依赖都直接写在 <dependencies> 标签下,版本号散落在各处。当项目模块变多时,这会导致版本混乱和难以升级。专业的做法是使用 <dependencyManagement> 在父工程中进行统一的版本声明。
我们将进行两步操作:
- 使用
<properties> 标签将所有版本号提取为变量。 - 将整个
<dependencies> 块移动到 <dependencyManagement> 块内部。
文件路径: pom.xml (修改)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| <?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.6</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>spring-boot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-demo</name>
<packaging>pom</packaging> <description>spring-boot-demo</description> <url/>
<properties> <java.version>17</java.version> <lombok.version>1.18.32</lombok.version> <hutool.version>5.8.27</hutool.version> <mybatis-plus.version>3.5.5</mybatis-plus.version> <springdoc.version>2.7.0</springdoc.version> </properties>
<dependencyManagement> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>demo-common</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>demo-framework</artifactId> <version>${project.version}</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency>
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${springdoc.version}</version> </dependency>
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency>
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies> </dependencyManagement>
</project>
|
代码解析:
<properties>: 我们将所有第三方库的版本号都提取到了这里,便于未来统一升级。<dependencyManagement>: 这就像一个“依赖版本仲裁中心”。在这里声明的依赖,并 不会 被实际引入,它只是一个“版本清单”。- 清空
<dependencies>: 父 pom 作为一个管理者,它本身不应该包含任何具体的实现代码,因此也不需要直接依赖任何 jar 包。
经过改造后,所有子模块未来在引入这些依赖时,只需要提供 groupId 和 artifactId,无需再指定 version,它们会自动继承父工程中声明的版本。这保证了整个项目所有模块使用的依赖版本是高度统一的。
最后,父工程需要知道它到底管理了哪些子模块。我们在 pom.xml 的顶层(与 <properties> 同级)添加 <modules> 标签,并在其中声明我们规划好的四个子模块。
7.3. 实战:拆分核心模块
父工程的改造完成后,现在就如同我们已经画好了建筑蓝图。接下来的任务,就是按照蓝图,一砖一瓦地搭建起每个独立的模块(房间),并将我们现有的代码(家具)搬运到它们各自正确的位置。
在 IDEA 中,您可以通过右键点击项目根目录 -> New -> Module... 来创建新的 Maven 子模块。请确保在创建时,它能被正确识别为当前父工程的子模块。
7.3.1. 创建并迁移 demo-common (通用工具模块)
这个模块是我们的基础工具库,存放与具体业务无关的、可被所有其他模块复用的公共代码。
第一步:创建模块与 pom.xml
首先,在项目根目录下创建 demo-common 文件夹,并在其中创建 pom.xml 文件。
文件路径: demo-common/pom.xml (修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>spring-boot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
<artifactId>demo-common</artifactId>
<dependencies> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
</project>
|
注意:在子模块的 pom.xml 中,我们引入依赖时 无需再指定 <version> 和 <groupId>,因为它会从父工程的 <dependencyManagement> 中自动继承,这正是父工程的价值所在!
第二步:迁移代码
现在,我们将主项目中所有通用的代码包,整体移动到 demo-common 模块的 src/main/java/ 目录下。
迁移清单:
common 包 (包含 Result.java, ResultCode.java)enums 包 (包含 UserStatusEnum.java)exception 包 (包含 BusinessException.java)
7.3.2. 创建并迁移 demo-framework (框架核心模块)
这个模块负责所有与 Spring 框架相关的配置和增强功能。
第一步:创建模块与 pom.xml
文件路径: demo-framework/pom.xml (修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>spring-boot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
<artifactId>demo-framework</artifactId>
<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>demo-common</artifactId>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> </dependency>
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
</project>
|
第二步:迁移代码
迁移清单:
advice 包 (包含 GlobalExceptionHandler.java)config 包 (包含 MybatisPlusConfig.java, SpringDocConfig.java, WebConfig.java)converter 包 (包含 StringToUserStatusEnumConverter.java)interceptor 包 (包含 AuthInterceptor.java, LogInterceptor.java)
7.3.3. 创建并迁移 demo-system (系统业务模块)
这个模块是我们的核心业务模块,存放所有与“用户管理”相关的代码。
最理想的结构是让 demo-system 依赖 demo-framework,而不是重复声明 spring-boot-starter-web、mybatis-plus 等依赖。这样可以形成清晰的依赖链:admin -> system -> framework -> common
第一步:创建模块与 pom.xml
文件路径: demo-system/pom.xml (新增文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>spring-boot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
<artifactId>demo-system</artifactId>
<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>demo-framework</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
</project>
|
第二步:迁移代码
迁移清单:
controller 包 (所有 Controller)dto 包 (所有 DTO)entity 包 (所有 Entity)mapper 包 (所有 Mapper)service 包 (所有 Service 及其 impl)validation 包 (所有校验分组)vo 包 (所有 VO)
在修复代码之前,必须先让项目能正确加载。
- 打开根目录下的
pom.xml (spring-boot-demo/pom.xml)。 - 找到
<modules> 标签。 - 注释或删除 那一行
<module>demo-admin</module>,因为这个模块还不存在。 - 在 IDEA 右侧的 Maven 面板中,点击刷新按钮,确保项目能无错误地加载。
步骤 1: 执行全局搜索和替换
现在,我们要把所有旧的包引用 com.example.springbootdemo.* 替换成新的包名。
按下快捷键 Ctrl + Shift + R (Windows/Linux) 或 Cmd + Shift + R (Mac)。这将打开全局搜索和替换窗口。
执行第一次替换:
因为我们的新包结构是 com.example.democommon, com.example.demosystem 等,而不是 com.example.springbootdemo.common。原来的所有 DTO, VO, Service 等都引用了 com.example.springbootdemo 下的类。
建议开启自动导包,对于之前结构的包都进行删除并全部自动导入的操作

步骤 2: 优化 Imports
替换完成后,可能还有一些多余或无效的 import 语句。
- 在项目根目录上右键。
- 选择 “Optimize Imports”。IDEA 会自动清理所有文件中未使用的导入。
- 你也可以使用快捷键
Ctrl + Alt + O (Windows/Linux) 或 Cmd + Option + O (Mac) 在单个文件中操作。
7.4. 实战:创建 Admin 启动模块
7.4.1. 创建 demo-admin 模块与 pom.xml
首先,请按照同样的方式,创建第四个子模块:demo-admin。
这个模块的 pom.xml 非常关键,它不直接依赖具体的第三方库,而是依赖于我们自己创建的 framework 和 system 模块,像胶水一样将它们粘合在一起。同时,它需要 spring-boot-maven-plugin 插件来将整个应用打包成一个可执行的 jar。
文件路径: demo-admin/pom.xml (新增文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?xml version="1.0" encoding="UTF-8"?> <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> <parent> <groupId>com.example</groupId> <artifactId>spring-boot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
<artifactId>demo-admin</artifactId>
<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>demo-framework</artifactId> <version>${project.version}</version> </dependency>
<dependency> <groupId>com.example</groupId> <artifactId>demo-system</artifactId> <version>${project.version}</version> </dependency>
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
|
7.4.2. 迁移并改造启动类
现在,我们将父工程根目录下那个“光杆司令” SpringBootDemoApplication.java 移动到 demo-admin 模块中。
同时,我们需要对它进行一个小小的改造,以确保 Spring Boot 能够扫描到我们所有子模块中的组件(Bean)。
文件路径: demo-admin/src/main/java/com/example/springbootdemo/SpringBootDemoApplication.java (迁移并修改)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.example.demoadmin;
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.example") @MapperScan("com.example.*.mapper") public class SpringBootDemoApplication {
public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); }
}
|
7.4.3. 清理项目
重要信息: 记得将配置文件搬迁到 demo-admin 上
为了让我们的项目结构达到最终的完美形态,请执行以下清理操作:
- 删除父工程根目录下的
src 文件夹: 因为 SpringBootDemoApplication.java 已经被移动到了 demo-admin 模块,父工程根目录下的 src 文件夹现在是多余的,可以 安全删除。 - 删除子模块中多余的启动类: 您在
demo-common, demo-framework 和 demo-system 中由 IDE 自动生成的 Demo...Application.java 和 ...Tests.java 文件是不需要的,也请一并删除,因为整个应用只有一个启动入口和一套集成的测试环境。
7.4.4. 回归测试:验证重构结果
现在,整个项目的结构已经焕然一新。请确保您在 IDE 中选择的启动目标是 demo-admin 模块中的 SpringBootDemoApplication.java。
为了不影响读者的阅读和可能读者操作失误,这里提供一个整理好的仓库供读者快速 Clone Spring_Mvc_Study: 教学用的 SpringMVC 文件
启动应用 后,请打开 Swagger UI 或 Postman,尝试调用一个我们之前写好的接口,例如 GET /users/1。
如果接口能够正常返回数据,那么——
恭喜您! 您已经成功地将一个单体应用,重构为了一个结构清晰、职责分明、高度解耦的企业级多模块项目!这次重构的成功,为您未来驾驭大型复杂项目奠定了坚实的工程基础。