跳到主要内容

多模块项目

对于大型项目,将代码拆分为多个模块可以提高代码的可维护性和复用性。本章将介绍如何使用 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>

关键点:

  1. packaging 必须为 pom:父项目的打包类型必须是 pom
  2. modules 指定子模块:列出所有子模块目录名
  3. dependencyManagement 管理版本:统一管理依赖版本
  4. 公共依赖放在 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 (接口层)

依赖原则

  1. 单向依赖:依赖关系应该是单向的,避免循环依赖
  2. 层次分明:上层依赖下层,下层不依赖上层
  3. 接口隔离:通过 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 插件。

小结

本章我们学习了:

  1. 多模块项目的优势和适用场景
  2. 继承和聚合的概念与区别
  3. 如何创建和配置多模块项目
  4. 模块依赖关系的设计原则
  5. 多模块项目的构建方法
  6. 版本管理的最佳实践
  7. Spring Boot 多模块项目的配置

在下一章中,我们将学习 Maven 的 Profile 配置。