简介:Apache Maven 3.6.2 版本的完整压缩包,面向需要搭建 Java 项目构建环境的开发者与运维人员,用于解决依赖管理、标准化构建流程以及本地 Maven 安装配置等问题,适合从入门到日常开发使用。资源包内共68个文件,以42个 jar 核心库和插件依赖为主,另含启动脚本、配置文件、说明文档及许可证文件,整体约8.77MB,目录涵盖 bin、conf、lib、boot 等模块,兼顾 Windows 与类 Unix 环境的启动需求。目前已有325人学习下载,说明其在 Java 构建工具场景中具有一定参考与实用价值。借助标准 POM 模型,读者可快速完成依赖解析、编译测试、打包部署等任务;conf 目录下的全局配置便于调整本地仓库、远程仓库与代理,插件机制也可按需扩展,整体开箱即用,适合作为 Java 开发环境的常备构建工具。
1. Maven 3.6.2 的 zip 包拿到手,先确认 JDK 与解压路径这两件事
内网构建机重装、CI 镜像要固定构建工具版本的时候,运维发来一个 apache-maven-3.6.2.zip 是常事。这种绿色包看起来最没技术含量:解压、配 PATH、收工。但真到机房里,卡住人的从来不是 Maven 本身,而是 JAVA_HOME 指向了一个 jre 目录、settings.xml 改在了错误的位置、私服地址被一条mirrorOf *全劫持了这三类小事故。这个版本之所以还有人在用,多半是两个原因:老项目的插件版本太旧,在新 JDK 上跑不动;或者内网私服只开了 http,新版构建工具的默认策略直接把这类仓库拦掉。适合谁?需要在同一台机器上并存多个构建工具版本、又要求结果可复现的运维和开发;以及第一次从零搭 Java 构建环境、想把每个参数搞明白的新手。下面按实际装机的顺序走一遍,从解压到第一个 BUILD SUCCESS,再到依赖冲突怎么定位,坑都标出来。
2. 解压到 PATH 生效:apache-maven-3.6.2 目录拆解与三个环境变量
2.1 解压后先看目录,别急着配 PATH
压缩包解开自带一层apache-maven-3.6.2/,落地的绝对路径就是它,不要再手工套一层maven/,否则后面所有配置里的 MAVEN_HOME 都会多一级,mvn -v打印出来的 Maven home 也别扭,排查时容易看错。
# Linux / macOS:解到 /opt 下,注意 -d 后面带斜杠 unzip apache-maven-3.6.2.zip -d /opt/ # 确认顶层目录名(有的包名和目录名不完全一致) ls -l /opt/ | grep -i maven ls /opt/apache-maven-3.6.2-d决定落地目录,/opt只是习惯位置,装到~/opt或D:\dev都行;唯一要避开的是带空格和中文的路径,Windows 上C:\Program Files\...这类目录还会牵扯到写权限。目录结构不长,但每一层都有明确职责,先认一遍再动手:
| 路径 | 内容 | 要不要动 |
|---|---|---|
bin/ | mvn、mvn.cmd、m2.conf启动脚本 | 不动,只把 bin 加进 PATH |
boot/ | plexus-classworlds-*.jar,负责拉起整个类加载器 | 绝对不动 |
conf/ | settings.xml、toolchains.xml、logging/ | 做全局配置时才动 |
lib/ | maven-core、maven-model、resolver等几十个 jar | 不动,出问题整包换 |
boot/和lib/是黑匣子,手工替换里面的 jar 属于血泪经验里最没必要的一种折腾——构建行为开始玄学的时候,正确做法是整包换一个干净版本,而不是逐个 jar 换着试。conf/settings.xml是全局配置模板,很多人直接改它,这是后面第 5 章要展开的一个坑。
2.2 JAVA_HOME 与 PATH 的先后关系
Maven 自己不带 JDK。bin/mvn这个 shell 脚本启动时第一件事就是找 java:优先读JAVA_HOME,读不到才退到 PATH 里的java。所以顺序必须先确认 JAVA_HOME 指向一个真实存在的 JDK 根目录,再把 Maven 的 bin 塞进 PATH。反过来做,很可能出现「PATH 里的 java 是 11,JAVA_HOME 指的是 8」这种两个版本打架的局面,编译行为和mvn -v输出对不上,排查能耗掉半天。
# 写到 ~/.bashrc 或 /etc/profile.d/maven.sh 里,source 生效 export JAVA_HOME=/usr/lib/jvm/java-8-openjdk export MAVEN_HOME=/opt/apache-maven-3.6.2 export PATH="$MAVEN_HOME/bin:$PATH" # 先单独验证 JDK,再去验证 mvn "$JAVA_HOME/bin/java" -versionMAVEN_HOME严格来说不是必须的:3.6.2 的启动脚本会根据自己的脚本位置反推安装目录,所以只加 PATH 也能跑。写进去主要是给 IDE、CI 脚本和老插件读的,别信「必须先配 M2_HOME」的老教程,那个变量早就不作为主路径用了。Windows 下同理,用图形界面的「系统属性 - 环境变量」改:
:: 只设这两个,PATH 里再手工追加 %MAVEN_HOME%\bin setx JAVA_HOME "D:\dev\jdk8" setx MAVEN_HOME "D:\dev\apache-maven-3.6.2"注意不要用setx PATH "%PATH%;..."拼接。这个命令会把展开后的整条 PATH 写回注册表,超过 1024 字符会被截断,症状是某次重启后系统里一堆命令突然找不到,属于典型翻车现场。PATH 一定走图形界面编辑。
JDK 版本上,3.6.2 跑 JDK 8 或 11 最稳;JDK 7 是它的下限,能启动但没什么实际意义。JDK 17 及以上能起来,但不少老版本插件在反射限制下会报错,这时候要么换插件版本,要么换构建工具版本,别在这个组合上耗。
2.3 把 mvn -v 的六行输出逐行读完
装完第一件事不是建项目,是读输出。mvn -v打印的每一行都对应一个后面可能出问题的点:
mvn -vApache Maven 3.6.2 (40f52333136460af0dc0d7232c0dc0bcf0d79e0a) Maven home: /opt/apache-maven-3.6.2 Java version: 1.8.0_292, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-8-openjdk/jre Default locale: zh_CN, platform encoding: UTF-8 OS name: "linux", version: "4.19.0", arch: "amd64", family: "unix"第一行是版本号和构建标识,CI 里做版本校验就靠它。第二行Maven home必须是解压出来的那个目录,如果它指向了另一个路径,说明 PATH 里有更早的同名命令,which -a mvn能查出来。第三、四行最关键:Java version是你以为的版本,Java home才是实际使用的路径。上面这个例子Java home落在jre子目录里,编译普通项目没问题,但一旦项目用到javadoc、tools.jar相关的插件,就可能报找不到类——正确做法是让 JAVA_HOME 指向 JDK 根目录,而不是它下面的 jre。
第五行platform encoding决定源码读取的默认编码,zh_CN配GBK的组合在老服务器上很常见,是中文注释编译报错的直接来源。第六行在跨平台脚本里用来分支判断,比如 Windows 上要改用mvn.cmd。看到The JAVA_HOME environment variable is not defined correctly这条错误,含义只有一个:JAVA_HOME 要么空,要么指向了不存在的路径,别去翻 Maven 的其他配置。
3. settings.xml 改四处:本地仓库、mirrorOf、profile 与私服口令
3.1 localRepository:把仓库从系统盘挪走
默认仓库在${user.home}/.m2/repository,一个中型多模块项目装完依赖轻松 1~3 GB,放在系统盘迟早出事。正确的位置是用户级配置${user.home}/.m2/settings.xml,没有就新建;不要改安装目录下的conf/settings.xml。
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"> <!-- 仓库落到数据盘,Windows 下用正斜杠或双反斜杠 --> <localRepository>D:/m2/repository</localRepository> </settings>路径分隔符用/,Windows 同样认,写\反而要转义。这里配完之后,mvn -v不会体现,得用mvn help:evaluate -Dexpression=settings.localRepository -q -DforceStdout验证实际取值——这条命令在排查「到底读的哪份配置」时非常好用,后面还会用到。
同一个用户下想跑两套互不干扰的仓库,不用改配置,命令行临时指一个就行:mvn -Dmaven.repo.local=/data/m2-test clean package。做版本升级验证、或者排查「是不是缓存脏了」的时候,这一招比删仓库快得多。
3.2 mirror 与 mirrorOf:拦哪些、放过哪些
镜像的作用是把对某个仓库的请求转发到另一个地址。写错mirrorOf是最高频的配置事故,因为它不报配置错误,只在拉依赖时表现为 404 或者诡异的超时。
<mirrors> <mirror> <!-- id 随便起,但不要和任何 server/repository 的 id 重名 --> <id>internal-public</id> <mirrorOf>central</mirrorOf> <url>https://mirror.example.com/repository/maven-public/</url> </mirror> </mirrors>地址换成你自己环境里的私服或代理地址,其他字段照抄。mirrorOf的取值规则必须记牢,它是排障时第一个要看的东西:
| 写法 | 含义 | 典型场景 |
|---|---|---|
central | 只拦中央仓库 | 只需要加速公共依赖 |
* | 拦所有仓库 | 全内网、只允许一个出口时用 |
external:* | 拦所有非本机、非 file 协议的仓库 | 兼顾本机测试仓库 |
*,!internal-snapshots | 全拦但排除指定 id | 私服里还有第二套快照源 |
repo-a,repo-b | 只拦列出的 id | 精细控制 |
*最省事也最危险:项目 pom 里如果声明了另一个私服仓库(比如存放内部快照的那个),它的请求会被一起劫持到这里的 url 上,症状就是「明明在 pom 里配了私服,还是 404」。遇到这种情况先mvn help:effective-settings看合并后的镜像规则,再决定是改成external:*还是加排除项。
3.3 profile:把编译级别和编码钉死
settings.xml里的 profile 是对所有项目生效的兜底配置,最值得放进去的就是编译级别和编码这三四个属性:
<profiles> <profile> <id>jdk8-default</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> </profile> </profiles>必须清楚它的边界:这些是属性,pom 里同名属性一旦写了就会覆盖它,所以别指望在 settings 里「强推」编译版本,它只是没写时的默认值。真正要强制,得靠后面第 6 章的 enforcer 规则。另外activeByDefault的 profile 在命令行显式激活任何其他 profile 时会失效,这个行为和直觉相反,容易误判。
profile 里还可以放repositories和pluginRepositories,适合把内网私服地址统一收在一处,避免每个 pom 抄一遍。但注意插件仓库和依赖仓库是两套,只配了前者会出现「依赖能下、插件下不来」的半瘫状态。
3.4 servers 与口令加密:别把明文密码提交进仓库
要往私服 deploy,就得配认证。明文写在settings.xml里,一旦这份文件被同步到 dotfiles 仓库或者共享盘,等于把发布权限公开了。
<servers> <server> <!-- 这个 id 必须和 pom 里 distributionManagement 的 repository id 完全一致 --> <id>internal-release</id> <username>deployer</username> <password>{Z3ByZW...省略...}</password> </server> </servers>加密口令用自带命令生成,先生成主密码文件,再加密具体口令:
# 1) 生成主口令,输出一串密文 mvn --encrypt-master-password # 2) 把上一步的密文写进 ~/.m2/settings-security.xml # <settingsSecurity><master>{上一步密文}</master></settingsSecurity> # 3) 加密私服口令,把结果填到 server/password,注意保留外层花括号 mvn --encrypt-passwordsettings-security.xml放在~/.m2/下,权限设成 600,不要和settings.xml一起进版本库。id 对不上是 401 的头号原因——settings.xml里写internal-release、pom 里写release,认证信息就完全不会被应用,报错还只说 401,不看文档根本联想不到。
4. 从空目录到 BUILD SUCCESS:最小 pom 与六条常用命令
4.1 标准目录约定与最小可用 pom
Maven 靠约定找文件,不按约定放就会编译不到源码。标准布局只有四个位置值得记:src/main/java放源码、src/main/resources放要打进产物的配置、src/test/java放测试、pom.xml在项目根。
mkdir -p demo-service/src/main/java/com/example/demo mkdir -p demo-service/src/main/resources cd demo-service最小 pom 只要四样东西:坐标三元组加打包方式。其余全部靠继承的超级 pom 兜底:
<?xml version="1.0" encoding="UTF-8"?> <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.demo</groupId> <artifactId>demo-service</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- scope 不写默认 compile;测试用的必须写 test,否则会污染运行期依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> </project>modelVersion必须是4.0.0,写错直接解析失败。packaging不写默认就是 jar,写pom表示这是个聚合或父工程、本身不出产物。scope是最容易写漏的字段:test只参与测试编译和运行,provided编译期可见但不会打进包(容器提供的依赖用它),runtime编译不可见但跑起来需要。漏写等于默认compile,会一路传递到下游项目,属于典型的隐形炸弹。
4.2 生命周期、阶段与命令行参数
Maven 的构建是阶段化的:clean一套、default一套(validate → compile → test → package → verify → install → deploy)、site一套。写mvn install会把 install 之前的所有阶段都跑一遍,所以「只想打包却触发了一堆检查」不是 bug,是阶段顺序。
# 最常用的一条:清干净 + 跑测试 + 打包 + 装进本地仓库 mvn clean install # 跳过测试但要编译测试代码(能发现测试类编译错误) mvn clean package -DskipTests # 连测试代码都不编译,最快,但会漏掉测试类的编译问题 mvn clean package -Dmaven.test.skip=true-DskipTests和-Dmaven.test.skip=true的区别值得单独记:前者编译测试代码只是不执行,后者彻底跳过编译。赶时间用后者,但提交前至少跑一次完整install,否则测试代码里的编译错误会一路漂到主干上。
其余按场景挑:-T 1C开了并行构建,一个 CPU 核一个线程,多模块项目提速明显,但老版本插件不一定线程安全,出现诡异失败先去掉它;-o离线模式,只用本地仓库,适合内网断网演示;-U强制刷新快照和失败的下载记录;-X打调试日志,-e只打异常堆栈,排障时常常两个一起用;-pl demo-service -am只构建指定模块并一起构建它依赖的模块,多模块工程日常必备。
4.3 离线构建与缓存状态的观察方法
真正把构建结果变得可复现的,是本地仓库的状态。断网环境里先跑一次完整的依赖预拉取,再切离线:
# 在线把依赖、插件全部拉到本地 mvn dependency:go-offline # 之后断网构建 mvn -o clean packagego-offline不是万能的,它按 pom 里声明的插件配置来拉,动态解析出来的插件(比如某些通过扩展机制加载的)可能漏掉,所以预拉取之后一定要真的断网跑一遍验证,别只看命令成功。
缓存出问题时,第一件事是看仓库里有没有「失败标记文件」:
# 找出所有下载失败留下的时间戳文件 find ~/.m2/repository -name "*.lastUpdated" | head -20 # 确认没问题后批量清掉,再重新构建 find ~/.m2/repository -name "*.lastUpdated" -delete*.lastUpdated是下载失败时留下的时间戳,在更新策略周期内 Maven 不会重试,表现就是「网络早就通了,还是一直报同一个错」。除了删文件,也可以-U让它强制重试,但-U只影响快照和失败记录,对已经存在的_remote.repositories记录不起作用——真要彻底重来,删掉对应groupId目录比删整个仓库更精准。
5. 避坑:五条把 mvn 卡在第一步的现场记录
5.1 mvn 报 JAVA_HOME 未正确定义,但 java -version 明明正常
现象:命令行敲java -version一切正常,敲mvn -v却报The JAVA_HOME environment variable is not defined correctly。
原因:java来自 PATH,而启动脚本优先读JAVA_HOME,两者根本不是一个东西。JAVA_HOME 要么为空,要么指向了 jre 子目录,要么路径尾部多了一个斜杠或反斜杠。
解决:先echo $JAVA_HOME,再"$JAVA_HOME/bin/java" -version。第二条失败就说明路径本身不对。指向 JDK 根目录、不带结尾分隔符,重新source后验证。Windows 下还要注意变量值不能带引号,带引号时脚本会当成路径的一部分。
5.2 改了 settings.xml 却完全不生效
现象:镜像、本地仓库都改了,mvn的行为一点变化没有。
原因:改的是安装目录下的conf/settings.xml,而项目实际用的是用户级配置;或者改完没重启终端,环境变量还是旧的;又或者是 IDE 内置的构建工具在用另一份配置。
解决:先确认实际生效的路径,别靠猜。
# 打印合并后的最终配置,直接看 localRepository 和 mirrors mvn help:effective-settings -Doutput=/tmp/eff-settings.xml grep -n "localRepository\|mirrorOf" /tmp/eff-settings.xml用户级配置优先于全局配置,两个文件会被合并,同 id 的条目以用户级为准。IDE 里还要单独确认它的构建工具设置是否指向了外部的这份安装。
5.3 依赖一直下不下来,仓库里一堆 .lastUpdated
现象:构建报某个依赖找不到,重试多次依旧,删了本地目录重来也一样。
原因:失败记录被缓存了。也可能镜像范围写错,请求被转发到了一个不含该构件的地址,返回 404 后照样留下失败标记。
解决:先看是 404 还是超时。404 多数是镜像或仓库 id 的问题,用 3.2 节的effective-settings对一遍;超时才是网络问题。然后清掉标记文件重试,必要时加-U。还不行就mvn -X看它到底请求了哪个 url。
mvn -X dependency:resolve 2>&1 | grep -i "downloading\|downloaded\|could not"5.4 中文注释编译报 unmappable character
现象:源码里有中文注释,mvn compile报unmappable character for encoding GBK或者编译出来的日志一团乱码。
原因:源码是 UTF-8,但构建工具用了平台默认编码(mvn -v第五行显示的那个)。Windows 上平台默认常常不是 UTF-8。
解决:project.build.sourceEncoding一定写进 pom,不要只依赖 settings 里的兜底。极端情况下还可以在命令行强制-Dfile.encoding=UTF-8,但这只影响运行 JVM 的默认编码,不能替代 pom 里的属性。同时把 IDE 的文件编码也统一到 UTF-8,否则只是把问题从构建挪到了编辑器。
5.5 老私服用 http,新版工具直接拦掉
现象:同一份 settings.xml,在老版本上能正常拉依赖,换成新版本构建工具后报仓库被阻断。
原因:较新的版本默认引入了一条拦截 http 仓库的镜像规则,plain http 地址会被直接挡掉。这正是很多环境还停在 3.6.2 的真实原因,不是懒得升级。
解决:能改私服就改成 https,这是唯一干净的方案。改不了的情况下,用 3.6.2 这类没有默认拦截的版本,并把这个选择写进构建脚本的注释里,别让后来人以为升级是随手的事。同时把私服地址收敛到settings.xml一处,将来换协议只改一个地方。
6. 依赖树剪枝与 enforcer 锁定:把冲突挡在提交之前
依赖冲突的典型表现是NoSuchMethodError或ClassNotFoundException,而报错位置通常在离冲突很远的地方。定位靠依赖树,-Dincludes是关键参数,它支持groupId:artifactId的通配写法,能把一棵几百行的树砍到只剩目标:
# 只看某个构件是被谁引进来、最终选中了哪个版本 mvn dependency:tree -Dincludes=com.example:* -Dverbose # 排查完把整棵树存下来,方便 diff 两个版本的差异 mvn dependency:tree -DoutputFile=/tmp/tree.txt -DappendOutput=false-Dverbose会显示被省略的冲突节点和它被裁剪的原因,是看懂「最短路径优先、同深度先声明优先」这两条调解规则的最快方式。多模块项目里,我一般会先把父 pom 的树存一份基线,改动依赖后再存一份做 diff,比逐个模块猜要快。
只想看,不想每次都人工比,就把规则写死。enforcer 插件在 validate 阶段执行,规则不满足直接 fail,挡在提交之前:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>3.0.0-M3</version> <executions> <execution> <id>enforce-rules</id> <phase>validate</phase> <goals><goal>enforce</goal></goals> <configuration> <rules> <!-- 编译用的 JDK 必须落在指定区间,避免有人用 17 跑出「本地能过」 --> <requireJavaVersion> <version>[1.8,1.9)</version> </requireJavaVersion> </rules> </configuration> </execution> </executions> </plugin>版本号要按实际环境调,区间写宽一点更实用。dependencyConvergence规则更狠,任何版本不一致都报错,直接开在成熟项目上会炸出一屏问题,建议先在单个模块试,把冲突收敛到dependencyManagement里统一声明后再全局开。enforcer 的价值不在「多一道检查」,而在于它把「我记得要统一版本」这种依赖人的习惯,变成了构建流水线上不可绕过的动作。
从那以后我每落地一个构建工具版本,都强制走一遍「mvn -v逐行读完 +effective-settings确认实际配置 + 断网-o跑一次完整构建」这套动作,宁可前期多花十分钟,也不想在发布窗口里对着一个 401 或unmappable character猜。希望帮到你。
本文还有配套的精品资源,点击获取