【033】Maven 生命周期与坐标:多模块结构

本文聚焦 Java 工程化的「基础设施」------ Maven。从 P1 阶段走来,你已经掌握了 Java 语言核心、JVM 原理、并发编程,写的代码能跑、能调、能排查。但一旦进入团队协作、依赖管理、多服务开发,靠「手写 javac」和「拖 jar 包」就完全不够用了。Maven 是 Java 世界最主流的构建工具,几乎所有企业项目都在用。本文会从最基础的「坐标」概念讲起,让你理解为什么一个 GAV 能唯一确定一个构件;然后深入 Maven 的三大生命周期,搞清楚 compile、test、package、install、deploy 这些命令到底做了什么;最后介绍多模块项目的结构设计,帮助你理解为什么企业级项目要拆成 parent、common、api、service 多个子工程。读完本文,你将掌握 Maven 的核心概念和最佳实践,为后续 Spring Boot、微服务、CI/CD 打下坚实的工程基础。

如果你之前只是「复制粘贴 pom.xml」,运行 mvn clean install 时只知道盯着 BUILD SUCCESS 或 FAILURE,那这篇会帮你把「黑盒」打开。下面我按坐标与仓库 → 生命周期与插件 → 多模块结构 → 实战踩坑的顺序往下聊。


1. Maven 是什么:从「手工构建」到「标准化工程」

1.1 没有 Maven 的日子

假设你要开发一个 Java Web 项目,没有构建工具时,流程是这样的:

  1. 手动下载 jar 包(Spring、MyBatis、Jackson......)
  2. 放到项目的 lib/ 目录
  3. 手动配置 IDE 的 classpath
  4. 运行 javac 编译,java 运行
  5. 打包时手动复制依赖 jar,写 MANIFEST.MF

这会带来一系列问题:

  • 版本冲突:项目 A 用 Spring 5.3,项目 B 用 Spring 6.0,jar 包混用导致诡异异常
  • 依赖传递:你引入 spring-web,但它依赖 spring-core、spring-beans......手动管理极其痛苦
  • 构建不一致:开发环境和生产环境的依赖版本可能不同
  • 协作困难:新人入职要花半天配环境

1.2 Maven 解决了什么

Maven 的核心价值是「标准化 + 自动化」:

  • 标准化:统一的项目结构、构建流程、依赖管理方式
  • 自动化:一键编译、测试、打包、发布
  • 依赖管理:自动下载依赖、解决传递依赖、排除冲突

Maven 的核心理念是「约定优于配置(Convention over Configuration)」:

复制代码
${project.basedir}/
├── pom.xml                    # 项目配置文件
├── src/
│   ├── main/
│   │   ├── java/             # 源代码
│   │   ├── resources/        # 资源文件
│   │   └── webapp/           # Web 应用(WAR 项目)
│   └── test/
│       ├── java/             # 测试代码
│       └── resources/        # 测试资源
└── target/                    # 构建输出

这个目录结构是 Maven 约定的,不需要你手动配置。只要遵循约定,Maven 就知道去哪里找源代码、测试代码、资源文件。

1.3 Maven 与 Gradle 的关系

Gradle 是另一个主流构建工具,采用 Groovy/Kotlin DSL,灵活性更高:

特性 Maven Gradle
配置语言 XML(声明式) Groovy/Kotlin DSL(编程式)
构建速度 较慢(每次重新解析) 快(增量构建、构建缓存)
学习曲线
生态成熟度 极高
企业应用 传统企业、Spring 官方支持 Android、新项目

本文聚焦 Maven,Gradle 会在 035 篇专门讲解。


2. 坐标系统:GAV 的含义与仓库机制

2.1 什么是 GAV 坐标

Maven 使用「坐标」唯一标识一个构件(Artifact)。坐标由三部分组成:G roupId、A rtifactId、V ersion,简称 GAV

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.4</version>
</dependency>

groupId:组织或公司的反向域名

  • 通常是域名反转:com.alibabaorg.springframework
  • 区分不同组织的项目

artifactId:项目或模块名称

  • 具体的构件名称:fastjsonspring-boot-starter-web
  • 一个组织可以有多个 artifactId

version:版本号

  • 正式版:3.3.4
  • 快照版:3.3.4-SNAPSHOT(开发中的版本)
  • 里程碑版:3.3.0-M13.3.0-RC1

2.2 坐标与仓库路径的映射

Maven 根据坐标自动计算构件在仓库中的路径:

复制代码
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 3.3.4
packaging: jar

↓ 映射到仓库路径

${repository}/org/springframework/boot/spring-boot-starter-web/3.3.4/spring-boot-starter-web-3.3.4.jar

规律groupId. 分隔成目录层级,artifactIdversion 组成最终文件名。

2.3 仓库类型:本地、中央、私服

Maven 有三种仓库类型:

本地仓库(Local Repository)

  • 位置:~/.m2/repository(默认)
  • 作用:缓存已下载的构件,避免重复下载
  • 配置:~/.m2/settings.xml
xml 复制代码
<settings>
    <localRepository>/path/to/local/repo</localRepository>
</settings>

中央仓库(Central Repository)

私服(Private Repository)

  • 常见产品:Nexus、Artifactory
  • 作用:企业内部构件的发布和代理
  • 优势:
    • 加速下载(代理中央仓库)
    • 统一管理(内部构件、第三方构件)
    • 权限控制

仓库查找顺序

  1. 本地仓库 → 有则使用
  2. 私服(如配置)→ 有则下载到本地
  3. 中央仓库 → 下载到本地

2.4 settings.xml 配置详解

settings.xml 是 Maven 的全局配置文件,位于 ~/.m2/settings.xml

配置私服镜像

xml 复制代码
<settings>
    <mirrors>
        <mirror>
            <id>aliyun</id>
            <name>阿里云公共仓库</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <mirrorOf>central</mirrorOf>
        </mirror>
    </mirrors>
</settings>

配置私服认证

xml 复制代码
<settings>
    <servers>
        <server>
            <id>company-releases</id>
            <username>admin</username>
            <password>admin123</password>
        </server>
    </servers>
</settings>

配置 profile

xml 复制代码
<settings>
    <profiles>
        <profile>
            <id>dev</id>
            <properties>
                <env>dev</env>
            </properties>
            <repositories>
                <repository>
                    <id>company-snapshots</id>
                    <url>https://nexus.company.com/repository/snapshots/</url>
                    <snapshots>
                        <enabled>true</enabled>
                    </snapshots>
                </repository>
            </repositories>
        </profile>
    </profiles>
    <activeProfiles>
        <activeProfile>dev</activeProfile>
    </activeProfiles>
</settings>

3. 生命周期:Maven 的「骨架」

3.1 三大生命周期

Maven 定义了三套相互独立的生命周期(Lifecycle):

生命周期 作用 关键阶段
clean 清理项目 pre-clean → clean → post-clean
default 构建核心 validate → compile → test → package → install → deploy
site 生成文档 pre-site → site → post-site → site-deploy

重要规则

  • 执行某个阶段时,之前的阶段会自动执行
  • 例如执行 mvn package,会依次执行 validate → compile → test → package
  • 三套生命周期相互独立,执行 mvn clean package 会触发 clean 和 default 两套

3.2 default 生命周期详解

default 生命周期是最常用的,包含以下阶段(按顺序):

复制代码
validate          → 验证项目结构
initialize        → 初始化构建状态
generate-sources  → 生成源代码
process-sources   → 处理源代码
generate-resources → 生成资源
process-resources → 处理资源(复制到 target)
compile           → 编译源代码
process-classes   → 处理编译后的类
generate-test-sources → 生成测试源代码
process-test-sources → 处理测试源代码
generate-test-resources → 生成测试资源
process-test-resources → 处理测试资源
test-compile      → 编译测试代码
process-test-classes → 处理测试类
test              → 运行单元测试
prepare-package   → 准备打包
package           → 打包(jar/war)
pre-integration-test → 集成测试准备
integration-test  → 运行集成测试
post-integration-test → 集成测试收尾
verify            → 验证打包结果
install           → 安装到本地仓库
deploy            → 部署到远程仓库

常用命令

bash 复制代码
# 编译
mvn compile

# 编译 + 测试
mvn test

# 编译 + 测试 + 打包
mvn package

# 编译 + 测试 + 打包 + 安装到本地仓库
mvn install

# 编译 + 测试 + 打包 + 部署到远程仓库
mvn deploy

# 清理 + 打包
mvn clean package

# 跳过测试
mvn package -DskipTests

# 跳过测试编译和执行
mvn package -Dmaven.test.skip=true

3.3 clean 生命周期

复制代码
pre-clean  → 清理前的工作
clean      → 删除 target 目录
post-clean → 清理后的工作

常用命令

bash 复制代码
# 清理 target 目录
mvn clean

# 清理 + 打包
mvn clean package

3.4 site 生命周期

复制代码
pre-site   → 生成文档前的准备
site       → 生成项目文档网站
post-site  → 文档生成后的工作
site-deploy → 发布文档到服务器

常用命令

bash 复制代码
# 生成文档
mvn site

# 生成文档并发布
mvn site-deploy

3.5 插件与目标

Maven 的核心只是一个骨架,具体的构建工作由插件(Plugin)完成。每个插件包含多个目标(Goal)。

常见插件

插件 作用 常用目标
maven-compiler-plugin 编译 Java 代码 compile、testCompile
maven-surefire-plugin 运行单元测试 test
maven-jar-plugin 打包 JAR jar
maven-war-plugin 打包 WAR war
maven-install-plugin 安装到本地仓库 install
maven-deploy-plugin 部署到远程仓库 deploy
maven-clean-plugin 清理构建 clean
maven-resources-plugin 处理资源文件 resources、testResources

插件绑定关系

  • Maven 在生命周期的每个阶段,默认绑定了某些插件目标
  • 例如 compile 阶段绑定了 maven-compiler-plugin:compile
  • 你可以自定义绑定

查看插件绑定

bash 复制代码
mvn help:describe -Dcmd=compile -Ddetail

4. pom.xml 核心配置

4.1 基本结构

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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 坐标 -->
    <groupId>com.example</groupId>
    <artifactId>demo-project</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!-- 基本信息 -->
    <name>Demo Project</name>
    <description>A demo Maven project</description>

    <!-- 属性 -->
    <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-boot.version>3.3.4</spring-boot.version>
    </properties>

    <!-- 依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
    </dependencies>

    <!-- 构建 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
            </plugin>
        </plugins>
    </build>
</project>

4.2 依赖范围(Scope)

依赖范围决定了依赖在什么阶段可用:

Scope 编译 测试 运行 打包 示例
compile(默认) spring-core
provided servlet-api、lombok
runtime mysql-connector-java
test junit、mockito
system 本地 jar(不推荐)
import - - - - dependencyManagement(仅 pom)

示例

xml 复制代码
<dependencies>
    <!-- compile 范围(默认) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- provided 范围:编译时需要,运行时由容器提供 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- runtime 范围:运行时才需要 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- test 范围:仅测试时使用 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

4.3 依赖传递与排除

依赖传递

当 A 依赖 B,B 依赖 C 时,A 会自动获得对 C 的依赖。

复制代码
A → B → C

但以下情况不会传递:

  • C 的 scope 是 providedtest
  • B 对 C 设置了 <optional>true</optional>
  • A 对 B 设置了 <exclusions>

依赖排除

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.43</version>
    <exclusions>
        <!-- 排除传递依赖 -->
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework</artifactId>
        </exclusion>
    </exclusions>
</dependency>

可选依赖

xml 复制代码
<dependency>
    <groupId>org.example</groupId>
    <artifactId>some-lib</artifactId>
    <version>1.0.0</version>
    <optional>true</optional>  <!-- 不会传递给下游 -->
</dependency>

4.4 查看依赖树

bash 复制代码
# 查看依赖树
mvn dependency:tree

# 查看特定依赖的来源
mvn dependency:tree -Dincludes=org.springframework:spring-core

# 分析依赖冲突
mvn dependency:analyze

依赖树示例

复制代码
com.example:demo-project:jar:1.0.0-SNAPSHOT
+- org.springframework.boot:spring-boot-starter-web:jar:3.3.4:compile
|  +- org.springframework.boot:spring-boot-starter:jar:3.3.4:compile
|  |  +- org.springframework.boot:spring-boot:jar:3.3.4:compile
|  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:3.3.4:compile
|  |  \- org.springframework.boot:spring-boot-starter-logging:jar:3.3.4:compile
|  +- org.springframework.boot:spring-boot-starter-json:jar:3.3.4:compile
|  +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.3.4:compile
|  \- org.springframework:spring-webmvc:jar:6.1.14:compile
\- org.projectlombok:lombok:jar:1.18.30:provided

5. 多模块项目:企业级项目的标准结构

5.1 为什么需要多模块

单模块项目在规模较小时没问题,但随着项目增长,会面临:

  • 代码耦合:所有代码在一个模块,边界不清晰
  • 复用困难:公共服务无法被其他项目复用
  • 构建缓慢:修改一行代码,整个项目都要重新构建
  • 团队协作:多人同时修改一个模块,冲突频繁

多模块项目的优势:

  • 职责分离:每个模块有明确的职责
  • 依赖管理:统一管理版本,避免冲突
  • 按需构建:只构建修改的模块
  • 复用性:公共模块可以被多个项目使用

5.2 多模块项目结构

典型结构

复制代码
demo-parent/                    # 父工程
├── pom.xml                     # 父 pom
├── demo-common/                # 公共模块
│   ├── pom.xml
│   └── src/
├── demo-api/                   # API 模块(对外接口)
│   ├── pom.xml
│   └── src/
├── demo-service/               # 业务服务模块
│   ├── pom.xml
│   └── src/
└── demo-web/                   # Web 模块(启动入口)
    ├── pom.xml
    └── src/

5.3 父 pom 配置

父 pom 的职责

  • 定义公共属性(版本号、编码等)
  • 管理依赖版本(dependencyManagement)
  • 管理插件版本(pluginManagement)
  • 声明子模块
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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>  <!-- 父工程必须是 pom 类型 -->

    <name>Demo Parent</name>
    <description>多模块项目父工程</description>

    <!-- 子模块 -->
    <modules>
        <module>demo-common</module>
        <module>demo-api</module>
        <module>demo-service</module>
        <module>demo-web</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-boot.version>3.3.4</spring-boot.version>
        <mybatis-plus.version>3.5.9</mybatis-plus.version>
        <hutool.version>5.8.32</hutool.version>
    </properties>

    <!-- 依赖管理:只声明版本,不实际引入 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 内部模块 -->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>demo-common</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>demo-api</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>demo-service</artifactId>
                <version>${project.version}</version>
            </dependency>

            <!-- 第三方依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- 公共依赖:所有子模块都会继承 -->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <!-- 插件管理 -->
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

5.4 子模块 pom 配置

demo-common(公共模块)

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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 继承父 pom -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>demo-common</artifactId>
    <packaging>jar</packaging>

    <name>Demo Common</name>
    <description>公共工具类、常量、基础配置</description>

    <dependencies>
        <!-- 这里不需要写 version,由父 pom 统一管理 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
    </dependencies>
</project>

demo-api(API 模块)

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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>demo-api</artifactId>
    <packaging>jar</packaging>

    <name>Demo API</name>
    <description>对外暴露的接口定义、DTO、VO</description>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-common</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
    </dependencies>
</project>

demo-service(业务模块)

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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>demo-service</artifactId>
    <packaging>jar</packaging>

    <name>Demo Service</name>
    <description>业务逻辑、数据访问层</description>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        </dependency>
    </dependencies>
</project>

demo-web(启动模块)

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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>demo-web</artifactId>
    <packaging>jar</packaging>

    <name>Demo Web</name>
    <description>Web 层、启动入口</description>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-service</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 打包为可执行 jar -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

5.5 多模块构建命令

bash 复制代码
# 在父工程目录执行,构建所有模块
mvn clean install

# 只构建指定模块(同时构建依赖的模块)
mvn clean install -pl demo-web -am

# 参数说明:
# -pl (--projects)     指定要构建的模块
# -am (--also-make)    同时构建依赖的模块
# -amd (--also-make-dependents)  同时构建依赖于该模块的模块

# 跳过指定模块
mvn clean install -pl !demo-web

# 只编译,不运行测试
mvn clean install -DskipTests

# 指定线程数并行构建
mvn clean install -T 4

# 离线模式(使用本地仓库)
mvn clean install -o

6. 常见问题与最佳实践

6.1 依赖冲突排查

问题现象

复制代码
java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.getDeclaredAnnotations(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;

这类错误通常是依赖版本冲突导致的:编译时用的是 A 版本,运行时加载的是 B 版本。

排查步骤

bash 复制代码
# 1. 查看依赖树
mvn dependency:tree

# 2. 查找特定依赖的来源
mvn dependency:tree -Dincludes=org.springframework:spring-core

# 3. 分析依赖问题
mvn dependency:analyze

解决方法

  1. 使用 dependencyManagement 统一版本
  2. 使用 <exclusions> 排除冲突依赖
  3. 使用 mvn dependency:analyze 检查未使用的依赖

6.2 版本号管理最佳实践

使用属性统一管理版本

xml 复制代码
<properties>
    <spring-boot.version>3.3.4</spring-boot.version>
    <mybatis-plus.version>3.5.9</mybatis-plus.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

使用 BOM(Bill of Materials)

Spring Boot、Spring Cloud 都提供了 BOM,可以简化版本管理:

xml 复制代码
<dependencyManagement>
    <dependencies>
        <!-- Spring Boot BOM -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.3.4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- Spring Cloud BOM -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

版本号命名规范

  • 正式版本:1.0.02.1.3
  • 快照版本:1.0.0-SNAPSHOT(开发中,每次构建会检查更新)
  • 里程碑版本:1.0.0-M11.0.0-RC1(预发布)
  • 发布候选版本:1.0.0-RELEASE(不推荐,正式版直接用 1.0.0

6.3 多模块分层原则

常见分层结构

复制代码
demo-parent/
├── demo-common/          # 最底层,无业务依赖
│   └── 工具类、常量、枚举、基础配置
├── demo-api/             # 接口层,依赖 common
│   └── DTO、VO、接口定义
├── demo-domain/          # 领域层,依赖 api(可选)
│   └── 实体、领域服务
├── demo-repository/      # 数据访问层,依赖 domain(可选)
│   └── Mapper、Repository
├── demo-service/         # 业务逻辑层,依赖 repository
│   └── Service、业务逻辑
└── demo-web/             # Web 层,依赖 service
    └── Controller、启动类

依赖方向原则

  • 单向依赖:上层依赖下层,下层不能依赖上层
  • 避免循环依赖:A → B → A 是绝对禁止的
  • 公共下沉:公共代码放到 common 模块

6.4 Maven 常用命令速查

bash 复制代码
# 清理
mvn clean

# 编译
mvn compile

# 打包
mvn package

# 安装到本地仓库
mvn install

# 部署到远程仓库
mvn deploy

# 运行测试
mvn test

# 跳过测试
mvn package -DskipTests

# 查看依赖树
mvn dependency:tree

# 分析依赖
mvn dependency:analyze

# 查看有效 pom
mvn help:effective-pom

# 查看插件帮助
mvn help:describe -Dplugin=compiler -Ddetail

# 创建项目(archetype)
mvn archetype:generate -DgroupId=com.example -DartifactId=demo -DarchetypeArtifactId=maven-archetype-quickstart

# 创建 Web 项目
mvn archetype:generate -DgroupId=com.example -DartifactId=demo-web -DarchetypeArtifactId=maven-archetype-webapp

7. 综合示例:从零搭建多模块项目

7.1 创建父工程

bash 复制代码
# 创建父工程目录
mkdir demo-parent && cd demo-parent

# 创建父 pom.xml

父 pom 内容见上文 5.3 节。

7.2 创建子模块

bash 复制代码
# 创建子模块目录
mkdir -p demo-common/src/main/java/com/example/common
mkdir -p demo-common/src/main/resources
mkdir -p demo-api/src/main/java/com/example/api
mkdir -p demo-service/src/main/java/com/example/service
mkdir -p demo-web/src/main/java/com/example/web
mkdir -p demo-web/src/main/resources

7.3 构建与运行

bash 复制代码
# 在父工程目录执行
mvn clean install

# 启动 Spring Boot(在 demo-web 目录)
cd demo-web
mvn spring-boot:run

7.4 常见问题

问题一:父 pom 找不到子模块

确保子模块目录名与 <module> 标签中的名称一致,且子模块目录下有 pom.xml 文件。

问题二:依赖版本冲突

使用 mvn dependency:tree 查看依赖树,在父 pom 的 dependencyManagement 中统一版本。

问题三:循环依赖

确保依赖方向正确:common → api → service → web。使用 mvn dependency:tree 检查是否有循环依赖。

问题四:SNAPSHOT 版本不更新

bash 复制代码
# 强制更新 SNAPSHOT
mvn clean install -U

小结

  • GAV 坐标是 Maven 构件的唯一标识,由 groupId、artifactId、version 三部分组成,映射到仓库路径
  • 三大生命周期(clean、default、site)相互独立,执行某个阶段会自动执行之前的所有阶段
  • 依赖范围(compile、provided、runtime、test)决定了依赖在不同阶段的可见性
  • dependencyManagement 用于统一管理版本,子模块引用时不需要写版本号
  • 多模块项目通过父 pom 继承和聚合,实现职责分离、统一版本、按需构建
  • 排查依赖冲突的核心命令是 mvn dependency:treemvn dependency:analyze

P2 阶段开篇说明

从本篇开始,我们进入 P2 工程化与协作阶段。Maven 是这个阶段的基础,后续的 Spring Boot 项目搭建、单元测试、CI/CD 都会用到 Maven 的知识。下一篇我们将深入依赖管理的「深水区」------依赖冲突与排除、dependencyManagement 的最佳实践,帮助你从容应对企业级项目中的依赖地狱问题。

下一篇(034)预告:依赖冲突与排除;dependencyManagement。

相关推荐
lihongli0001 小时前
关于c++中锁的种类与使用
java·开发语言·c++
凤凰院凶涛QAQ1 小时前
《C++转Java快速入手系列》继承与多态篇
java·c++
ppandss11 小时前
JavaWeb从0到1-DAY5.1-Maven-JUnit
junit·log4j·maven
倒霉蛋小马1 小时前
Idea--如何同一个SpringBoot项目复制多次,模拟集群环境
java·ide·intellij-idea
IT 行者1 小时前
Spring Boot 4.1.0-RC1 发布:核心新特性解析
java·spring boot·后端
ppandss11 小时前
JavaWeb从0到1-DAY5-Maven
python·maven
Cat_Rocky1 小时前
Ingress-Nginx 全局超时配置及生效方式
java·服务器·nginx
农业工作者1 小时前
IDEA解决springboot工程中Cannot resolve symbol ‘SpringApplication异常 maven解决
java·开发语言·maven
你不是我我10 小时前
【Java 开发日记】HTTP3 性能更好,为什么内网微服务依然多用 HTTP2?HTTP2 内网优势是什么?
java·开发语言·微服务