简介:Apache Maven 3.6.3 是面向 Java 开发者的项目构建与依赖管理工具,适合需要标准化构建流程、统一依赖版本的中初级开发者及团队使用。该版本在依赖解析、冲突处理与内存管理上做了优化,构建速度与稳定性有所提升,并延续了清理、编译、测试、打包、部署等标准生命周期,配合 pom.xml 即可完成依赖引入与插件扩展。资源包共 2000 个文件,以 sha1 校验文件、pom 配置、repositories 仓库描述、jar 依赖包为主,另含少量 license、so、dll、cmd 及 mvn 启动脚本,压缩包约 134.84MB,完整保留了 Maven 运行所需的目录结构与核心组件。已有 194 人学习下载,读者可借此快速搭建本地构建环境,理解中央仓库、远程仓库与本地仓库的协作机制,并借助 Checkstyle、PMD 等插件扩展构建能力,为日常 Java 项目开发与依赖排错提供直接参考。
1. 为什么我劝你先别急着换 Maven 4:3.6.3 在真实项目里的位置
打开任何一个稍具规模的 Java 工程,pom.xml的依赖树都能拉出一屏。这时候如果构建工具本身掉链子,比如依赖下载卡在LastUpdated状态、多模块聚合时mvn install报出莫名其妙的Could not resolve dependencies,你花在排查上的时间往往比写业务代码还多。Maven 3.6.3 就是这样一个被大量存量项目钉在配置里的版本——它既不是最新的 3.9.x,也不是已经进入候选的 4.x,但很多公司的私服配置、CI 流水线镜像、甚至 IDE 内置的捆绑版本,都还停留在它上面。这不是守旧,而是因为 3.6.3 在依赖解析稳定性、插件兼容性和 JDK 8 到 JDK 11 的过渡期表现上,踩过的坑已经被社区填得差不多了。
这份资源围绕 Maven 3.6.3 的完整使用链路展开,从环境变量配置、settings.xml私服与镜像的写法,到多模块工程的pom继承与聚合、依赖作用域与可选依赖的边界,再到构建加速与常见报错排查。它适合两类人:一是接手了老项目、发现本地 Maven 版本和 CI 不一致导致构建结果对不上的后端开发;二是需要给团队统一构建环境、但又不想贸然升级到 3.9.x 的 DevOps 或技术负责人。如果你正在被“本地能跑、流水线挂掉”或者“依赖突然拉不下来”折磨,下面这些内容应该能帮你把黑匣子拆开看。
2. 环境落地:从解压到 settings.xml 的四个关键参数
2.1 安装包解压后的目录结构与 PATH 设置
拿到 Maven 3.6.3 的二进制包之后,第一件事不是急着配环境变量,而是先确认目录里有什么。解压后根目录下通常有bin、boot、conf、lib四个文件夹。bin里放的是mvn和mvn.cmd启动脚本,boot里是 plexus-classworlds 的类加载器 jar,conf里才是全局配置文件settings.xml,lib则是 Maven 自身运行所需的依赖。很多人改配置时直接去用户目录找.m2/settings.xml,结果发现全局配置和用户配置打架,这就是没分清conf/settings.xml和~/.m2/settings.xml的优先级关系。
环境变量需要配两个:MAVEN_HOME指向解压后的根目录,PATH里追加%MAVEN_HOME%\bin(Windows)或$MAVEN_HOME/bin(Linux/macOS)。配完之后在终端执行mvn -v,正常输出会包含 Maven 版本、Java 版本和系统信息。这里有个细节:如果mvn -v显示的 Java 版本和你JAVA_HOME指向的不一致,说明系统里存在多个 JDK,Maven 启动脚本优先读取的是JAVA_HOME,而不是PATH里排在前面的java命令。
# Linux/macOS 下验证 Maven 与 JDK 的绑定关系 echo $JAVA_HOME mvn -v # 输出示例: # Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) # Maven home: /opt/apache-maven-3.6.3 # Java version: 1.8.0_292, vendor: Oracle Corporation # Java home: /usr/lib/jvm/java-8-openjdk-amd64/jre上面这段命令的作用是确认 Maven 实际调用的 JDK 路径。参数说明:JAVA_HOME决定 Maven 运行时使用的 Java 环境,mvn -v输出的Java home必须和JAVA_HOME一致。如果项目需要 JDK 11 编译但 Maven 跑在 JDK 8 上,maven-compiler-plugin的source和target配置就会失效,编译时直接报invalid target release: 11。
2.2 settings.xml 里 mirror 与 repository 的配合逻辑
settings.xml是 Maven 3.6.3 里最容易配错的文件。核心结构是<mirrors>、<servers>、<profiles>三块。<mirrors>定义镜像仓库,<servers>存私服认证信息,<profiles>里的<repositories>和<pluginRepositories>才是真正告诉 Maven 去哪里找包的地方。常见误区是只配了 mirror 没配 repository,结果 mirror 的mirrorOf写成*之后,所有请求都被拦截转发到私服,但私服里没有的包又不会回源到中央仓库,直接报Could not find artifact。
正确的做法是:如果公司有私服,<mirror>的mirrorOf写成central或者*,!内部仓库id,让中央仓库的请求走私服代理,内部仓库的请求直连。如果只是个人开发、想加速中央仓库下载,可以用阿里云的公共镜像,mirrorOf写central即可。下面是一个兼顾私服和公共镜像的配置片段。
<!-- ~/.m2/settings.xml 中的 mirror 与 repository 配置 --> <settings> <mirrors> <mirror> <id>internal-mirror</id> <name>内部私服代理</name> <url>http://私服地址/repository/maven-public/</url> <!-- 只代理 central,不拦截内部仓库 --> <mirrorOf>central</mirrorOf> </mirror> </mirrors> <profiles> <profile> <id>internal-repo</id> <repositories> <repository> <id>internal-release</id> <url>http://私服地址/repository/maven-releases/</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>false</enabled></snapshots> </repository> <repository> <id>internal-snapshot</id> <url>http://私服地址/repository/maven-snapshots/</url> <releases><enabled>false</enabled></releases> <snapshots><enabled>true</enabled></snapshots> </repository> </repositories> </profile> </profiles> <activeProfiles> <activeProfile>internal-repo</activeProfile> </activeProfiles> </settings>这段配置的逻辑说明:<mirrorOf>central</mirrorOf>表示只拦截对中央仓库的请求,转发到内部私服的maven-public分组。maven-public通常是 Nexus 或 Artifactory 里的仓库组,里面聚合了 releases、snapshots 和 central 代理。<profiles>里单独声明internal-release和internal-snapshot,是为了让 Maven 知道内部仓库的地址,避免所有请求都挤在 mirror 上。<activeProfiles>激活这个 profile,否则 repositories 不生效。参数上要注意:<releases>和<snapshots>的enabled必须成对出现,只写一个另一个默认是 false,会导致快照包拉不下来。
2.3 本地仓库路径与离线构建的取舍
Maven 3.6.3 默认把本地仓库放在~/.m2/repository。如果 C 盘空间紧张或者想让多个 Maven 版本共用一份本地仓库,可以在settings.xml里用<localRepository>改路径。改完之后要把旧仓库的包拷过去,否则第一次构建会重新下载所有依赖。离线构建用mvn -o参数,它强制 Maven 只从本地仓库找包,不去远程请求。这个参数在 CI 环境里很有用,可以避免网络抖动导致的构建失败,但前提是本地仓库已经预热完整。
# 指定本地仓库路径并执行离线编译 mvn -o -Dmaven.repo.local=/data/maven-repo clean compile参数说明:-o开启离线模式,-Dmaven.repo.local临时覆盖本地仓库路径,优先级高于settings.xml里的<localRepository>。这个组合适合在流水线里做“依赖已缓存”的快速构建。如果离线模式下报The repository system is offline but the artifact ... is not available in the local repository,说明本地仓库缺包,需要先联网执行一次mvn dependency:go-offline把依赖拉全。
3. 多模块工程:pom 继承、聚合与依赖作用域的边界
3.1 parent 与 modules 的分工
多模块工程里,父pom的<packaging>必须是pom,它承担两个职责:一是通过<modules>聚合子模块,让一条mvn install能按顺序构建所有模块;二是通过<dependencyManagement>和<pluginManagement>统一管理版本,子模块引用时不需要写<version>。很多人把聚合和继承混为一谈,结果在父pom里既写<modules>又写<parent>,造成循环引用。正确的结构是:父pom只做聚合和版本管理,子模块用<parent>指向父pom的坐标,父pom不指向任何子模块作为 parent。
<!-- 父 pom 的聚合配置 --> <project> <groupId>com.example</groupId> <artifactId>demo-parent</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <modules> <module>demo-common</module> <module>demo-service</module> <module>demo-web</module> </modules> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.12.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>逻辑说明:<modules>里的顺序决定构建顺序,Maven 会自动分析模块间的依赖关系并调整,但显式按依赖顺序排列更稳妥。<dependencyManagement>里用<scope>import</scope>导入 Spring Boot 的 BOM,这样所有子模块引用 Spring Boot 相关依赖时都不需要写版本号。参数上注意:<type>pom</type>和<scope>import</scope>必须同时出现,否则 import 不生效,子模块会报dependencies.dependency.version is missing。
3.2 依赖作用域在编译、测试、打包时的实际表现
Maven 的scope决定了依赖在哪些阶段可见。compile是默认值,编译、测试、打包、运行都在;provided在编译和测试时可见,打包时不进WEB-INF/lib,典型场景是 Servlet API;runtime编译时不可见,测试和运行时可见,比如 JDBC 驱动;test只在测试编译和测试运行时可见。多模块工程里最容易翻车的是provided和optional的混用。比如demo-service依赖了demo-common,而demo-common里有个provided的依赖,那么demo-service在编译时是看不到这个依赖的,因为provided不会传递。
<!-- 子模块中声明依赖作用域 --> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>demo-common</artifactId> <version>1.0.0</version> <optional>true</optional> </dependency> </dependencies>参数说明:provided的 Servlet API 在打 war 包时不会进入lib目录,因为 Tomcat 容器自带。runtime的 MySQL 驱动在编译时不需要,但运行时必须存在。<optional>true</optional>表示这个依赖不会传递给依赖当前模块的其他模块,适合那些“只有本模块用、不希望污染上游”的场景。如果上游模块确实需要demo-common,必须在自己的pom里重新声明。
3.3 用 dependency:tree 定位版本冲突
依赖冲突是 Maven 3.6.3 里最耗时的排查项。两个不同模块引入了同一个库的不同版本,Maven 按“最短路径优先”和“最先声明优先”的规则选一个,选中的版本可能缺少另一个版本才有的类,运行时直接NoSuchMethodError。mvn dependency:tree能打印完整的依赖树,加上-Dverbose会显示被忽略的冲突版本。
# 查看依赖树并过滤特定 groupId mvn dependency:tree -Dverbose -Dincludes=org.slf4j # 输出中会标注 omitted for duplicate 或 omitted for conflict逻辑说明:-Dincludes参数按groupId:artifactId格式过滤,支持通配符。输出里带(version managed from ...)的行表示版本被dependencyManagement覆盖了,带omitted for conflict with ...的行表示这个版本被丢弃了。如果确认某个版本必须使用,可以在dependencyManagement里显式锁定,或者在引入依赖时用<exclusions>排除掉传递进来的旧版本。
4. 构建加速与插件配置:别让 clean install 跑成一杯咖啡的时间
4.1 并行构建与跳过测试的适用场景
Maven 3.6.3 支持-T参数做并行构建,-T 1C表示每个 CPU 核心一个线程。多模块工程在模块间没有强依赖时,并行构建能明显缩短时间。但并行构建对插件的线程安全性有要求,某些老版本的插件在并行模式下会写坏target目录,导致产物缺失。稳妥的做法是先在本地用-T 2试跑,确认没有异常再提高并行度。跳过测试用-DskipTests只跳过测试执行,测试代码仍然编译;-Dmaven.test.skip=true连测试代码都不编译,适合确定不需要测试产物的场景。
# 并行构建并跳过测试执行 mvn -T 1C clean install -DskipTests # 如果只想重新打包某个模块及其依赖 mvn -pl demo-web -am clean package -DskipTests参数说明:-pl指定要构建的模块,-am表示同时构建该模块依赖的模块。这个组合在只改了一个子模块时特别有用,避免全量构建。注意-pl后面跟的是模块目录名,不是artifactId,写错了会报Could not find the selected project in the reactor。
4.2 maven-compiler-plugin 与 JDK 版本的绑定
Maven 3.6.3 默认的maven-compiler-plugin版本是 3.8.1,它支持release参数,可以替代source和target。release的好处是它会同时检查 API 和字节码版本,避免在 JDK 11 上编译出 JDK 8 的字节码但误用了 JDK 11 才有的 API。配置时在父pom的pluginManagement里统一指定,子模块继承即可。
<build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <release>8</release> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </pluginManagement> </build>逻辑说明:<release>8</release>告诉编译器生成 Java 8 兼容的字节码,并校验使用的 API 不超过 Java 8。<encoding>UTF-8</encoding>防止中文注释在 Windows 默认 GBK 环境下编译乱码。如果项目里用了 JDK 11 的var关键字但release设成 8,编译会直接报错,这比source/target组合更安全。
4.3 私服快照更新策略与 LastUpdated 文件清理
快照依赖默认每天更新一次,由settings.xml里的<updatePolicy>控制。可选值有always、daily、interval:X、never。如果私服上的快照更新了但本地一直拉旧包,先检查updatePolicy是不是daily且当天已经更新过。另一个常见问题是本地仓库里残留.lastUpdated文件,Maven 看到这个文件会认为上次下载失败,在updatePolicy允许的时间窗口内不再重试。清理方法是删掉对应目录下的*.lastUpdated,或者用mvn -U强制更新。
# 强制更新快照并清理失败标记 mvn clean install -U # 批量清理本地仓库中的 lastUpdated 文件(Linux/macOS) find ~/.m2/repository -name "*.lastUpdated" -delete参数说明:-U强制检查所有快照和 release 的更新,忽略updatePolicy。find命令里的-delete直接删除匹配文件,执行前建议先-print确认范围。Windows 下可以用for /r %i in (*.lastUpdated) do del %i达到同样效果。
5. 避坑与排查:五个让构建失败的经典场景
5.1 现象:Could not resolve dependencies但私服里明明有包
原因通常是settings.xml里的 mirror 配置把内部仓库也拦截了。mirrorOf写成*时,所有仓库请求都转发到 mirror 指向的地址,如果那个地址不是仓库组、不包含内部仓库,就会找不到包。解决方法是把mirrorOf改成central或者external:*,让内部仓库的请求直连。改完执行mvn -U clean install验证。
5.2 现象:多模块构建时子模块找不到父 pom
原因可能是父pom没有先install到本地仓库,或者子模块的<parent>里<relativePath>指向错误。默认relativePath是../pom.xml,如果目录结构不是标准的一级子目录,需要显式写相对路径。解决方法是先在父目录执行mvn install -N,-N表示只构建当前项目不构建子模块,把父pom装进本地仓库后再构建子模块。
5.3 现象:编译报invalid target release: 11
原因是 Maven 运行时使用的 JDK 版本低于 11,但maven-compiler-plugin配置了release11。mvn -v确认Java home指向的 JDK 版本,如果低于 11,要么升级JAVA_HOME,要么把release降到当前 JDK 支持的版本。注意JAVA_HOME改完之后要新开终端,旧终端的环境变量不会自动刷新。
5.4 现象:NoSuchMethodError在运行时出现但编译通过
原因是依赖冲突导致运行时加载了旧版本的类。用mvn dependency:tree -Dverbose找到冲突的库,在dependencyManagement里锁定正确版本,或者在引入依赖时用<exclusions>排除旧版本。如果冲突来自第三方库的传递依赖,优先用dependencyManagement统一管理,而不是在每个引入点都写 exclusion。
5.5 现象:mvn clean删不掉 target 目录
原因是 target 目录里有文件被其他进程占用,Windows 下常见于 IDE 正在索引或者 Tomcat 正在运行。解决方法是先停掉占用进程,或者手动删除 target 目录。Linux/macOS 下如果提示Permission denied,检查目录属主是不是当前用户,必要时用sudo chown -R修正。
6. 用 enforcer 插件锁死版本:一个让团队少吵一半架的习惯
Maven 3.6.3 本身不强制 JDK 和 Maven 版本,但maven-enforcer-plugin可以做到。在父pom里配置requireJavaVersion和requireMavenVersion,任何人在低于要求的版本上构建都会直接失败,而不是等到编译报错才去查环境。这个插件还能用banDuplicatePomDependencyVersions禁止重复声明依赖版本,用dependencyConvergence强制依赖收敛。下面是一个我常用的配置模板。
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <id>enforce-versions</id> <goals><goal>enforce</goal></goals> <configuration> <rules> <requireMavenVersion> <version>[3.6.3,)</version> </requireMavenVersion> <requireJavaVersion> <version>[1.8,)</version> </requireJavaVersion> <dependencyConvergence/> </rules> </configuration> </execution> </executions> </plugin> </plugins> </build>逻辑说明:<requireMavenVersion>的[3.6.3,)表示最低 3.6.3,上不封顶。<requireJavaVersion>的[1.8,)表示最低 JDK 8。<dependencyConvergence/>会在依赖树里出现同一 artifact 的多个版本时直接报错,逼着开发者去dependencyManagement里统一版本。参数上注意:enforcer插件的version建议用 3.0.0 及以上,低版本对 Maven 3.6.3 的兼容性有零星问题。
配置完之后,在 CI 流水线里加一步mvn enforcer:enforce,就能在构建早期拦截环境不一致的问题。我自己的习惯是:每次新建多模块工程,先把 enforcer 配好,再写业务代码。从那以后,团队里“我本地能跑”的争论少了一大半,因为环境不达标根本进不到编译阶段。希望帮到你。
本文还有配套的精品资源,点击获取