1. 从零理解Maven:它不是“又是一个要装的工具”
先不谈配置,不谈命令,我们聊聊 Maven 在你日常开发里到底扮演什么角色。
你肯定遇到过这种情况:项目里要引入一个 JSON 库,你得先去官网下载 jar 包,拷进项目,再手动加到 classpath,如果这个库还依赖别的库,你还得把传递依赖一个个找齐。运气好碰上有文档的,运气不好就靠搜索引擎一篇篇翻,折腾半天才能编译通过。这还只是单机开发,如果是团队协作,每个人下的依赖版本都不一样,A 用 2.0,B 用 3.1,C 就莫名其妙编译报错——这种问题我见得太多太多了。
Maven 解决的就是这几件事:依赖管理、构建标准化和项目生命周期管理。你只需要在一个叫pom.xml的文件里声明要用什么库、什么版本,Maven 自己去仓库里下载,把所有传递依赖一并搞定;你也不需要记住javac、jar、cp这一串手动指令,只需要执行mvn clean package,它就把编译、测试、打包、生成报告全部做完。说得直白点,Maven 就是一个“项目管家”,从你新建项目的那一刻起,它告诉你目录怎么建、依赖怎么加、构建怎么跑、产物在哪,全流程标准化。
这篇文章适合谁?刚接触 Maven 的新人,从零搭环境、配仓库、跑通第一个构建;也适合用了很久但一直只是“idea 里点一下刷新”的开发者,搞清楚配置文件里那些参数到底是什么意思、依赖报错怎么排查、多个仓库之间怎么切换。有些内容属于“平时用不到,遇到问题才后悔没看”的偏门技巧,我尽量都用大白话讲清楚,跟着操作一遍基本能落地。
我最早用 Maven 也是稀里糊涂,pom.xml 全靠复制粘贴,settings.xml 出了问题就上网搜,搜到啥改啥,越改越乱。所以这篇笔记,我从头捋一遍,把原理、配置、命令、避坑点放一起,当成一个完整的入门手册来写。
2. 核心概念先搞懂:POM、坐标、仓库、生命周期
2.1 POM:整个项目的“总控文件”
每个 Maven 项目根目录下都有一个pom.xml,全称 Project Object Model,意思是“项目对象模型”。它定义了项目的基本信息、依赖、插件、构建配置。可以说,Maven 一切行为的起点都是这个文件,没有它,Maven 根本不知道从哪下手。
一个最简单的 pom.xml 长这样:
<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</version> <packaging>jar</packaging> </project>解释几个关键标签:
groupId:组织的唯一标识,一般是公司域名反写,比如com.example。artifactId:项目本身的名称,比如demo-project。version:项目版本号,开发中常用1.0-SNAPSHOT表示快照版本。packaging:打包方式,常见jar、war、pom。如果是聚合工程,父工程打包方式就是pom。
这四项合起来,就是 Maven 中的“坐标”。任何构件在仓库里都有一个独一无二的坐标,Maven 通过坐标定位并下载对应的 jar 包。
2.2 仓库机制:本地仓库、中央仓库、私服,到底谁先谁后
Maven 找依赖的顺序是固定的,这个顺序非常影响你对“依赖报错”的排查。
第一站是本地仓库。默认路径在用户目录下的.m2/repository,比如 Windows 上是C:\Users\你的用户名\.m2\repository,macOS/Linux 是~/.m2/repository。依赖第一次从远程下载后会缓存到这里,下次直接从本地读取,不联网也能编译(前提是依赖都齐了)。
第二站是中央仓库。这是 Maven 官方的公共仓库,地址是https://repo.maven.apache.org/maven2/,里边几乎什么库都有,但服务器在国外,国内访问速度不稳定。这也就是为什么我们要配阿里云镜像——本质是用国内服务器做中转,下载速度快很多。
第三站是私服(可选)。公司内部往往有 Nexus 或 Artifactory 搭的私服,用来存放私有 jar 包,也能代理中央仓库。如果你在settings.xml里配置了私服,Maven 会优先从私服拉取。
“我有两个本地仓库,怎么合并?”这个问题之所以会出现,就是因为本地仓库默认只有一个,但有的人电脑上改了settings.xml中的 localRepository 路径,或者换了台电脑拷贝了另一个仓库目录,导致依赖分散在两个地方,编译时这个从默认仓库找、那个从自定义仓库找,麻烦得很。
2.3 生命周期:clean、install、package 这些命令到底做了什么
Maven 有三套生命周期,分别是clean、default(默认)、site。我们日常用的命令都是基于这三套来的。
clean:清理,删掉target目录下的编译产物。default:核心生命周期,按顺序执行 validate → compile → test → package → verify → install → deploy。site:生成项目站点文档,用得少。
关键点在于:你执行mvn package时,它会按顺序把 package 之前的所有阶段都执行一遍,包括 compile、test,并不是只打个包就完事。同理,mvn install会执行到 install,意味着先编译、测试、打包,再把 jar 装进本地仓库。所以团队里联调时,本地模块有改动,必须先install,别人才能拉到你的最新版本。
注意:如果你只想跳过测试进行打包,可以用
mvn package -DskipTests,这个-DskipTests是跳过测试编译和执行,通常在临时验证构建产物时用。
3. 环境准备与配置:从下载到跑通第一个命令
3.1 下载安装与 JDK 版本对应关系
Maven 本身是 Java 写的,运行它需要 JDK。版本对应关系这个必须提前确认好,不然 Maven 装完启动时报错,你会一头雾水。
直接给结论:
| Maven 版本 | 最低 JDK 版本 | 说明 |
|---|---|---|
| Maven 3.3+ | JDK 1.7 | 老项目偶尔碰到 |
| Maven 3.6+ | JDK 1.8 | 目前最主流,兼容 8/11/17 |
| Maven 3.8+ | JDK 1.8 | 同上 |
| Maven 3.9.x | JDK 1.8 | 可以跑在 8、11、17、21 上 |
| Maven 4.x | JDK 1.8 以上 | 新版本,建议搭配 17+ 使用 |
推荐新手直接下载 Maven 3.9.x 或 3.8.x,搭配 JDK 8 或 JDK 17。别图新下载 4.x,教程少,插件兼容性还有坑,没必要。
下载地址用官方链接:https://maven.apache.org/download.cgi,进去找 “Files” 栏目,下载apache-maven-3.9.x-bin.tar.gz(macOS/Linux)或apache-maven-3.9.x-bin.zip(Windows)。如果官网下载慢,可以考虑国内镜像站点下载,但要注意校验文件完整性,直接用压缩包解压就行。
3.2 环境变量配置:Windows、macOS、Linux 各来一遍
这一步的目标很简单:让你在命令行任意路径输入mvn -v都能看到版本信息。
Windows 上的操作:
- 解压 Maven 到一个固定目录,比如
D:\dev\apache-maven-3.9.9,避免路径里有中文和空格。 - 右键“此电脑” → 属性 → 高级系统设置 → 环境变量。
- 新建系统变量
MAVEN_HOME,值为 Maven 解压路径。 - 编辑
Path变量,新增一行%MAVEN_HOME%\bin。 - 打开新命令行窗口,输入
mvn -v验证。
macOS 上推荐用 Homebrew,一条命令搞定:
brew install maven装完检查一下/opt/homebrew/bin是否在 PATH 里,输入mvn -v即可。如果非要手动安装,下载解压后编辑~/.zshrc,添加如下两行:
export MAVEN_HOME=/Users/你的用户名/app/apache-maven-3.9.9 export PATH=$MAVEN_HOME/bin:$PATH然后执行source ~/.zshrc。
Linux(以 CentOS/Ubuntu 为例):
# 下载解压 tar -xzf apache-maven-3.9.9-bin.tar.gz -C /opt/ # 编辑 /etc/profile 或 ~/.bashrc export MAVEN_HOME=/opt/apache-maven-3.9.9 export PATH=$MAVEN_HOME/bin:$PATH执行source /etc/profile后验证。
配置完环境变量最典型的报错是mvn: command not found,十有八九是 PATH 没加对或者没开新终端。另外一个坑:Windows 上如果你之前装过 Maven 的其他版本,环境变量里的 Path 可能有冲突,优先检查MAVEN_HOME是否指向了你想要的那个版本。
3.3 验证安装:mvn -v 输出怎么看
输入mvn -v,正常输出类似:
Apache Maven 3.9.9 (8e3f3e0d2e7f0b4e0d4f6f1e1a0e1f2e3f4e5f6) Maven home: /opt/apache-maven-3.9.9 Java version: 17.0.5, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-17 Default locale: zh_CN, platform encoding: UTF-8 OS name: "linux", version: "5.15.0", arch: "amd64", family: "unix"看三行:Maven home 是否正确,Java version 是否是你要的版本,platform encoding 是不是 UTF-8。如果 Java 版本不对,说明你的 JAVA_HOME 配置有问题,Maven 用的是JAVA_HOME环境变量指向的 JDK,不是你命令行里的 java。这个细节容易踩坑,尤其是电脑上装了多个 JDK 时。
4. 配置国内可用的仓库:settings.xml 与阿里云镜像实战
4.1 settings.xml 到底在哪里,改哪个
Maven 有两套settings.xml:
- 全局配置:在 Maven 解压目录下的
conf/settings.xml,影响这台电脑上所有用户、所有项目。 - 用户配置:在
~/.m2/settings.xml,只影响当前用户。如果两个都存在,用户配置优先。
一般建议改用户配置,因为升级 Maven 时不会覆盖全局配置,也不会影响别人。直接在~/.m2/下新建 settings.xml 内容复制全局配置再改即可。新手不知道改的是哪个文件的时候,在命令行执行:
mvn help:effective-settings它会列出最终生效的配置文件路径和内容,非常直观。
4.2 配置阿里云镜像:三步让下载速度起飞
在国内用中央仓库下载依赖,那个速度简直不能忍,一个小 jar 等半天是常事。配置镜像就是给仓库访问换一个“就近入口”。
在settings.xml的<mirrors>节点下加一段:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>核心是mirrorOf标签,它表示这个镜像代理哪个远程仓库。central表示代理中央仓库。注意:https://maven.aliyun.com/repository/public这个地址是阿里云提供的公共聚合仓库,它包含中央仓库渠道和公共 JCenter 渠道,覆盖绝大多数依赖。如果某些内部构件需要单独仓库,后面会讲多镜像怎么配。
修改完保存,随便在项目里执行mvn clean compile,你会看到日志里下载速度直接从几十 K 变成几 M。如果还是慢,检查一下是不是镜像没生效——执行以下命令看 effective settings:
mvn help:effective-settings确认 mirrors 节点里有没有阿里云配置。
4.3 配置多个镜像仓库:单个镜像满足不了项目需要时
有些项目既需要中央仓库的公共依赖,又需要公司私服里的内部构件。如果只配一个<mirror>且mirrorOf设为*,会把所有请求都指过去,万一这个镜像没有你要的依赖,构建直接失败。正确的做法是配置多个 mirror,并精确控制每个 mirror 代理的范围。
举例:公司私服地址是http://nexus.internal.com/repository/maven-public/,你想让公共依赖走阿里云,私服走公司内网。
<mirrors> <!-- 公司私服 --> <mirror> <id>internal-nexus</id> <mirrorOf>internal-repo</mirrorOf> <name>公司内网仓库</name> <url>http://nexus.internal.com/repository/maven-public/</url> </mirror> <!-- 阿里云镜像 --> <mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> </mirrors>然后在<repositories>节点里声明一个 id 为internal-repo的仓库:
<repositories> <repository> <id>internal-repo</id> <url>http://nexus.internal.com/repository/maven-public/</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>true</enabled></snapshots> </repository> </repositories>这样 Maven 遇到内部构件时,会通过镜像走上内网仓库,公共依赖走上阿里云。实际使用中,公共依赖占多数,阿里云仓库速度优势能充分利用,而内部构建也不会因为阿里云上找不到而失败。
4.4 修改本地仓库路径并合并多个 repository
前面提到过“两个本地仓库怎么合并”的问题。这种场景有几种:
- 电脑上残留了旧仓库目录,新仓库在默认路径。
- 从同事那拷了一个 repository 目录,里面有几个需要的 jar 但版本比较旧。
- 以前手动改过 localRepository,之后又忘了。
最简单的处理是:确定一个主仓库目录,把另一个合并进去。因为 Maven 的本地仓库本质上就是“坐标 → jar 文件”的目录结构,每个依赖都在类似com/example/demo-project/1.0.0/demo-project-1.0.0.jar这样的路径下。合并时把旧仓库中缺失的构件文件夹复制到主仓库即可。
步骤:
- 先确定主仓库路径。如果默认在
~/.m2/repository,就让它当主仓库。 - 打开命令行,执行:
# 查看当前本地仓库路径 mvn help:evaluate -Dexpression=settings.localRepository -q -DforceStdout如果输出null,说明没改过配置,用的默认路径;如果输出具体路径,说明之前改过。
以主仓库为准,需要合并的内容把两个目录做一次对比,把旧仓库里主仓库没有的文件夹复制过去。复制时注意别把
_remote.repositories和.lastUpdated这些元数据文件硬覆盖,那些是 Maven 用来校验远程下载状态的,搞乱了之后可能触发奇怪的“已存在但无法解析”的报错。拷贝完后再执行
mvn dependency:resolve验证依赖是否都解析成功。
这里必须提醒一点:不用刻意让两个仓库“合二为一”。如果你的依赖分散在多个磁盘位置,更优雅的做法是让本地仓库只留最新的一个,其余通过配置<repository>或<localRepository>实现多路径加载——但说句实在话,Maven 并不直接支持“本地仓库多路径”。它只认一个localRepository。所以想合并,整理合并目录是最踏实的方案。
5. 在 IDE 中集成 Maven:IDEA 与 VSCode 的差异化配置
5.1 IDEA:核心配置只有三处
IDEA 是 Java 开发最常用的 IDE,配置 Maven 的路径是Settings → Build, Execution, Deployment → Build Tools → Maven。
你需要改三个地方:
- Maven home path:选择 Maven 解压目录。IDEA 自带了一个 Maven,有时会用自带版本执行构建,建议手动指向你自己的 Maven。
- User settings file:右边有 Override 复选框,勾选后选择你的
settings.xml。 - Local repository:IDEA 会自动识别 settings.xml 里配置的本地仓库路径,但如果你没改过 settings,它会默认用自己的
.m2/repository,这里建议手动确认一下,改成你想要的那个目录。
如果你在 pom.xml 里加依赖后 IDEA 一直爆红,大概率是没刷新。右侧 Maven 工具栏点一下刷新按钮,或者用快捷键Ctrl + Shift + O(Windows/Linux)/Command + Shift + O(macOS)。这个操作会重新解析依赖,如果还是报错,再看下面的依赖排查章节。
还有个小技巧:IDEA 的 Build 和 Maven 是两套体系。有时候 IDEA 自带编译器编译通过,但命令行mvn clean package报错,这是正常的,因为 IDEA 内置编译器走的是它自己的依赖解析逻辑,跟 Maven 不完全一致。最终以 Maven 的构建结果为准。
5.2 VSCode:插件组合了解一下
VSCode 配置 Maven 没有 IDEA 那么集成化,但它有一个比较轻量的套路。
先装两个插件:Extension Pack for Java(微软官方)和Maven for Java(也是微软官方)。前者管 Java 语言支持,后者管 Maven 项目解析。
VSCode 里 Maven 的配置其实走的是 settings.json:
{ "java.configuration.maven.userSettings": "/Users/你的用户名/.m2/settings.xml", "java.configuration.maven.globalSettings": "/path/to/global/settings.xml", "maven.executable.path": "/opt/apache-maven-3.9.9/bin/mvn" }如果你发现 VSCode 加载项目时依赖解析很慢,或者总提示“cannot resolve symbol”,大概率是 VSCode 找不到 Maven 的可执行文件或 settings.xml 路径不对。帮 VSCode 明确指定后,重启窗口即可。
注意:VSCode 修改配置后,建议执行
Java: Clean Java Language Server Workspace或者重载窗口,否则语言服务可能缓存旧配置。
5.3 IDE 配置 JDK 与 Maven 的常见坑
很多新手在 IDEA 里配置 JDK 时,只设置了 “Project SDK”,但 Maven 用的是自己的 JDK 配置。在 IDEA 中,Maven runner 的 JDK 和项目编译的 JDK 是两个维度。
- 项目编译 JDK:
Project Structure → Project → SDK - Maven 运行 JDK:
Settings → Build Tools → Maven → Runner → JRE
如果发现 Maven 构建时用的版本不对,优先检查 Runner 里的 JRE 选项。VSCode 中则通过java.configuration.runtimes配置:
"java.configuration.runtimes": [ { "name": "JavaSE-17", "path": "/usr/lib/jvm/java-17", "default": true } ]这个配置的意义在于,Maven 编译插件(如 compiler-plugin)会读取 JAVA_HOME 环境变量或者 IDE 传入的 JDK 路径。如果环境变量和 IDE 内部的 JDK 不一致,可能出现“编译报错 class file version 错误”。本质就是 JDK 版本冲突,不要慌,统一 JDK 版本后重新构建即可。
6. 依赖管理进阶:从依赖报错到多模块项目
6.1 那些年我们一起踩过的依赖报错,全在这了
依赖报错是新手遇到最多的拦路虎。我把常见报错和对应排查方向整理成一个速查表:
| 报错信息 | 可能原因 | 排查方向 |
|---|---|---|
Cannot resolve symbol | 依赖没下载完成 / IDEA 索引没刷新 | 刷新 Maven 项目,删.lastUpdated文件重新导入 |
Could not find artifact com.xxx:xxx:1.0 | 仓库里没有这个坐标/版本 | 确认坐标是否写错,检查私服或镜像是否包含该构件 |
Unknown lifecycle phase "install" | 命令拼写错误或引号问题 | 检查命令格式,比如mvn clean install不要写成mvn clean-install |
Connection timed out | 仓库访问不了 | 检查网络,确认代理是否影响本地连接 |
The forked VM terminated without properly saying goodbye | 测试进程内存溢出 | 调整 surefire 插件 forkCount 和 argLine 参数 |
Failed to execute goal ... compiler-plugin ... compilation failure | 编译失败 | 打开完整报错输出,定位到具体文件具体行 |
invalid target release: 17 | JDK 版本和编译插件版本不匹配 | 升级 compiler-plugin 到 3.8.0+,或在 pom 里指定 maven.compiler.source/target |
有一个经验性极强的排查技巧:删掉本地仓库里.lastUpdated结尾的文件。这类文件表示之前下载失败,Maven 会因为它们的存在而不再重新下载。执行删除后重新构建:
# Linux / macOS find ~/.m2/repository -name "*.lastUpdated" -exec rm -rf {} \; # Windows(PowerShell) Get-ChildItem -Path "$HOME\.m2\repository" -Recurse -Filter "*.lastUpdated" | Remove-Item然后重新mvn clean install。这个操作能解决很大一部分“依赖明明存在但解析失败”的问题。
6.2 依赖传递与排除依赖:为什么 jar 包里多了一堆不认识的库
Maven 的依赖是有传递性的,比如你引入spring-boot-starter-web,它自己又会引入 Tomcat、Spring MVC、Jackson 等几十个依赖。好处是省事,坏处是版本冲突和冗余依赖难以控制。
看当前项目的完整依赖树:
mvn dependency:tree这个输出非常有用,它能显示每个依赖是怎么被引入的,以及版本是什么。如果你发现某个传递依赖版本不对,可以在 pom 里显式声明这个依赖的版本,Maven 在依赖冲突时会优先选择 pom 中直接声明的版本(“就近优先”原则)。如果想排除某个传递依赖,用<exclusions>:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> </exclusions> </dependency>排除依赖很有用,尤其是在你不想用某个库的默认实现,想替换成自己的实现时。但注意:排除后必须自己补上替代依赖,否则运行时会有ClassNotFoundException。
6.3 多模块项目:一个父 POM 管所有
实际项目里,很少只有一个模块。常见的结构是一个父工程加多个子模块:
my-project/ ├── pom.xml (父POM, packaging = pom) ├── common/ (公共工具模块) ├── service/ (业务服务模块) └── web/ (接口模块)父 POM 关键部分:
<packaging>pom</packaging> <modules> <module>common</module> <module>service</module> <module>web</module> </modules>子模块只需要写自己的artifactId和依赖。如果子模块之间也有依赖关系,比如 web 依赖 service,直接在 web 的 pom 里加上 service 的依赖即可:
<dependency> <groupId>com.example</groupId> <artifactId>service</artifactId> <version>1.0-SNAPSHOT</version> </dependency>这种情况下,开发顺序有讲究。你改了 common 模块,必须先在 common 目录下执行mvn clean install把新版本装进本地仓库,然后再去构建 service 或 web,否则它们会用到旧版本。如果改了代码但构建时老是用旧版本,优先检查你是不是忘了 install 依赖模块。
6.4 依赖版本统一管理:properties 和 dependencyManagement
很多项目里会出现十几个依赖都是version标签里写死 1.0 的情况,升级时逐处改,改到一半忘了改另一个,然后就冲突了。更优雅的做法是这样的:
在父 POM 中定义 properties:
<properties> <spring.version>5.3.20</spring.version> <jackson.version>2.13.4</jackson.version> </properties>子模块引用:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>另外推荐使用dependencyManagement统一控制依赖版本。它和 dependencies 的区别是:dependencyManagement 只在父 POM 中管理版本号,子模块声明依赖时可以省略 version,系统会自动继承。如果子模块想覆盖,自己写上 version 即可。这个模式在多模块项目里是标准做法,强烈建议养成就这样写。
7. 常用命令与打包细节:clean、install、package 的进阶用法
7.1 命令速查表与执行顺序
日常开发中几个高频命令:
| 命令 | 作用 | 实际场景 |
|---|---|---|
mvn clean | 清理 target 目录 | 重新构建前先把旧产物删掉 |
mvn compile | 编译主代码 | 快速检查代码能否编译通过 |
mvn test | 运行单元测试 | 验证测试用例 |
mvn package | 打包成 jar/war | 输出构建产物 |
mvn install | 打包并安装到本地仓库 | 供其他模块或项目依赖 |
mvn deploy | 上传到私服 | 发布版本给团队使用 |
mvn dependency:tree | 查看依赖树 | 排查依赖冲突和传递依赖 |
mvn help:effective-pom | 查看最终生效的 POM | 了解默认配置 |
命令行里mvn clean install是比较常见的组合写法,它先执行 clean 生命周期,再执行 default 生命周期到 install 阶段。注意顺序不是install clean,一定把clean放前面。
7.2 跳过测试的正确姿势,别再乱写了
跳过测试有几种方式,参数不同,效果也不同:
-DskipTests:编译测试类,但不执行测试。适合需要快速验证构建产物的场景。-Dmaven.test.skip=true:不编译也不执行测试类。场景是确定测试代码没必要编译的情况,比如改了主代码但测试代码暂时坏了。
如果项目里集成测试特别耗时,只是临时本地打包你想跑快一点,mvn clean package -DskipTests就够用了。但发布到公司流水线上的话,通常不应该跳测试,避免把坏代码发上去。
有些项目配置了maven-surefire-plugin的skipTests属性,它在 pom 里也会起作用:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>false</skipTests> </configuration> </plugin>优先级是:命令行参数 > pom 配置 > 默认值。如果你的 pom 里写死了 skipTests=true,命令行加-DskipTests=false是覆写不回来的。这是因为 IF 条件的原因,跳过测试的开关比较特殊。遇到这种坑,直接改 pom 或者用-Dmaven.test.skip=false试试。
7.3 打包命名:finalName 和 classifier 的使用
默认打包产物是artifactId-version.jar,比如demo-project-1.0.0.jar。如果想自定义文件名,在 pom 里加:
<build> <finalName>my-demo</finalName> </build>如果同一个项目想生成不同场景的包,用 classifier。比如同时输出jar和jar-with-dependencies(包含所有依赖的可执行 jar),就得借助插件配置。典型用法是配置 maven-assembly-plugin:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <finalName>my-demo</finalName> <appendAssemblyId>false</appendAssemblyId> </configuration> </plugin>appendAssemblyId=false会让生成的 jar 不带-jar-with-dependencies后缀,直接以 finalName 命名。这个设置有时候很实用,特别是别人要求一个固定名字的交付物时。
8. 基于实际场景的演练:从零构建一个小项目
8.1 用命令创建 Maven 项目骨架
用 Maven 命令行创建项目:
mvn archetype:generate -DgroupId=com.example \ -DartifactId=hello-maven \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false执行完成后会生成一个包含pom.xml和App.java的基础 Java 项目的骨架。注意:这个 archetype 生成的版本比较旧,编译时如果你用 JDK 17,可能需要手动在 pom 里升级编译器插件版本,否则会报source/target 1.5 已过时或invalid target release的错误。
推荐一个更现代的创建方式,直接用 Spring Initializr(网页版)生成项目后导入 IDEA,或者用 IDEA 的“New Project”向导选择 Maven 模板。IDEA 2022 之后默认支持 Spring Boot 项目的 Maven 结构初始化,生成的 pom 基本可用,省去手动调整编译参数的步骤。
8.2 添加依赖并构建验证
在生成的pom.xml中添加一个实际依赖,比如commons-lang3:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.14.0</version> </dependency>在App.java中写一段代码:
package com.example; import org.apache.commons.lang3.StringUtils; public class App { public static void main(String[] args) { System.out.println(StringUtils.capitalize("hello maven")); } }然后依次执行:
mvn clean compile mvn exec:java -Dexec.mainClass="com.example.App"如果 exec 插件没有配置,可以用mvn package后通过java -cp target/classes:依赖路径 com.example.App运行。更直接的方式是在 IDEA 中右键运行 App.main 方法,这就不依赖 Maven 插件了。
8.3 将模块发布到本地仓库供其他项目复用
假设hello-maven是一个公共工具模块,另一个web-demo项目要依赖它。在hello-maven目录下执行:
mvn clean install然后到web-demo的 pom 里加依赖:
<dependency> <groupId>com.example</groupId> <artifactId>hello-maven</artifactId> <version>1.0-SNAPSHOT</version> </dependency>install和package的区别就在这里:package只是生成 jar 到 target,本地仓库里还是旧版本;install会将新的 jar 安装到本地仓库的坐标目录下。这就是为什么多人协作时必须 commit 后统一执行 install,而不只是 package。我记得有一次项目组里有人只改了代码没 install,另一个人怎么拉代码都还是旧版本,排查到凌晨才发现是这个问题。
9. 常见问题与排查技巧实录
9.1 Maven 官网下载慢、下载包损坏怎么破
官网下载慢有两个思路:一是找国内源下载,二是用命令行下载。推荐一个比较稳的组合:在官网找一个具体版本文件的完整 URL,然后换镜像域名下载。
比如官网地址是:
https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.tar.gz那你可以尝试用阿里云镜像的地址:
https://maven.aliyun.com/repository/central/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.tar.gz下载完成后校验 SHA-256。这个校验比“能解压”更靠谱,解压出来损坏的文件经常导致很奇怪的构建错误。
9.2 IDEA 中 Maven 依赖全部爆红:两大类原因
IDEA 中看着一堆红色的 import,第一反应别急着改 pom。先分两类:
- 从未下载成功:看本地仓库里是否有对应坐标目录,是否只有
.lastUpdated文件。 - 已经下载成功但 IDEA 没识别:刷新 Maven 项目,甚至重启 IDEA。
如果刷新没用,执行mvn clean install -U,-U会强制更新快照版本和远程元数据。这招对 SNAPSHOT 依赖特别有效,因为 Maven 默认每天只检查一次快照更新,-U可以绕过这个缓存策略。
9.3 一个依赖反复下载失败:.lastUpdated 的陷阱
经常有这种情况:某个 jar 第一次下载超时,之后你再怎么刷新,它都提示找不到。原因就是 Maven 生成了.lastUpdated文件作为“上次下载失败”的标记,在同一个时间窗口内(默认一天)它不会重新尝试。
除了删除这些标记文件外,如果项目急用,还可以在仓库管理端重新上传 jar,或者直接把 jar 手动放进本地仓库的对应目录,并在旁边建一个_remote.repositories文件。文件内容格式如下:
# NOTE: This is a Maven Resolver internal implementation file # It is not intended to be edited hello-maven-1.0-SNAPSHOT.jar>central= hello-maven-1.0-SNAPSHOT.pom>central=路径和文件名按照实际 jar 情况填写。这招适用于“我有 jar 包,但仓库服务器暂时连不上”的应急情况。
9.4 多个 Maven 版本共存:不同项目用不同版本怎么办
有的公司老项目锁定 Maven 3.6.x,新项目用 3.9.x。如果只配置一个 MAVEN_HOME 全局变量,切项目时总得改来改去。
更实用的方案是用 SDKMAN(Linux/macOS)或者直接用 IDEA/VSCode 的项目级 Maven 配置。IDEA 里每个项目可以单独指定 Maven home path,不受全局影响。命令行用的就是全局变量。开发中一般不怎么在命令行频繁切版本,所以直接在 IDE 里配置项目级 Maven 最省心。
另外 Windows 上如果装了不止一个 Maven,检查一下 Path 里有没有多个指向 Maven bin 的条目——有时候 IDEA 会自己配一个 Maven 路径,覆盖了系统变量。这种情况不是 Bug,是优先级问题,习惯就好。
9.5 配置了私服但依赖还是走中央仓库
这种情况很常见,原因是你的<mirror>配置和<repository>配置没配合好。Maven 解析仓库的优先级有几个阶段:
- 先去本地仓库找,没有就向远程仓库请求。
- 远程仓库的查找顺序受到 mirror 影响。如果 mirror 的 mirrorOf 配置为
*,所有远程仓库请求都会走这个镜像。
所以当你配了一个mirrorOf=*指向阿里云时,私服相当于被“架空”了。要么改 mirrorOf 为external:*(表示除本地文件之外的仓库都走这个镜像,但本地仓库除外),要么精确指定你想让镜子代理的仓库 id。
external:*这种写法有个细节:Maven 会认为“外部”仓库包含所有非 localhost 和非 file:// 的仓库。如果你私服是内网 IP,它也会被代理到阿里云,导致内网构件找不到。想兼顾,还是得用多镜像并精确控制 mirrorOf 更靠谱。
10. 最后分享一点实战体验
写到这里,该说的都说了。我在项目里折腾 Maven 的时间不算短,遇到的最大问题永远是“凭直觉改配置”,而不是“按原理排查”。Maven 的设计思路其实不复杂:给每个构件一个坐标,按坐标去仓库找,找到就下载,找不到就报错。你的 pom 和 settings 就是对这个过程的“路径规划”。
如果你刚开始学,我的建议很直接:不要复制粘贴别人的完整配置就完事。先自己创建项目,用默认配置跑通一次,再逐步加上镜像、私服、多模块、统一版本管理。每次修改只改一个变量,跑一次构建,确认效果,再改下一个。这个过程看起来慢,但会让你真正理解每个配置的意义,以后再遇到问题就知道去哪看、怎么排查。
还有一个小习惯值得坚持:养成看完整日志而不是只看报错摘要的习惯。Maven 报错信息里,真正的原因往往在日志中段或末尾,那些[ERROR]行下面跟着的“Caused by”,才是问题的源头。比如依赖冲突时,它会明确告诉你是哪个 jar、哪个版本、被哪条依赖链引入,顺着排查基本十分钟内解决。
Maven 这个工具本身并不难,难的是“当你以为懂了之后遇到的那些奇怪问题”。但只要掌握了仓库机制、生命周期、配置优先级这几个核心,大概率能应对日常开发中绝大多数场景了。