← 返回 构建与部署图解 目录

☕ Maven全链路图解

3套生命周期 + 依赖解析 + 冲突解决 + 面试速答 · SVG可视化

① 三套生命周期

Maven生命周期 = 工厂的生产流程:一个工厂有三条独立的生产线——clean是拆除旧厂房的流水线(清理上次构建的产物),default是主生产流水线(从原材料到成品出库),site是写产品说明书的流水线(生成项目文档)。你可以说"执行到包装环节"(mvn package),Maven就自动从第一步流水线开头一路执行到包装——前面的步骤自动执行,不需要你手动一步步叫
🧹 clean 生命周期 拆除旧厂房 pre-clean 🔥 clean 删除target/目录 post-clean 常用命令 mvn clean 🏭 default 生命周期 主生产流水线 validate 验证 initialize 初始化 ⚙️ compile 编译 🧪 test 测试 📦 package 打包 verify 验证 install 安装到本地 deploy 部署到远程 📝 site 生命周期 写产品说明书 pre-site 📄 site 生成项目文档站点 post-site site-deploy mvn site
图1:Maven三套生命周期 — clean拆旧/default生产/site写文档,互不干扰
核心概念:阶段(Phase) vs 插件目标(Goal):阶段是流水线上的"工位名称"(compile、test),但真正干活的是插件的"目标"(compiler:compile、surefire:test)。mvn compile实际执行的是compiler插件的compile目标——阶段只是个名字,插件目标才是真正干活的人。

② default生命周期详解

validate 验证项目 ⚙️ compile src/main/java → target/classes 🧪 test src/test/java → 运行单元测试 📦 package target/classes → target/*.jar 或 target/*.war verify 集成测试检查 💾 install 复制到本地仓库 ~/.m2/repository/ 其他项目可用 🚀 deploy 上传到远程仓库 私服/Nexus 团队共享 🔑 命令→执行阶段映射 mvn compile validate → initialize → compile mvn test validate → ... → compile → test mvn package validate → ... → compile → test → package mvn install validate→...→package→verify→install mvn deploy validate→...→install→deploy mvn clean package 先clean再package(最常用!)
图2:default生命周期9个阶段 — 执行某个阶段时,前面所有阶段自动执行
pom.xml — 基础结构
📁 my-project
pom.xml
📁 src
📁 main
App.java
📁 test
AppTest.java
<!-- Maven项目坐标 = 人的身份证 --> <groupId>com.example</groupId> <!-- 公司/组织 --> <artifactId>my-project</artifactId> <!-- 项目名 --> <version>1.0.0</version> <!-- 版本号 --> <packaging>jar</packaging> <!-- 打包类型: jar/war/pom --> <!-- 最重要的两个属性 --> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <java.version>17</java.version> <spring-boot.version>3.2.0</spring-boot.version> </properties> <!-- 继承Spring Boot父POM — 不用写版本号 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> </parent>

③ 依赖解析机制

Maven依赖解析 = 找书过程:你要一本《Spring实战》(你的项目依赖spring-boot),先看自己书架(本地仓库 ~/.m2/repository/),没有就上公司资料室找(私服Nexus),还没有就去国家图书馆(Maven中央仓库)。找到后复制一本放自己书架上,下次就不用再跑了。而且Spring实战里面还引用了其他书(传递依赖),Maven会自动帮你把引用的书也找齐
👤 你的项目 需要 spring-boot ① 先找这里 📚 本地仓库 ~/.m2/repository/ ✅ 找到了! ② 没有→去私服 🏢 私服(Nexus) 公司内部仓库 + 中央仓库代理 ✅ 找到→下载→存入本地仓库 ③ 还没有→去中央仓库 🏛 Maven中央仓库 repo.maven.apache.org — 全球最大的Java库
图3:Maven依赖解析路径 — 本地→私服→中央仓库,找到后缓存到本地

3.1 依赖范围(Scope)

Scope = 借书的权限compile(默认)= 你随时都能看这本书,打包也带进去;provided = 这本书是公共的(如Servlet API),JDK/服务器已经提供了,打包不用带;runtime = 写代码时不用看,运行时才需要(如JDBC驱动);test = 只在考试时用(JUnit),打包不带。
Scope 编译时 测试时 运行时 打包 典型例子
compile ✅ 带进去 spring-boot, lombok
provided ❌ 不带 servlet-api, JDK自带
runtime ✅ 带进去 mysql-connector, HikariCP
test ❌ 不带 junit, mockito
system ❌ 不带 本地jar(不推荐)
pom.xml — 依赖配置
📁 my-project
pom.xml
<dependencies> <!-- compile(默认)— 编译+运行+打包都带 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 版本号由parent管理,不用写 --> </dependency> <!-- provided — 编译时用,运行时由容器提供 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</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>

3.2 传递依赖

你的项目 spring-boot-starter-web spring-boot-starter spring-boot-starter-tomcat spring-webmvc spring-boot-autoconfigure spring-aop spring-core → spring-jcl → ... 🔑 传递依赖 你只声明了1个依赖 Maven自动把引用链全找齐
图4:传递依赖 — 你只声明1个starter,Maven自动拉取整个依赖树
Terminal — 查看依赖树
📁 my-project
Terminal
# 查看完整依赖树(包括传递依赖) $ mvn dependency:tree # 输出示例: com.example:my-project:jar:1.0.0 ├── org.springframework.boot:spring-boot-starter-web:3.2.0 │ ├── org.springframework.boot:spring-boot-starter:3.2.0 │ │ ├── org.springframework.boot:spring-boot-autoconfigure:3.2.0 │ │ └── spring-core:6.1.1 │ ├── org.springframework:spring-webmvc:6.1.1 │ │ └── org.springframework:spring-aop:6.1.1 │ └── tomcat-embed-core:10.1.x ├── com.mysql:mysql-connector-j:8.0.33 └── org.junit.jupiter:junit-jupiter:5.10.1 (test) # 只看冲突的依赖 $ mvn dependency:tree -Dverbose | grep "omitted for conflict" # 分析用了/没用到的依赖 $ mvn dependency:analyze

④ 依赖冲突与解决

依赖冲突 = 两个人给你不同版本的书:A朋友送你《Spring 5.0》,B朋友送你《Spring 6.0》,你到底看哪本?Maven有两条规则:① 最短路径优先——A朋友是直接给你的(1层),B朋友是通过C转给你的(2层),听A的!② 先声明优先——如果路径一样长,谁先在pom.xml里声明就听谁的。但!Spring Boot的<dependencyManagement>就像老师统一规定了全班用哪个版本,不管谁送的,一律按老师说的来。
规则1:最短路径优先 你的项目 路径长度=1 ✅ spring-core 6.1 直接依赖,胜出! 路径长度=2 spring-beans 6.1 (传递依赖) ❌ spring-core 5.3 路径更长,被忽略! 最终使用 spring-core 6.1(最短路径) 规则2:先声明优先 (路径长度相同时) 你的项目 路径=2 ✅ 库A v2.0 先声明 → 胜出! 路径=2 ❌ 库A v1.0 后声明 → 被忽略 <dependencies> <dependency> <!-- ✅ 先声明 --> artifactId: lib-a-v2 <dependency> <!-- ❌ 后声明 --> artifactId: lib-b-v1
图5:依赖冲突解决规则 — 最短路径优先,路径相同则先声明优先

4.1 手动解决冲突

pom.xml — 解决依赖冲突
📁 my-project
pom.xml
<!-- 方法1:exclusion排除传递依赖 --> <dependency> <groupId>com.example</groupId> <artifactId>lib-b</artifactId> <exclusions> <exclusion> <!-- 排除lib-b带来的旧版spring-core --> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> </exclusions> </dependency> <!-- 方法2:直接声明想要的版本(最短路径=1)--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>6.1.1</version> <!-- 直接声明→路径最短→一定胜出!--> </dependency> <!-- 方法3:dependencyManagement统一版本(Spring Boot做法)--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>6.1.1</version> <!-- 所有spring-core统一用这个版本 --> </dependency> </dependencies> </dependencyManagement>
依赖冲突的典型症状ClassNotFoundExceptionNoSuchMethodErrorAbstractMethodError——代码编译通过(编译时用的是正确的版本),但运行时报错(运行时加载了冲突的旧版本)。排查步骤:mvn dependency:tree -Dverbose → 找到冲突 → exclusion或直接声明。

⑤ 插件与面试速答

5.1 核心插件速查

插件 绑定阶段 作用 常用配置
maven-compiler-plugin compile 编译Java源码 source/target版本
maven-surefire-plugin test 运行单元测试 skipTests, 并行测试
maven-jar-plugin package 打jar包 Main-Class配置
maven-war-plugin package 打war包 webResources过滤
maven-install-plugin install 安装到本地仓库 一般不需要配置
maven-deploy-plugin deploy 部署到远程仓库 repository URL
maven-clean-plugin clean 删除target目录 自定义删除目录
spring-boot-maven-plugin package 打可执行jar mainClass, profiles
pom.xml — Spring Boot打包插件
📁 my-project
pom.xml
<build> <plugins> <!-- Spring Boot打包插件 — 生成可执行jar --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.example.Application</mainClass> <!-- 指定启动类 --> <excludes> <exclude> <groupId>org.projectlombok</groupId> <!-- Lombok编译后不需要 --> </exclude> </excludes> </configuration> </plugin> <!-- 跳过测试(紧急时) --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> <!-- ⚠️ 仅CI/紧急时使用,平时不要跳!--> </configuration> </plugin> </plugins> </build>

5.2 settings.xml 关键配置

⚙️ 全局 settings.xml ${MAVEN_HOME}/conf/settings.xml ① localRepository — 本地仓库路径 ② mirrors — 镜像源(如阿里云) ③ profiles — JDK版本等 所有项目生效 👤 用户 settings.xml ~/.m2/settings.xml ① servers — 私服账号密码 ② mirrors — 阿里云镜像加速 ③ profiles — 本地JDK/编码 优先级:用户 > 全局
图6:settings.xml配置位置 — 用户级覆盖全局级
~/.m2/settings.xml — 阿里云镜像
📁 ~/.m2
settings.xml
📁 repository
<settings> <!-- 本地仓库位置(默认 ~/.m2/repository)--> <localRepository>/Users/you/.m2/repository</localRepository> <mirrors> <!-- 阿里云镜像 — 国内下载快10倍+ --> <mirror> <id>aliyun</id> <mirrorOf>central</mirrorOf> <name>Aliyun Maven</name> <url>https://maven.aliyun.com/repository/central</url> </mirror> </mirrors> <profiles> <profile> <id>jdk-17</id> <activation> <activeByDefault>true</activeByDefault> <jdk>17</jdk> </activation> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> </profile> </profiles> </settings>

5.3 Maven vs Gradle

对比维度 Maven Gradle
配置格式 XML(pom.xml)
冗长
Groovy/Kotlin DSL
简洁
构建速度 每次全量构建
较慢
增量构建+构建缓存
快2~10倍
灵活性 约定优于配置
标准化
脚本可编程
灵活
学习曲线 低,XML易懂
入门快
中,DSL需学习
稍陡
生态 最成熟,插件最多
企业标配
快速成长,Android标配
追赶中
Spring Boot 官方首选
完善支持
也支持
可用

5.4 面试高频问题

Maven的构建生命周期是什么?

Maven有3套相互独立的生命周期

clean:pre-clean → clean → post-clean(清理上次构建产物)

default:validate → compile → test → package → verify → install → deploy(核心构建流程)

site:pre-site → site → post-site → site-deploy(生成项目文档)

核心机制:执行某个阶段时,该阶段之前的所有阶段会自动按顺序执行。如mvn package会依次执行validate→compile→test→package。

Maven如何解决依赖冲突?

两条核心规则:

① 最短路径优先:A→B→C(v1) 和 A→D(v2),D的路径更短,选v2。

② 先声明优先:路径长度相同时,pom.xml中先声明的依赖胜出。

解决方式

1. <exclusion>排除不需要的传递依赖

2. 直接在pom.xml中声明想要的版本(路径最短=1,一定胜出)

3. <dependencyManagement>统一管理版本(Spring Boot的做法)

Maven的Scope有哪些?

5种scope,决定了依赖在什么阶段可用、是否打包进最终产物:

compile(默认):编译+测试+运行都可用,打包带进去

provided:编译+测试可用,运行时由JDK/容器提供,打包不带(如servlet-api)

runtime:测试+运行可用,编译不用,打包带进去(如JDBC驱动)

test:只有测试可用,打包不带(如JUnit)

system:类似provided,但需指定本地jar路径(不推荐)

mvn clean package和mvn package有什么区别?

mvn package:只执行default生命周期(validate→compile→test→package)。如果target目录里有上次的旧class文件,可能混用新旧class,导致诡异bug。

mvn clean package:先执行clean生命周期(删除target/),再执行package。确保从零开始编译,不会有残留。

最佳实践:开发时可以用mvn package(快),正式构建/CI一定要用mvn clean package(干净)。

Spring Boot的spring-boot-starter-parent做了什么?

它是一个POM类型的父项目,帮你做了这些事:

① 统一版本管理:通过<dependencyManagement>预定义了所有Spring Boot相关依赖的版本号,你不需要写version

② 统一插件配置:预配置了spring-boot-maven-plugin、compiler、surefire等插件

③ 统一属性:预定义了Java版本、编码格式(UTF-8)、资源过滤等

④ 依赖版本对齐:确保所有Spring组件版本兼容,不会出现A用6.0 B用5.3的冲突

dependencyManagement和dependencies的区别?

<dependencies>:直接引入依赖,子模块会自动继承。

<dependencyManagement>:只声明版本号,不实际引入!子模块需要自己写<dependency>,但不用写version(由父POM统一管理)。

类比:dependencies像妈妈直接把菜端到你面前;dependencyManagement像妈妈在菜单上标注了每道菜用多少盐,你自己点菜,但盐的量按妈妈的标准来。

📌 速记口诀

三套生命周期:clean拆旧/default生产/site写文档
default核心阶段:validate→compile→test→package→verify→install→deploy
依赖解析路径:本地仓库→私服→中央仓库,找到就缓存
依赖范围Scope:compile全带/provided不带/runtime运行才用/test测试才用
冲突解决:最短路径优先→路径相同先声明优先→dependencyManagement定版本
常用命令:mvn clean package(最常用)/ mvn install / mvn deploy
Maven vs Gradle:XML标准化 vs DSL灵活,Gradle增量构建更快