Maven高级详解
Maven 高级详解
本章位置:第二阶段 Java 核心框架
前置知识:Maven 基础、SpringBoot、MyBatis、SpringBoot 底层原理
下一阶段:第三阶段 Java 企业项目 + AI 助手
学习目标:系统掌握 Maven 的依赖传递、依赖冲突、依赖仲裁、dependencyManagement、父子工程、多模块工程、生命周期、插件、Profile、私服、版本管理,以及 SpringBoot 项目中的构建与打包。
一、为什么要学 Maven 高级
前面 Maven 基础阶段,我们已经会:
pom.xml
dependency
clean
compile
test
package
install
但是企业项目一旦变大,就会出现:
几十个依赖
多个模块
父子工程
版本统一管理
依赖冲突
测试和生产配置不同
公司内部私有 jar
SpringBoot 多模块打包
插件配置
构建速度问题
这时候只会:
复制 dependency
就不够了。
二、Maven 高级知识结构
Maven Advanced
│
├─ Dependency
│ ├─ Transitive Dependency
│ ├─ Conflict
│ ├─ Mediation
│ ├─ Exclusion
│ └─ Optional
│
├─ Version Management
│ ├─ dependencyManagement
│ ├─ properties
│ ├─ BOM
│ └─ parent
│
├─ Multi Module
│ ├─ parent
│ ├─ modules
│ ├─ inheritance
│ └─ aggregation
│
├─ Lifecycle
│ ├─ clean
│ ├─ default
│ └─ site
│
├─ Plugin
│ ├─ compiler
│ ├─ surefire
│ ├─ resources
│ ├─ jar
│ ├─ war
│ └─ spring-boot
│
├─ Environment
│ ├─ profile
│ ├─ settings.xml
│ └─ mirror
│
├─ Repository
│ ├─ local
│ ├─ central
│ └─ private
│
└─ Enterprise
├─ Nexus
├─ Artifactory
├─ CI/CD
└─ version strategy
三、依赖传递
假设:
项目 A
依赖 B
B
依赖 C
那么项目 A 通常会自动获得:
C
这叫:
Transitive Dependency
依赖传递
四、为什么依赖会传递
例如你引入:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.19</version>
</dependency>
MyBatis 自己可能还依赖:
其他 jar
Maven 会根据它们的:
pom.xml
继续解析。
五、依赖树
查看:
mvn dependency:tree
它会把:
直接依赖
传递依赖
全部列出来。
六、为什么 dependency:tree 很重要
排查:
ClassNotFoundException
NoSuchMethodError
版本冲突
重复 jar
依赖来源
非常有用。
七、直接依赖和传递依赖
直接依赖:
你在当前 pom.xml 里明确写的
传递依赖:
其他依赖带进来的
八、依赖冲突
假设:
A → commons-lang3 3.10
B → commons-lang3 3.14
当前项目同时依赖:
A
B
那么就出现:
版本冲突
九、Maven 不能同时随便用多个版本
同一个:
groupId + artifactId
在最终 classpath 中:
通常只会选择一个版本
十、依赖仲裁
Maven 会根据:
依赖路径
决定最终版本。
常见原则:
路径最近优先
路径一样时
先声明的优先
十一、最短路径优先
例如:
Project
├─ A
│ └─ X 1.0
└─ B
└─ C
└─ X 2.0
路径:
Project → A → X 1.0
Project → B → C → X 2.0
更近:
X 1.0
通常被选中。
十二、为什么“最新版本”不一定被选
Maven 不是简单:
谁版本大就选谁
它主要按:
依赖路径
仲裁。
十三、依赖冲突症状
最典型:
编译正常
运行报错
例如:
NoSuchMethodError
意思可能是:
编译时看到新版本方法
运行时实际加载旧版本
十四、NoSuchMethodError
这种错误很值得警惕:
类存在
但方法不存在
常见原因之一:
版本冲突
十五、排查依赖冲突
第一步:
mvn dependency:tree
第二步搜索目标依赖:
看看是谁引入
哪个版本最终生效
十六、dependency:tree includes
可以:
mvn dependency:tree -Dincludes=groupId:artifactId
例如:
mvn dependency:tree -Dincludes=org.slf4j:slf4j-api
十七、exclusions
如果某个依赖带来不想要的传递依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>demo-sdk</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>
org.slf4j
</groupId>
<artifactId>
slf4j-api
</artifactId>
</exclusion>
</exclusions>
</dependency>
十八、exclusions 的作用
表示:
引入 demo-sdk
但不要它传递进来的 slf4j-api
十九、不要看到冲突就乱 exclusion
正确思路:
先 dependency:tree
确认依赖来源
确认最终需要哪个版本
再排除
二十、optional
某依赖可以:
<optional>true</optional>
表示:
当前项目需要
但不希望自动传递给使用当前项目的下游项目
二十一、optional 常见于 SDK
例如:
my-sdk
内部支持:
Redis
MySQL
Kafka
但某些功能:
不是每个使用者都需要
可以 optional。
二十二、scope 对传递也有影响
常见:
compile
provided
runtime
test
它们会影响:
什么时候可见
是否向下传递
二十三、compile
默认。
通常:
编译可见
运行可见
会参与传递
二十四、provided
例如:
Servlet API
表示:
编译需要
运行环境提供
二十五、runtime
表示:
运行时需要
二十六、test
只用于:
测试
不会正常进入:
生产运行依赖
二十七、dependencyManagement
这是 Maven 高级必须掌握的核心。
它主要用于:
统一管理依赖版本
二十八、dependencyManagement 不等于真正引入依赖
例如父 pom:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>
org.mybatis
</groupId>
<artifactId>
mybatis
</artifactId>
<version>
3.5.19
</version>
</dependency>
</dependencies>
</dependencyManagement>
这并不会自动:
把 MyBatis 加入 classpath
二十九、子模块真正使用
子模块还要:
<dependency>
<groupId>
org.mybatis
</groupId>
<artifactId>
mybatis
</artifactId>
</dependency>
但可以:
不写 version
三十、dependencyManagement 的价值
假设 10 个模块都用:
MyBatis
Jackson
Lombok
不想每个模块都:
重复写版本
父工程统一:
dependencyManagement
三十一、dependencyManagement 解决版本统一
例如:
user-service
3.5.19
order-service
3.5.10
payment-service
3.5.16
会很乱。
父工程统一:
3.5.19
三十二、properties 管版本
常见:
<properties>
<java.version>
17
</java.version>
<mybatis.version>
3.5.19
</mybatis.version>
</properties>
然后:
<version>
${mybatis.version}
</version>
三十三、为什么 properties 好
以后升级:
只改一个地方
三十四、BOM 是什么
BOM:
Bill of Materials
可以理解:
依赖版本清单
它主要帮助:
统一一整套相关依赖版本
三十五、导入 BOM
常见:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>
xxx
</groupId>
<artifactId>
xxx-bom
</artifactId>
<version>
1.0.0
</version>
<type>
pom
</type>
<scope>
import
</scope>
</dependency>
</dependencies>
</dependencyManagement>
三十六、为什么 BOM 重要
例如 Spring Cloud:
几十个组件
如果自己手动配版本:
很容易不兼容
BOM:
提前定义兼容版本组合
三十七、SpringBoot parent
典型 SpringBoot 项目:
<parent>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-parent
</artifactId>
<version>
...
</version>
</parent>
三十八、spring-boot-starter-parent 做了什么
它会提供:
依赖版本管理
插件默认配置
Java 构建相关默认值
三十九、为什么 SpringBoot dependency 常不写 version
例如:
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-web
</artifactId>
</dependency>
没有 version。
因为:
SpringBoot parent / BOM
已经统一管理
四十、不要随便覆盖 SpringBoot 管理的版本
例如:
SpringBoot 管理 Jackson 版本
你手工强制:
换成另一个不兼容版本
可能出现:
NoSuchMethodError
ClassNotFoundException
四十一、父子工程
Maven 支持:
Parent POM
子模块:
继承父工程配置
四十二、父工程 packaging
常见:
<packaging>
pom
</packaging>
表示:
这个模块主要作为聚合/父工程
四十三、父工程结构
例如:
mall
│
├─ pom.xml
├─ mall-common
├─ mall-user
├─ mall-order
└─ mall-web
四十四、父 pom
<groupId>
com.example
</groupId>
<artifactId>
mall
</artifactId>
<version>
1.0-SNAPSHOT
</version>
<packaging>
pom
</packaging>
四十五、modules
<modules>
<module>
mall-common
</module>
<module>
mall-user
</module>
<module>
mall-order
</module>
</modules>
四十六、聚合是什么
父工程通过:
modules
一次构建多个模块。
执行:
mvn clean install
Maven 会根据:
模块依赖顺序
自动构建。
四十七、继承是什么
子 pom:
<parent>
<groupId>
com.example
</groupId>
<artifactId>
mall
</artifactId>
<version>
1.0-SNAPSHOT
</version>
</parent>
可以继承:
properties
dependencyManagement
pluginManagement
部分 build 配置
四十八、聚合和继承不是同一件事
聚合:
父工程知道有哪些模块
继承:
子模块继承父 pom 配置
很多项目:
二者同时使用
但概念不同。
四十九、子模块可以互相依赖
例如:
mall-user
依赖:
mall-common
写:
<dependency>
<groupId>
com.example
</groupId>
<artifactId>
mall-common
</artifactId>
<version>
${project.version}
</version>
</dependency>
五十、为什么要先 install common
如果单独构建:
mall-user
但本地仓库没有:
mall-common
可能报:
Could not resolve dependencies
五十一、从根工程构建更稳
执行:
mvn clean install
Maven Reactor 会:
自动识别模块依赖关系
按顺序构建。
五十二、Maven Reactor
多模块构建时:
参与当前构建的一组项目
称为:
Reactor
五十三、Reactor 会排序
例如:
common
↑
user
↑
web
Maven 会:
先 common
再 user
再 web
五十四、-pl
可以只构建指定模块:
mvn install -pl mall-user
五十五、-am
配合:
mvn install -pl mall-user -am
表示:
同时构建它依赖的模块
五十六、-amd
also make dependents
表示:
同时构建依赖当前模块的下游模块
五十七、为什么多模块项目很常见
企业项目会拆:
common
api
domain
service
web
或者微服务:
user-service
order-service
gateway
common
五十八、模块不要过度拆
如果项目很小:
拆 20 个模块
只会:
增加复杂度
模块拆分要根据:
职责
复用
团队边界
五十九、Maven 生命周期
Maven 有几套生命周期:
clean
default
site
六十、clean 生命周期
常见阶段:
pre-clean
clean
post-clean
六十一、default 生命周期
最常用。
包含很多阶段,例如:
validate
compile
test
package
verify
install
deploy
六十二、生命周期和 phase
例如:
mvn package
会从 default 生命周期:
前面的阶段一路执行到 package
六十三、package 不只是“打包”
执行:
mvn package
通常会先:
validate
compile
test
package
六十四、install
mvn install
会继续:
安装到本地仓库
六十五、deploy
mvn deploy
用于:
把构建产物发布到远程 Maven 仓库
例如:
公司 Nexus
六十六、verify
用于:
运行额外验证
某些集成测试、质量插件会绑定到:
verify
六十七、site 生命周期
用于:
生成项目站点 / 报告
现在普通业务开发:
使用频率较低
六十八、生命周期本身不干活
真正执行工作的是:
Plugin Goal
六十九、Plugin 是什么
Maven 插件:
真正执行构建任务的组件
例如:
编译
测试
打包
复制资源
七十、Goal
插件里面的:
具体任务
叫:
Goal
例如:
compiler:compile
七十一、Phase 和 Goal
Phase:
生命周期阶段
Goal:
插件实际执行动作
生命周期阶段可以:
绑定一个或多个 Goal
七十二、maven-compiler-plugin
用于:
编译 Java
例如:
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-compiler-plugin
</artifactId>
<configuration>
<release>
17
</release>
</configuration>
</plugin>
七十三、release
推荐 JDK 17:
<release>
17
</release>
比只写:
source / target
更完整地控制目标 Java API。
七十四、maven-surefire-plugin
主要:
运行单元测试
七十五、跳过测试
临时:
mvn package -DskipTests
通常:
跳过测试执行
但测试代码仍可能编译。
七十六、maven.test.skip
mvn package -Dmaven.test.skip=true
通常:
连测试编译也跳过
七十七、两个跳过测试区别
-DskipTests
不运行测试
但可能编译测试
-Dmaven.test.skip=true
连测试编译也跳
七十八、不要把跳测试当修复方案
如果测试代码:
有真实错误
正确做法:
修测试
而不是长期:
-DskipTests
七十九、maven-resources-plugin
负责:
处理 resources
例如:
复制 src/main/resources
八十、maven-jar-plugin
用于:
普通 JAR
打包。
八十一、maven-war-plugin
用于:
WAR
打包。
八十二、spring-boot-maven-plugin
SpringBoot 项目非常重要:
<plugin>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-maven-plugin
</artifactId>
</plugin>
八十三、SpringBoot repackage
它会:
重新组织 jar
生成:
可执行 SpringBoot JAR
八十四、为什么 java -jar 能运行
因为 SpringBoot Plugin 会:
加入启动结构
管理嵌套依赖
声明 Main-Class / Start-Class
八十五、普通 jar 和 SpringBoot jar 区别
普通 jar:
通常不包含全部依赖
SpringBoot 可执行 jar:
包含依赖结构
可以 java -jar
八十六、pluginManagement
类似:
dependencyManagement
但它管理:
插件版本和默认配置
八十七、pluginManagement 不会自动执行插件
父工程:
<pluginManagement>
<plugins>
<plugin>
...
</plugin>
</plugins>
</pluginManagement>
只是:
统一版本/配置
子模块真正使用时:
还需要声明 plugin
八十八、dependencyManagement vs pluginManagement
dependencyManagement
管理依赖版本
pluginManagement
管理插件版本
八十九、为什么插件版本也要锁
如果不锁:
不同环境
不同 Maven 版本
可能解析到:
不同插件版本
造成:
构建不稳定
九十、构建可重复性
企业构建追求:
同样源码
同样配置
尽量得到:
同样结果
所以要:
锁依赖版本
锁插件版本
九十一、Profile 是什么
Maven Profile:
构建环境配置
例如:
dev
test
prod
九十二、Profile 解决什么
不同环境可能有:
不同配置
不同插件
不同属性
不同仓库
九十三、profiles
<profiles>
<profile>
<id>
dev
</id>
<properties>
<env>
dev
</env>
</properties>
</profile>
<profile>
<id>
prod
</id>
<properties>
<env>
prod
</env>
</properties>
</profile>
</profiles>
九十四、激活 Profile
mvn package -Pdev
或者:
mvn package -Pprod
九十五、不要混淆 Maven Profile 和 Spring Profile
Maven:
构建时
Spring:
应用运行配置
例如:
Spring:
spring.profiles.active=dev
两者:
不是一个东西
九十六、Spring Profile
SpringBoot 常见:
application.yml
application-dev.yml
application-prod.yml
运行:
dev / prod
九十七、Maven Profile 什么时候用
例如:
不同构建参数
资源过滤
插件行为
发布仓库
九十八、不要把数据库密码直接放 Maven Profile
生产密钥:
不应该写进 Git
更适合:
环境变量
Secret Manager
九十九、资源过滤
Maven 可以:
把资源文件中的占位符
替换为 Maven 属性
例如:
${env}
一百、资源过滤风险
如果 SpringBoot 配置中也大量使用:
${...}
和 Maven 资源过滤混在一起:
容易冲突
要:
明确边界
一百零一、settings.xml
Maven 用户级配置:
settings.xml
常见位置:
Maven 安装目录/conf/settings.xml
和:
用户 ~/.m2/settings.xml
一百零二、settings.xml 主要配置
localRepository
mirrors
servers
profiles
proxies
一百零三、localRepository
可以修改:
本地仓库路径
例如:
<localRepository>
D:/Maven/repository
</localRepository>
一百零四、mirror
镜像:
替代某些远程仓库访问
例如:
公司镜像
国内镜像
一百零五、mirror 不是 repository
repository:
声明依赖从哪里找
mirror:
替代另一个仓库
概念不同。
一百零六、servers
用于:
远程仓库认证
例如:
Nexus 用户名密码
一百零七、为什么密码不写 pom.xml
pom.xml:
通常提交 Git
账号密码放进去:
容易泄露
认证信息更适合:
settings.xml
或:
CI Secret
一百零八、Maven 私服是什么
企业通常不会所有依赖都:
直接访问公网中央仓库
会搭:
Maven Private Repository
例如:
Nexus Repository
JFrog Artifactory
一百零九、私服作用
缓存中央仓库
加快下载
保存公司内部 jar
控制依赖
统一安全策略
一百一十、私有 SDK
例如公司:
company-common
company-auth-sdk
company-payment-sdk
不可能发布到公共中央仓库。
会发布到:
公司 Nexus
一百一十一、snapshot 仓库
通常保存:
开发快照版本
例如:
1.0-SNAPSHOT
一百一十二、release 仓库
保存:
正式稳定版本
例如:
1.0.0
一百一十三、为什么 SNAPSHOT 会变化
同样版本:
1.0-SNAPSHOT
今天和明天:
内容可能不同
所以不适合:
作为长期稳定生产依赖
一百一十四、Release 版本应该不可变
理想:
1.0.0
发布后
不再覆盖
新改动:
发 1.0.1
一百一十五、distributionManagement
用于定义:
deploy 发布目标
例如:
<distributionManagement>
<repository>
<id>
releases
</id>
<url>
...
</url>
</repository>
<snapshotRepository>
<id>
snapshots
</id>
<url>
...
</url>
</snapshotRepository>
</distributionManagement>
一百一十六、settings.xml server id
必须和:
distributionManagement.repository.id
对应。
一百一十七、mvn deploy
会把:
jar
pom
metadata
发布到:
远程仓库
一百一十八、install 和 deploy 区别
install
本地仓库
deploy
远程仓库
一百一十九、企业开发常见流程
开发 common-sdk
↓
mvn deploy
↓
发布 Nexus
↓
其他项目 dependency
↓
自动下载
一百二十、版本号设计
常见:
Major.Minor.Patch
例如:
2.3.1
一百二十一、Major
重大:
不兼容变化
一百二十二、Minor
新增:
向后兼容功能
一百二十三、Patch
Bug Fix
一百二十四、SNAPSHOT
开发版:
2.4.0-SNAPSHOT
一百二十五、不要依赖 LATEST / RELEASE
构建应该:
版本明确
避免:
今天和明天构建不同
一百二十六、effective-pom
查看 Maven 最终生效配置:
mvn help:effective-pom
一百二十七、为什么 effective-pom 很有用
因为当前 pom 还会受到:
parent
super POM
profile
dependencyManagement
影响。
你看到的 pom:
不是最终全部配置
一百二十八、help:effective-settings
查看最终 settings:
mvn help:effective-settings
一百二十九、Maven Super POM
所有 Maven 项目:
隐式继承一个 Super POM
里面有:
默认目录
默认插件绑定
中央仓库等基础配置
一百三十、所以 pom 很短也能构建
因为很多默认值来自:
Super POM
一百三十一、Maven 构建顺序
大致:
读取 settings.xml
↓
读取 pom
↓
解析 parent
↓
合并 profile
↓
生成 effective model
↓
解析依赖
↓
构建 classpath
↓
执行 lifecycle
↓
调用 plugin goals
一百三十二、Maven 本地仓库损坏
有时候依赖下载中断:
jar / pom 不完整
可能出现:
奇怪解析错误
可以:
删除该依赖对应目录
重新下载
不要:
整个 repository 全删
除非必要。
一百三十三、.lastUpdated
Maven 下载失败后可能留下:
*.lastUpdated
短时间内:
不再重复尝试下载
一百三十四、强制更新
可以:
mvn clean package -U
-U:
强制检查更新
特别:
SNAPSHOT
一百三十五、离线模式
mvn -o package
表示:
Offline
只使用:
本地仓库
一百三十六、为什么离线可能失败
如果某依赖:
本地没下载过
自然:
无法解析
一百三十七、Maven Wrapper
很多项目有:
mvnw
mvnw.cmd
.mvn/
一百三十八、Wrapper 作用
让项目:
固定 Maven 版本
开发者不用:
每台机器手工装完全一致版本
一百三十九、执行 Maven Wrapper
Windows:
.\mvnw.cmd clean package
Linux/macOS:
./mvnw clean package
一百四十、为什么企业 CI 喜欢 Wrapper
因为:
开发机
CI Server
其他同事
都能:
使用项目指定 Maven 版本
一百四十一、SpringBoot 多模块示例
rbac-parent
│
├─ rbac-common
├─ rbac-domain
├─ rbac-service
└─ rbac-web
一百四十二、rbac-common
放:
Result
Exception
Utils
Common Constants
一百四十三、rbac-domain
放:
Entity
DTO
VO
一百四十四、rbac-service
放:
Service
Mapper
MyBatis XML
一百四十五、rbac-web
放:
SpringBoot Application
Controller
Interceptor
Web Config
一百四十六、是不是一定要这么拆
不是。
项目小:
单模块更简单
学习阶段不应该:
为了“高级”
强行多模块
一百四十七、什么时候拆模块
满足:
代码明显分层
模块可复用
团队多人协作
构建边界清晰
再拆。
一百四十八、SpringBoot 多模块主启动类
通常只有:
web / application 模块
需要:
@SpringBootApplication
其他模块:
普通 jar
一百四十九、SpringBoot Plugin 不要每个模块都 repackage
common 模块如果:
也执行 SpringBoot repackage
可能造成:
普通依赖 jar 结构异常
一般:
只有可运行模块
使用 spring-boot-maven-plugin
一百五十、这是多模块常见坑
父工程如果统一配置:
spring-boot-maven-plugin
要注意:
是否所有子模块都真正需要执行
一百五十一、Plugin 继承问题
父 pom:
plugins
中的插件可能:
被子模块继承
如果只是想:
统一版本
但不自动执行
使用:
pluginManagement
更合适。
一百五十二、依赖也一样
父 pom 直接写:
dependencies
子模块通常:
会继承依赖
如果只想:
统一版本
应该用:
dependencyManagement
一百五十三、父 pom 常见正确职责
统一 Java 版本
统一依赖版本
统一插件版本
声明 modules
少量所有模块都需要的公共配置
一百五十四、父 pom 不要什么都塞
如果某依赖:
只有 web 模块使用
不要因为方便:
直接放父 dependencies
否则:
所有子模块都被污染
一百五十五、依赖最小化
每个模块:
只声明自己真正需要的依赖
优点:
边界清晰
减少冲突
减少包体积
一百五十六、Maven Classpath
不同阶段:
compile classpath
test classpath
runtime classpath
并不完全一样。
一百五十七、为什么 IDEA 能运行但 Maven 失败
可能:
IDEA 项目配置
和
Maven pom
不一致
例如:
IDEA 手工加了 jar
但 pom 没有
一百五十八、正确原则
Maven 项目:
pom.xml
应该是依赖真相来源
不要长期依赖:
IDEA 手工 Libraries
一百五十九、为什么命令行构建很重要
如果:
只有 IDEA 点 Run 能成功
CI:
可能失败
所以项目至少应该:
mvn clean package
能够成功。
一百六十、CI/CD 中 Maven
典型:
Git Push
↓
CI
↓
mvn clean test
↓
mvn package
↓
生成 jar
↓
Docker Build
↓
Deploy
一百六十一、为什么 test 阶段不能长期跳过
CI 的价值之一:
自动发现回归问题
如果:
永远 skipTests
测试就失去意义。
一百六十二、构建缓存简单了解
现代 CI 为了加快:
缓存 ~/.m2/repository
避免:
每次从头下载所有依赖
一百六十三、不要把整个本地仓库提交 Git
.m2/repository:
不是项目源码
不要:
提交 Git
一百六十四、settings.xml 也不要随便提交敏感信息
特别:
私服密码
Token
一百六十五、Maven 编译编码
pom:
<properties>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
<project.reporting.outputEncoding>
UTF-8
</project.reporting.outputEncoding>
</properties>
一百六十六、为什么编码要统一
避免:
中文乱码
不同电脑构建结果不同
一百六十七、Maven JDK 和 IDEA JDK
可能有多个地方:
Project SDK
Maven Runner JDK
JAVA_HOME
如果不一致:
会出现奇怪问题
一百六十八、查看 Maven 实际 JDK
mvn -version
会显示:
Maven version
Java version
Java home
一百六十九、如果 JDK 17 但 Maven 显示旧 JDK
检查:
JAVA_HOME
IDEA Maven Runner JDK
一百七十、不支持发行版本 5
如果看到:
不支持发行版本 5
常见:
编译插件/默认 source target 太旧
推荐:
<maven.compiler.release>
17
</maven.compiler.release>
一百七十一、如果编译失败几十个错误
不要只看最后:
35 errors
重点:
从第一个真正错误开始
后面很多:
可能是连锁错误
一百七十二、JUnit 被删除后 test 仍报错
如果删:
JUnit dependency
但:
src/test/java
还存在:
import org.junit.jupiter...
Maven:
testCompile
仍然会失败。
一百七十三、依赖和源码必须匹配
不要:
只删依赖
不删使用依赖的代码
一百七十四、effective dependency version
如果不知道某依赖:
为什么版本是这个
可以:
dependency:tree
effective-pom
组合排查。
一百七十五、dependencyManagement 不会强制所有库兼容
它只是:
指定版本
并不能保证:
这个版本组合一定兼容
所以最好使用:
框架官方 BOM
一百七十六、不要随意升级单个 Spring 组件
例如 SpringBoot 管:
Spring Framework
Jackson
Tomcat
版本是一套测试过的组合。
你只升级:
spring-web
可能破坏:
整体兼容
一百七十七、SpringBoot 升级正确思路
优先:
升级 SpringBoot 整体版本
让 BOM:
统一带动依赖升级
一百七十八、为什么 Maven 能支撑 SpringBoot 自动配置
流程:
dependency
↓
jar 进入 classpath
↓
SpringBoot ConditionalOnClass
↓
检测类是否存在
↓
决定 AutoConfiguration
所以:
Maven 依赖
直接影响 SpringBoot 自动配置
一百七十九、例如 Web Starter
pom:
starter-web
带来:
Spring MVC
Tomcat
Jackson
SpringBoot:
发现这些类存在
↓
自动配置 Web
一百八十、删掉 Starter 会怎样
如果没有:
Spring MVC
相关 class:
Web 自动配置条件可能不成立
一百八十一、这就是 Maven 和 SpringBoot 底层真正连接点
Maven
决定 classpath
SpringBoot
根据 classpath 自动配置
一百八十二、常见错误 1:dependencyManagement 当成 dependency
父 pom 已经:
dependencyManagement
子模块却没有真正:
dependency
结果:
类找不到
一百八十三、常见错误 2:父 dependencies 污染所有模块
把:
MySQL Driver
Web
MyBatis
全写父:
dependencies
导致:
common 模块也拥有所有依赖
一百八十四、常见错误 3:版本冲突
症状:
NoSuchMethodError
先:
mvn dependency:tree
一百八十五、常见错误 4:私服认证失败
例如:
401 Unauthorized
检查:
settings.xml servers
id 是否匹配
用户名密码
一百八十六、常见错误 5:镜像配置导致所有仓库失效
mirrorOf:
配置错误
可能:
所有依赖都拉不到
一百八十七、常见错误 6:SNAPSHOT 没更新
使用:
mvn clean package -U
强制检查。
一百八十八、常见错误 7:多模块单独运行失败
子模块依赖:
另一个本地模块
但对方:
还没 install
一百八十九、常见错误 8:父 pom 找不到
子 pom:
parent relativePath
或:
仓库中没有 parent
可能:
Non-resolvable parent POM
一百九十、relativePath
默认 Maven 会尝试:
../pom.xml
找父 pom。
也可以:
<relativePath>
../pom.xml
</relativePath>
一百九十一、如果父 POM 在远程仓库
可以:
<relativePath/>
表示:
不要从本地相对路径找
一百九十二、常见错误 9:插件重复执行
父和子都:
绑定同一个 execution
可能:
执行两次
一百九十三、常见错误 10:资源文件没打进去
检查:
src/main/resources
resources plugin
exclude/include
一百九十四、检查 jar 内容
可以:
jar tf target/app.jar
查看:
实际打包文件
一百九十五、SpringBoot 配置没进 jar
检查:
application.yml
是否在:
src/main/resources
一百九十六、MyBatis XML 没进 jar
检查:
src/main/resources/mapper
而不是放:
src/main/java
除非额外配置资源复制。
一百九十七、为什么 Mapper XML 放 resources
Maven 默认:
src/main/resources
会复制到:
classpath
MyBatis:
按 classpath 加载
一百九十八、这和之前 mybatis-config.xml 报错一样
错误:
Could not find resource mybatis-config.xml
根本原因常常:
文件没进入 classpath
一百九十九、classpath 思维非常重要
Maven:
决定哪些 class/resource
进入构建输出
框架:
从 classpath 查找
二百、常用 Maven 命令清单
mvn -version
mvn clean
mvn compile
mvn test
mvn package
mvn verify
mvn install
mvn deploy
mvn dependency:tree
mvn help:effective-pom
mvn help:effective-settings
二百零一、常用参数
-U
强制更新
-P
激活 profile
-pl
指定模块
-am
同时构建依赖模块
-amd
同时构建下游模块
-DskipTests
跳过测试执行
-o
离线
二百零二、企业排错顺序
Maven 报错:
1. 看第一个 Error
2. 看 mvn -version
3. 看依赖是否存在
4. dependency:tree
5. effective-pom
6. settings.xml
7. 网络/私服
8. 本地仓库具体依赖目录
二百零三、不要上来删除整个 .m2
因为:
会导致所有依赖重新下载
浪费时间。
优先:
定位具体坏依赖
二百零四、IDEA Maven Reload
修改 pom 后:
Maven 工具窗口
→ Reload All Maven Projects
二百零五、IDEA Maven 面板
常见:
Lifecycle
Plugins
Dependencies
可以:
双击生命周期命令
执行。
二百零六、IDEA 快捷操作
pom 依赖报红:
Alt + Enter
重新加载:
Maven 工具窗口 Reload
二百零七、练习 1:dependency:tree
执行:
mvn dependency:tree
找:
spring-core
jackson-databind
tomcat-embed-core
看看:
是谁传递进来的
二百零八、练习 2:依赖冲突
故意引入:
同一库两个不同版本来源
观察:
dependency:tree
的:
omitted for conflict
二百零九、练习 3:exclusion
找到一个传递依赖:
使用 exclusions 排除
再:
dependency:tree
验证。
二百一十、练习 4:dependencyManagement
创建父工程:
parent
统一:
mybatis version
子模块:
dependency 不写 version
二百一十一、练习 5:多模块
创建:
demo-parent
demo-common
demo-service
demo-web
让:
web → service → common
二百一十二、练习 6:Reactor
根目录执行:
mvn clean install
观察:
Reactor Build Order
二百一十三、练习 7:-pl -am
执行:
mvn package -pl demo-web -am
观察:
依赖模块一起构建
二百一十四、练习 8:effective-pom
mvn help:effective-pom
搜索:
spring-boot
maven-compiler-plugin
查看:
父 POM 带来的最终配置
二百一十五、练习 9:Profile
创建:
dev
prod
执行:
mvn package -Pdev
和:
mvn package -Pprod
二百一十六、练习 10:SpringBoot 打包
执行:
mvn clean package
然后:
java -jar target/xxx.jar
二百一十七、练习 11:跳过测试
分别:
mvn package -DskipTests
和:
mvn package -Dmaven.test.skip=true
理解区别。
二百一十八、练习 12:资源路径
把:
mybatis-config.xml
放:
src/main/resources
打包后:
jar tf target/xxx.jar
观察是否存在。
二百一十九、练习 13:Maven Wrapper
如果项目有:
mvnw.cmd
Windows 执行:
.\mvnw.cmd -version
二百二十、必须掌握依赖管理
Direct Dependency
Transitive Dependency
Conflict
Mediation
Exclusion
Optional
Scope
二百二十一、必须掌握版本管理
properties
dependencyManagement
BOM
parent
二百二十二、必须掌握多模块
parent
modules
inheritance
aggregation
Reactor
-pl
-am
二百二十三、必须掌握生命周期
clean
compile
test
package
verify
install
deploy
二百二十四、必须掌握插件
maven-compiler-plugin
maven-surefire-plugin
maven-resources-plugin
maven-jar-plugin
maven-war-plugin
spring-boot-maven-plugin
二百二十五、必须掌握配置
settings.xml
profile
mirror
server
localRepository
二百二十六、必须掌握私服
Nexus
Snapshot
Release
distributionManagement
deploy
二百二十七、面试题 1
问:
Maven 依赖传递是什么?
答:
如果项目 A 依赖 B,
而 B 又依赖 C,
那么 A 通常可以自动获得 C,
这就是依赖传递。
二百二十八、面试题 2
问:
Maven 依赖冲突怎么解决?
答:
先使用 mvn dependency:tree
查看依赖路径和最终版本。
Maven 主要根据最短依赖路径进行仲裁,
同层级时会受到声明顺序影响。
可以通过直接声明目标版本、
dependencyManagement、
exclusions 等方式统一版本。
二百二十九、面试题 3
问:
dependencyManagement 和 dependencies 区别?
答:
dependencies 会真正引入依赖。
dependencyManagement 主要负责统一管理版本,
不会自动把依赖加入当前模块。
子模块真正需要使用时,
仍需要声明 dependency,
但可以省略 version。
二百三十、面试题 4
问:
聚合和继承有什么区别?
答:
聚合通过 modules 管理一次构建哪些模块。
继承通过 parent 让子模块继承父 POM 的配置。
二者经常一起使用,
但概念不同。
二百三十一、面试题 5
问:
Maven lifecycle 和 plugin 有什么区别?
答:
Lifecycle 定义构建阶段和执行顺序。
真正执行编译、测试、打包等工作的
是 Maven Plugin 的 Goal。
生命周期阶段会绑定相应的插件 Goal。
二百三十二、面试题 6
问:
install 和 deploy 区别?
答:
install 把当前项目产物安装到本地 Maven 仓库。
deploy 把构建产物发布到远程仓库,
通常是公司 Nexus 或 Artifactory。
二百三十三、面试题 7
问:
BOM 是什么?
答:
BOM 是 Bill of Materials,
用于统一管理一组相关依赖的版本。
常用于保证一套框架组件之间的版本兼容。
二百三十四、面试题 8
问:
为什么 SpringBoot dependency 常不写版本?
答:
因为 SpringBoot Parent 或 BOM
已经通过 dependencyManagement
统一管理了相关依赖版本。
业务项目只需要声明 artifact,
由 SpringBoot 版本体系决定兼容版本。
二百三十五、面试题 9
问:
-DskipTests 和 -Dmaven.test.skip=true 区别?
答:
-DskipTests 通常只是跳过测试执行,
测试代码仍会进行编译。
-Dmaven.test.skip=true
通常连测试代码编译也会跳过。
二百三十六、面试题 10
问:
为什么企业需要 Maven 私服?
答:
私服可以缓存公共依赖,
提升下载速度和稳定性。
同时可以存储公司内部 SDK,
统一控制依赖来源、版本和安全策略。
二百三十七、面试题 11
问:
为什么会出现 NoSuchMethodError?
答:
常见原因之一是依赖版本冲突。
编译时依赖的版本包含某个方法,
但运行时实际加载的是另一个较旧版本,
就可能出现类存在但方法不存在。
二百三十八、面试题 12
问:
SpringBoot Starter 和 Maven 有什么关系?
答:
Starter 本质上就是 Maven 依赖组合。
它把一组相关库一次性引入 classpath。
SpringBoot 自动配置再根据 classpath
中的类和配置条件决定创建哪些 Bean。
二百三十九、Maven 高级总知识树
Maven
│
├─ Dependency
│ ├─ direct
│ ├─ transitive
│ ├─ conflict
│ ├─ mediation
│ ├─ exclusion
│ ├─ optional
│ └─ scope
│
├─ Management
│ ├─ properties
│ ├─ dependencyManagement
│ ├─ BOM
│ └─ parent
│
├─ Module
│ ├─ packaging=pom
│ ├─ modules
│ ├─ inheritance
│ ├─ aggregation
│ └─ reactor
│
├─ Lifecycle
│ ├─ clean
│ ├─ compile
│ ├─ test
│ ├─ package
│ ├─ verify
│ ├─ install
│ └─ deploy
│
├─ Plugin
│ ├─ compiler
│ ├─ surefire
│ ├─ resources
│ ├─ jar
│ ├─ war
│ └─ spring-boot
│
├─ Profile
│ ├─ dev
│ ├─ test
│ └─ prod
│
├─ Repository
│ ├─ local
│ ├─ central
│ ├─ mirror
│ └─ private
│
└─ Enterprise
├─ Nexus
├─ CI
├─ Wrapper
└─ Version Strategy
二百四十、Maven 和前面所有知识的联系
Maven 和 SpringBoot:
Starter
↓
classpath
↓
AutoConfiguration
Maven 和 MyBatis:
依赖
+
resources
↓
Mapper XML
↓
classpath
Maven 和 Tomcat:
package
↓
WAR / JAR
↓
部署运行
Maven 和 JDK:
compiler plugin
↓
release=17
Maven 和企业项目:
多模块
↓
统一版本
↓
私服
↓
CI/CD
二百四十一、第二阶段总回顾
第二阶段已经完成:
单元测试 / 配置文件 / JavaBean
单例 / 匿名内部类 / 枚举
反射 / 内省 / 注解
Lambda
Stream
MyBatis 基础
Servlet
JSP
EL / JSTL
Web CRUD / MVC
MySQL 进阶
Maven / HTTP / Tomcat
MyBatis 进阶
SpringBoot 请求响应
RESTful
RBAC 综合案例
事务管理 / AOP
SpringBoot 底层原理
Maven 高级
二百四十二、你现在应该达到什么程度
至少应该能够:
写基础 Java Web 项目
写 SpringBoot Controller
使用 MyBatis
设计 RESTful API
处理事务
理解 AOP
设计 RBAC 权限
理解 SpringBoot 自动配置
排查 Maven 依赖冲突
构建 SpringBoot 项目
二百四十三、进入第三阶段前建议复习
重点复习:
SpringBoot
MyBatis
RESTful
事务
AOP
RBAC
Maven
Git 基础
因为第三阶段开始:
项目比知识点更重要
二百四十四、第三阶段会发生什么变化
前两阶段:
主要学习“一个知识点是什么”
第三阶段:
开始学习“多个技术如何组合成企业项目”
包括:
Vue
Activiti
Git
Redis
若依
企业项目
MyBatis Plus
二百四十五、本章总结
Maven 高级最核心的五块:
依赖
版本
多模块
构建
仓库
依赖:
传递
冲突
仲裁
exclusion
版本:
properties
dependencyManagement
BOM
多模块:
parent
modules
inheritance
aggregation
Reactor
构建:
Lifecycle
+
Plugin
企业仓库:
Nexus
Snapshot
Release
deploy
最重要排错命令:
mvn -version
mvn dependency:tree
mvn help:effective-pom
mvn help:effective-settings
最重要理解:
Maven 决定 classpath
而 SpringBoot:
根据 classpath
决定自动配置
因此:
Maven 不是单纯“下载 jar 的工具”
而是整个 Java 项目的:
依赖管理器
构建系统
模块管理工具
发布工具
到这里:
第二阶段 Java 核心框架
正式完成
下一阶段进入:
第三阶段:Java 企业项目 + AI 助手
第一部分:
AI 驱动 Web - Vue
后面将从:
SpringBoot 后端
继续衔接:
Vue 前端
Axios
前后端联调
组件化
页面路由
Element Plus
项目实战