在 Spring Boot 项目中使用多模块(Maven 或 Gradle)是一种非常好的实践,它有助于将大型项目拆分为职责清晰、易于管理和维护的模块。
下面我将详细解释多模块项目与普通单模块项目的区别、需要修改的地方以及如何正确使用。
核心思想
多模块项目的核心是有一个父模块(Parent Module) 来聚合多个子模块(Child Module)。父模块本身通常不包含业务代码,它主要做三件事:
- 项目管理:定义所有子模块。
- 依赖管理:统一管理所有子模块共用的依赖版本。
- 插件管理:统一配置所有子模块共用的插件。
子模块则是具体的功能单元,例如:
app-web:Web 控制器层
app-service:业务服务层
app-dao 或 app-repository:数据持久层
app-common 或 app-core:通用工具、配置、模型等
app-generator:代码生成器等特殊任务模块
与普通项目的区别及需要修改的地方
我们将从项目结构、父 POM、子 POM、启动类、包扫描和依赖传递这几个关键点来阐述。
1. 项目结构 (Project Structure)
普通单模块项目结构:
1 2 3 4 5 6 7 8
| monolithic-project/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ └── resources/ │ └── test/ ├── pom.xml └── ...
|
多模块项目结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| multi-module-project/ (父项目/根目录) ├── app-web/ (子模块1,通常是启动模块) │ ├── src/ │ │ ├── main/ │ │ └── test/ │ └── pom.xml ├── app-service/ (子模块2) │ ├── src/ │ │ ├── main/ │ │ └── test/ │ └── pom.xml ├── app-dao/ (子模块3) │ ├── src/ │ │ ├── main/ │ │ └── test/ │ └── pom.xml ├── app-common/ (子模块4) │ ├── src/ │ │ ├── main/ │ │ └── test/ │ └── pom.xml └── pom.xml (父POM)
|
修改点:你需要按照模块职责创建上述目录结构。
2. 父模块的 POM.xml
这是变化最大的地方。父 POM 的打包类型必须为 pom,并且要声明子模块。
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
| <?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>
<groupId>com.example</groupId> <artifactId>multi-module-project</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging>
<name>multi-module-project</name> <description>Demo project for Spring Boot Multi-Module</description>
<modules> <module>app-common</module> <module>app-dao</module> <module>app-service</module> <module>app-web</module> </modules>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency> </dependencies> </dependencyManagement>
<build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement> </build> </project>
|
3. 子模块的 POM.xml
每个子模块需要声明其父模块,并且只需要引入自己直接需要的依赖,无需指定版本(版本由父模块的 dependencyManagement 统一控制)。
以 app-common 模块为例:
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
| <?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>multi-module-project</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent>
<artifactId>app-common</artifactId> <packaging>jar</packaging>
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
|
以 app-web (最终启动模块)为例,它需要依赖其他子模块:
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
| ... (同上,parent部分) <artifactId>app-web</artifactId> <packaging>jar</packaging>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>com.example</groupId> <artifactId>app-service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ...
|
4. 启动类 (Application Class) 和包扫描
重要规则:只有一个模块会包含启动类(即 main 方法),通常是 app-web 这种最顶层的模块。
关键问题:组件扫描(Component Scan)
默认情况下,Spring Boot 启动类只会扫描它所在包及其子包下的组件(如 @Component, @Service, @Controller等)。
如果你的实体类、Service、Dao 分布在不同的模块中,你必须确保它们的包名是启动类包名的子包。
最佳实践:
- 基础包名:在父 POM 中定义一个统一的
groupId,例如 com.example。
- 子模块包结构:每个子模块的代码都从这个基础包名开始。
app-web 模块的代码放在 com.example.web 包下。
app-service 模块的代码放在 com.example.service 包下。
app-dao 模块的代码放在 com.example.dao 包下。
app-common 模块的代码放在 com.example.common 包下。
这样,启动类 com.example.web.Application 的 @SpringBootApplication 注解默认就能扫描到 com.example 下的所有组件(web, service, dao, common 都是其子包)。
如果无法修改包名,你可以在启动类上使用 @ComponentScan 注解手动指定要扫描的包:
1 2 3 4 5 6 7
| @SpringBootApplication @ComponentScan({"com.example.web", "com.example.service", "com.example.dao"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
5. 依赖传递 (Dependency Transitivity)
Maven 的依赖是传递的。这意味着:
- 如果
app-service 依赖了 app-dao。
- 而
app-web 又依赖了 app-service。
- 那么
app-web 也会自动拥有 app-dao 的依赖,无需重复声明。
总结:操作步骤
- 创建父项目:使用 Spring Initializr 或 IDE 创建一个新项目,并立即将其
pom.xml 的 <packaging> 改为 pom,并删除其 src 目录(因为父项目不需要代码)。
- 创建子模块:在父项目根目录下,右键新建 Module。IDE(如 IntelliJ IDEA)通常会自动在其子模块的 POM 中正确设置
<parent>。
- 配置父 POM:在父 POM 中定义
<modules>, <dependencyManagement>, 和 <pluginManagement>。
- 配置子 POM:在每个子 POM 中只声明它们直接需要的依赖。
- 建立模块间依赖:在需要依赖其他模块的子 POM 中,像引入普通依赖一样引入兄弟模块(使用它们的
groupId, artifactId, version)。
- 规划包结构:确保所有模块的包名是启动类包名的子包,或者手动配置组件扫描。
- 编写代码:像在普通项目中一样在各个模块中编写代码。
- 构建和运行:
- 构建整个项目:在根目录(父POM所在目录)执行
mvn clean install。Maven 会根据 <modules> 的顺序自动构建所有子模块。
- 运行项目:只需要运行包含启动类和
spring-boot-maven-plugin 的那个模块(通常是 app-web)。你可以通过 IDE 启动,或者进入该模块目录执行 mvn spring-boot:run。
总而言之,多模块项目并不是简单地把代码分开就行,而是需要通过 Maven/Gradle 的聚合与继承机制进行有机的整合。正确配置后,它的优势(代码复用、关注点分离、构建优化)会非常明显。