Maven高级详解

O泡李华 5

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

项目实战