多模块项目
对于大型项目,将代码拆分为多个模块可以提高代码的可维护性和复用性。本章将介绍如何使用 Maven 构建和管理多模块项目。
为什么需要多模块项目?
在单体应用中,所有代码都在一个项目中,随着项目规模增长,会面临以下问题:
- 代码臃肿:所有代码混在一起,难以维护
- 耦合度高:模块之间相互依赖,修改一处影响多处
- 构建缓慢:每次构建都要编译整个项目
- 复用困难:无法单独复用某个功能模块
多模块项目通过将项目拆分为独立的模块,解决了这些问题:
- 职责清晰:每个模块负责特定功能
- 独立开发:模块可以独立开发、测试和部署
- 依赖管理:模块间依赖关系清晰
- 并行构建:可以只构建修改的模块
项目继承与聚合
Maven 提供了两种机制来管理多模块项目:继承和聚合。
继承
继承允许子项目继承父项目的配置。子项目可以继承:
- dependencies(依赖)
- dependencyManagement(依赖管理)
- properties(属性)
- build 配置(构建配置)
- plugins(插件配置)
聚合
聚合允许在一个项目中构建多个模块。父项目通过 <modules> 元素指定子模块,执行 Maven 命令时会自动构建所有子模块。
继承与聚合的区别
| 特性 | 继承 | 聚合 |
|---|---|---|
| 目的 | 共享配置 | 一起构建 |
| 配置方式 | 子项目指定 parent | 父项目指定 modules |
| 关系方向 | 子指向父 | 父指向子 |
| 可以单独使用 | 可以 | 可以 |
通常,继承和聚合会一起使用:父项目既作为聚合项目,也作为父 POM。
创建多模块项目
项目结构
一个典型的多模块项目结构:
parent-project/
├── pom.xml # 父项目 POM
├── common/ # 公共模块
│ └── pom.xml
├── service/ # 服务模块
│ └── pom.xml
├── web/ # Web 模块
│ └── pom.xml
└── api/ # API 模块
└── pom.xml
父项目配置
父项目的 pom.xml:
<?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>parent-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Parent Project</name>
<description>多模块项目父工程</description>
<!-- 子模块列表 -->
<modules>
<module>common</module>
<module>service</module>
<module>web</module>
<module>api</module>
</modules>
<!-- 属性定义 -->
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.3.23</spring.version>
<mybatis.version>3.5.11</mybatis.version>
</properties>
<!-- 依赖管理 -->
<dependencyManagement>
<dependencies>
<!-- Spring BOM -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 内部模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 第三方依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 公共依赖 -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
<!-- 插件管理 -->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
关键点:
- packaging 必须为 pom:父项目的打包类型必须是 pom
- modules 指定子模块:列出所有子模块目录名
- dependencyManagement 管理版本:统一管理依赖版本
- 公共依赖放在 dependencies:所有子模块都会继承
子模块配置
Common 模块
common 模块存放公共代码:
<?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>parent-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
<packaging>jar</packaging>
<name>Common Module</name>
<description>公共工具模块</description>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
</project>
Service 模块
service 模块依赖 common 模块:
<?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>parent-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>service</artifactId>
<packaging>jar</packaging>
<name>Service Module</name>
<description>业务服务模块</description>
<dependencies>
<!-- 依赖内部模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>common</artifactId>
</dependency>
<!-- 业务依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
</dependencies>
</project>
Web 模块
web 模块依赖 service 模块:
<?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>parent-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>web</artifactId>
<packaging>war</packaging>
<name>Web Module</name>
<description>Web 应用模块</description>
<dependencies>
<!-- 依赖内部模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>service</artifactId>
</dependency>
<!-- Web 依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>myapp</finalName>
</build>
</project>
模块依赖关系
在多模块项目中,模块之间的依赖关系应该清晰明确。
典型的依赖层次
web (Web 层)
└── service (服务层)
└── common (公共层)
└── api (接口层)
依赖原则
- 单向依赖:依赖关系应该是单向的,避免循环依赖
- 层次分明:上层依赖下层,下层不依赖上层
- 接口隔离:通过 api 模块定义接口,实现解耦
检测循环依赖
mvn dependency:tree
如果存在循环依赖,Maven 会报错。
构建多模块项目
构建所有模块
在父项目目录执行:
# 编译所有模块
mvn compile
# 打包所有模块
mvn package
# 安装所有模块到本地仓库
mvn install
构建指定模块
# 只构建 web 模块
mvn package -pl web
# 构建 web 模块及其依赖模块
mvn package -pl web -am
# 构建 web 模块及依赖它的模块
mvn package -pl web -amd
# 构建多个模块
mvn package -pl web,service
参数说明:
| 参数 | 说明 |
|---|---|
| -pl, --projects | 指定要构建的模块 |
| -am, --also-make | 同时构建指定模块的依赖模块 |
| -amd, --also-make-dependents | 同时构建依赖指定模块的模块 |
反应堆顺序
Maven 按照依赖关系确定构建顺序,这个顺序称为反应堆顺序:
mvn package
输出:
[INFO] Reactor Build Order:
[INFO]
[INFO] parent-project
[INFO] common
[INFO] api
[INFO] service
[INFO] web
Maven 会自动计算依赖顺序,确保被依赖的模块先构建。
版本管理
统一版本号
在多模块项目中,所有模块应该使用相同的版本号。在父 POM 中定义版本,子模块使用 ${project.version} 引用:
<!-- 父 POM -->
<version>1.0.0-SNAPSHOT</version>
<!-- dependencyManagement 中 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
版本更新
使用 Maven Versions 插件更新版本:
# 设置新版本
mvn versions:set -DnewVersion=2.0.0-SNAPSHOT
# 提交版本更改
mvn versions:commit
# 回滚版本更改
mvn versions:revert
多模块项目最佳实践
1. 合理划分模块
按功能或层次划分模块:
- 按层次:common、dao、service、web
- 按功能:user、order、payment
- 混合:common、user-service、order-service、web
2. 使用 dependencyManagement
在父 POM 中使用 dependencyManagement 统一管理依赖版本,避免版本冲突。
3. 避免循环依赖
模块之间不能相互依赖,如果出现公共需求,提取到更底层的模块。
4. 控制依赖范围
- 公共依赖放在父 POM
- 模块特有依赖放在子模块
- 使用 dependencyManagement 只管理版本,不引入依赖
5. 统一版本号
所有模块使用相同版本号,便于发布和管理。
6. 合理使用聚合和继承
- 父项目同时作为聚合项目和父 POM
- 如果只需要共享配置,使用继承
- 如果只需要一起构建,使用聚合
Spring Boot 多模块项目
Spring Boot 多模块项目是常见的实践:
项目结构
springboot-multimodule/
├── pom.xml # 父 POM
├── common/ # 公共模块
│ └── pom.xml
├── service/ # 服务模块
│ └── pom.xml
└── web/ # Web 模块(启动模块)
└── pom.xml
父 POM
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-multimodule</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>service</module>
<module>web</module>
</modules>
<properties>
<java.version>17</java.version>
</properties>
</project>
Web 模块(启动模块)
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>springboot-multimodule</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>web</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意:只有 web 模块需要 spring-boot-maven-plugin 插件。
小结
本章我们学习了:
- 多模块项目的优势和适用场景
- 继承和聚合的概念与区别
- 如何创建和配置多模块项目
- 模块依赖关系的设计原则
- 多模块项目的构建方法
- 版本管理的最佳实践
- Spring Boot 多模块项目的配置
在下一章中,我们将学习 Maven 的 Profile 配置。