Maven 这个东西,第一次接触的人多半是在某个教程里看到一句“先装个 Maven”,然后照着敲了几行命令,发现命令行里能跑出mvn -v的版本号,就以为搞定了。真正开始拉依赖、跑构建的时候,才发现本地仓库路径不对、镜像源慢得让人想砸键盘、IDEA 里配的 JDK 和命令行里的不是同一个、settings.xml改了没生效……这些问题几乎每个刚上手的人都会撞上一遍。我见过太多人卡在“装完了但用不了”这个状态里,所以这篇就把 Maven 从下载到跑通第一个构建的完整链路拆开讲,重点放在那些教程里通常一笔带过、但实际会绊住你的地方。
这篇内容适合两类人:一类是刚接触 Java 构建工具、需要把环境搭起来的新手;另一类是装过但总觉得配置哪里不对劲、想系统梳理一遍的老手。我会把每一步“为什么这么做”讲清楚,而不是只给命令。装 Maven 本身十分钟的事,真正花时间的是理解它的目录约定、仓库机制和配置优先级,这些搞明白了,后面遇到问题你自己就能定位。
1. 装之前先想清楚:Maven 到底依赖什么
很多人下载完压缩包解压就急着配环境变量,结果mvn -v报一堆错,回头才发现 JDK 根本没装或者版本对不上。Maven 本身是个 Java 程序,它的运行完全依赖一个可用的 JDK,所以安装顺序必须是先 JDK 后 Maven,这个顺序不能反。
1.1 JDK 版本与 Maven 版本的匹配关系
Maven 对 JDK 版本是有要求的,不是随便哪个 JDK 都能带得动。较新的 Maven 3.9.x 系列要求 JDK 8 及以上,如果你用的是 Maven 4 的预览版本,那门槛会更高。反过来,如果你项目里用的是 JDK 17 或 JDK 21,Maven 3.8 以上基本都能正常工作。这里有个容易被忽略的点:Maven 运行用的 JDK 和项目编译用的 JDK 可以是两个不同的东西,前者由JAVA_HOME决定,后者由pom.xml里的maven-compiler-plugin配置或者JAVA_HOME共同影响。新手经常把这两者混为一谈,导致“命令行能编译但 IDEA 报错”这种割裂现象。
我的建议是:让JAVA_HOME指向你日常开发用的那个 JDK,并且保证命令行和 IDE 里用的是同一个。这样能省掉大量“为什么两边行为不一致”的排查时间。验证方式很简单,命令行执行echo %JAVA_HOME%(Windows)或echo $JAVA_HOME(macOS/Linux),再执行java -version,确认输出一致。
1.2 环境变量里那几个关键项
Maven 需要两个环境变量配合:JAVA_HOME和MAVEN_HOME(或者直接用M2_HOME,两者含义接近,社区里更常用MAVEN_HOME)。JAVA_HOME指向 JDK 根目录,注意是 JDK 不是 JRE,因为 Maven 编译时需要javac。MAVEN_HOME指向 Maven 解压后的根目录,然后把%MAVEN_HOME%\bin追加到PATH里。
这里有个实操细节值得说:Windows 下配PATH时,很多人直接写死绝对路径,比如C:\dev\apache-maven-3.9.6\bin。这样能用,但以后升级 Maven 版本就得改PATH。更稳妥的做法是先建MAVEN_HOME变量,PATH里引用%MAVEN_HOME%\bin,升级时只改变量值就行。macOS/Linux 下同理,在~/.zshrc或~/.bash_profile里写export MAVEN_HOME=/opt/apache-maven-3.9.6和export PATH=$MAVEN_HOME/bin:$PATH,改完记得source一下让配置生效。
提示:配完环境变量后一定要新开一个终端窗口再验证,老窗口里的环境变量是旧的,改了也不会刷新,这是最常见的“配了没生效”原因之一。
1.3 验证安装是否真的成功
mvn -v是最直接的验证命令,正常输出会包含 Maven 版本、Java 版本、操作系统信息和 Maven 主目录。如果这里报'mvn' 不是内部或外部命令,基本就是PATH没配对;如果报的是 Java 相关的错误,那就是JAVA_HOME的问题。还有一种情况是输出了版本号但 Java 版本显示的是你意料之外的那个,说明系统里有多个 JDK,JAVA_HOME指向的不是你以为的那个,这时候得去检查环境变量的实际值,而不是凭记忆判断。
2. settings.xml:Maven 真正的大脑
装完能跑mvn -v只是第一步,真正决定 Maven 好不好用的是settings.xml。这个文件控制着本地仓库位置、远程仓库镜像、认证信息、代理设置等等。很多人装完 Maven 直接开始建项目,结果依赖下载慢得像蜗牛,或者本地仓库默认堆在用户目录下把 C 盘塞满,都是因为没动过这个文件。
2.1 全局配置与用户配置的优先级
settings.xml有两个位置:一个是 Maven 安装目录下的conf/settings.xml,叫全局配置;另一个是用户目录下的.m2/settings.xml,叫用户配置。两者同时存在时,用户配置会覆盖全局配置里的同名项,但不是简单替换整个文件,而是按元素合并。这个机制意味着你可以把团队通用的镜像配置放在全局,个人特有的仓库认证放在用户配置里,互不干扰。
实际操作中我倾向于只改用户配置,全局配置保持原样。原因很实际:全局配置在升级 Maven 时可能被覆盖,而且多人共用一台机器时改全局会影响别人。用户配置跟着账号走,升级、迁移都不受影响。如果.m2目录下没有settings.xml,直接从conf/settings.xml复制一份过去改就行,里面注释很全,照着改不容易出错。
2.2 本地仓库路径该放哪
Maven 默认把本地仓库放在~/.m2/repository,也就是用户目录下。这个默认值在 Linux/macOS 上问题不大,但在 Windows 上经常把系统盘撑爆,尤其是依赖多的项目,仓库轻松几十个 G。所以第一件该改的事就是把localRepository指到一个空间充足的盘。
<localRepository>D:/maven-repo</localRepository>路径用正斜杠或者双反斜杠都行,单反斜杠在 XML 里会被当转义符,容易出问题。改完之后已有的依赖不会自动搬过去,需要手动把旧仓库目录内容拷到新位置,或者干脆重新拉一遍。这里提醒一句:本地仓库不要放在网络盘或者同步盘里,Maven 对文件锁和并发写入比较敏感,放在这类盘上偶尔会出现依赖文件损坏、下载中断的问题,排查起来非常费劲。
2.3 镜像配置:解决下载慢的核心手段
依赖下载慢是新手最常抱怨的问题,根源是默认的中央仓库在境外,网络往返延迟高。解决办法是配镜像,把请求转发到国内的镜像站点。配置写在<mirrors>节点里,一个典型的镜像配置长这样:
<mirror> <id>aliyun-mirror</id> <mirrorOf>central</mirrorOf> <name>Aliyun Maven Mirror</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>mirrorOf的值决定这个镜像代理哪些仓库,写central表示只代理中央仓库,写*表示代理所有仓库。这里有个坑:如果写*,那些你自己在pom.xml里额外声明的私有仓库也会被这个镜像接管,导致私有依赖拉不到。所以除非你确定所有依赖都能从镜像站拿到,否则建议精确指定central,或者用*,!私有仓库id这种排除语法。
镜像不是配得越多越好,多个镜像同时匹配同一个仓库时,Maven 只会用第一个匹配的,后面的被忽略。所以配一个稳定可靠的镜像就够了,配一堆反而容易搞混哪个在生效。
3. 第一个项目跑通之前,先理解目录约定
Maven 最核心的设计之一就是“约定优于配置”,它规定了一套标准的项目目录结构,你按这个结构放文件,就不用写一堆配置告诉它源码在哪、资源在哪、测试在哪。新手如果不懂这套约定,把 Java 文件随便放,就会出现“明明代码没问题但编译找不到类”的情况。
3.1 标准目录结构长什么样
一个标准的 Maven 项目根目录下有这么几个关键位置:
| 目录路径 | 用途 |
|---|---|
src/main/java | 主代码的 Java 源文件 |
src/main/resources | 主代码用到的配置文件、静态资源 |
src/test/java | 测试代码的 Java 源文件 |
src/test/resources | 测试用到的资源文件 |
target | 构建输出目录,编译产物、打包结果都在这 |
pom.xml | 项目对象模型,整个项目的配置中心 |
target目录是自动生成的,不需要手动创建,也不该提交到版本控制里。其他几个src下的目录需要自己建,建好之后把代码按主代码和测试代码分开放,Maven 构建时就会自动去对应位置找。
3.2 pom.xml 里最少要写什么
一个能跑起来的最小pom.xml需要四个要素:modelVersion、groupId、artifactId、version。前三个定位项目身份,最后一个标识版本。除此之外,如果项目要编译 Java 代码,还得配maven-compiler-plugin指定源码和目标 JDK 版本,否则可能用了一个很老的默认版本编译,导致新语法报错。
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.example.demo</groupId> <artifactId>demo-app</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>project.build.sourceEncoding这行经常被忽略,但不设的话在中文环境下编译可能出乱码警告,加上更省心。groupId的命名习惯是反写域名,但这不是强制的,内部项目用简单的包名结构也行,关键是团队内保持一致。
3.3 依赖是怎么被找到的
当你在pom.xml里声明一个依赖,Maven 的查找顺序是:先看本地仓库有没有,有就直接用;没有就去远程仓库(经过镜像转发)下载,下载完存到本地仓库。这个机制意味着同一个依赖第二次用就不用再下载了,所以第一次构建慢是正常的,后面会快很多。
依赖的坐标由groupId、artifactId、version三部分确定,少一个都定位不到。有时候同一个库有多个版本,Maven 会按“最近优先”原则处理传递依赖的版本冲突,也就是离项目根最近的声明生效。这个规则新手容易忽略,导致实际用的版本和预期不一致,排查时可以用mvn dependency:tree把依赖树打出来看,哪个版本被选中一目了然。
4. 那些装完才发现的坑,以及怎么绕过去
环境搭好、项目能跑,不代表后面就顺了。实际使用中还有一批问题是在特定场景下才冒出来的,提前知道能省不少时间。
4.1 IDEA 里的 Maven 和命令行不是一回事
很多人命令行mvn跑得好好的,一到 IDEA 里就各种红。原因是 IDEA 有自己的一套 Maven 配置:它可能用内置的 Maven 而不是你装的那个,可能用不同的settings.xml,可能用不同的 JDK。这三处任何一处不一致,行为就会分叉。
排查顺序建议是:先看 IDEA 设置里 Maven 的“Maven home path”指向哪,确认是不是你装的那个;再看“User settings file”指向的settings.xml路径对不对;最后看“JDK for importer”选的 JDK 版本。这三项对齐了,IDEA 和命令行的行为基本就一致了。改完记得点一下重新导入,让配置生效。
4.2 依赖下载卡住或者报校验失败
有时候依赖下到一半卡住,或者报Could not transfer artifact之类的错误。常见原因有三个:网络抖动导致下载不完整、镜像站临时不可用、本地仓库里残留了损坏的.lastUpdated文件。前两个等一会儿重试通常能好,第三个需要手动清理——去本地仓库对应目录下删掉.lastUpdated结尾的文件,再重新构建。
如果某个依赖反复下载失败,可以先确认这个依赖在镜像站上到底有没有。有些冷门库或者特定版本只在中央仓库有,镜像站同步有延迟,这时候可以临时在pom.xml里给这个依赖单独指定仓库,绕过镜像。不过这种做法只建议临时用,长期还是应该推动镜像同步或者换用可用的版本。
4.3 构建成功但产物不对
mvn package显示 BUILD SUCCESS,但打出来的 jar 跑不起来,或者缺了某些资源文件。这类问题多半出在资源过滤或者打包插件配置上。比如src/main/resources下的文件默认会被打进 jar,但如果文件名或路径触发了资源过滤的规则,内容可能被替换成占位符没解析的样子。检查pom.xml里<resources>的配置,确认过滤规则没有误伤。
还有一种情况是依赖没打进去,运行时报ClassNotFoundException。这通常是因为打包方式没配对,普通 jar 不会把依赖一起打进去,需要配maven-shade-plugin或者maven-assembly-plugin打成可执行 jar。这个属于项目配置层面的事,但新手容易误以为是 Maven 装错了,白折腾环境。
5. 把配置固化成习惯,后面少踩一半坑
环境这东西,配一次能用很久,但配的时候偷的懒,后面都会以排查问题的形式还回来。我自己的习惯是装完 Maven 之后固定做几件事,形成一套可复用的配置基线,换机器或者帮别人搭环境时直接照搬。
5.1 一份可复用的 settings.xml 骨架
把常用的配置项集中写进用户级settings.xml,包括本地仓库路径、镜像、编码相关的 profile。下面这份骨架可以直接拿去改:
<settings> <localRepository>D:/maven-repo</localRepository> <mirrors> <mirror> <id>aliyun-mirror</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/public</url> </mirror> </mirrors> <profiles> <profile> <id>jdk-config</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </profile> </profiles> </settings>把 JDK 版本和编码放进 profile 并设为默认激活,这样即使某个项目的pom.xml没写编译配置,也能用上合理的默认值。这个做法在团队里推广开,能减少很多“为什么你那边能编译我这边不行”的扯皮。
5.2 定期清理本地仓库
本地仓库用久了会积累大量旧版本依赖和下载残留,占空间不说,偶尔还会因为残留文件导致构建异常。定期清理是个好习惯,但不要直接整个删掉,那样下次构建要重新下载所有依赖。更温和的做法是清理.lastUpdated文件和_remote.repositories文件,这两个是下载状态标记,删掉不影响已下载的 jar。
如果确实要腾空间,可以按最后修改时间清理很久没动过的依赖目录,但要注意有些老项目可能还在用这些版本,删之前确认一下当前活跃项目用不到。这个操作没有标准答案,取决于你的仓库使用习惯,我的经验是半年左右清理一次比较合适。
5.3 版本升级时的注意事项
Maven 升级不算频繁,但升的时候有几个点要留意。大版本升级(比如 3.8 到 3.9)通常兼容性没问题,但小版本之间偶尔会有插件行为变化。升级前先备份当前的settings.xml和MAVEN_HOME目录,升级后跑一遍现有项目的构建,确认没问题再清理旧版本。
升级后如果出现插件报错,先看是不是插件版本太老不兼容新 Maven,这种情况在pom.xml里把插件版本显式指定到较新版本通常能解决。另外升级后记得检查mvn -v输出的 Java 版本有没有变化,有时候新版本 Maven 对 JDK 的要求提高了,旧 JDK 带不动,需要同步升级 JDK。
装 Maven 这件事,表面上是下载解压配环境变量,实际上是在搭一套后续所有 Java 项目都依赖的基础设施。环境变量决定它能不能跑,settings.xml决定它跑得快不快、顺不顺,目录约定和pom.xml决定项目能不能正确构建。这三层任何一层没弄对,后面都会以各种奇怪的形式冒出来。我自己的体会是,与其出了问题再回头查,不如装的时候就按上面这些点过一遍,十分钟的细致能省掉后面几个小时的折腾。尤其是settings.xml和 IDEA 配置对齐这两块,是“装完用不了”问题的高发区,值得多花点时间确认。