
1. 先搞清楚Maven到底在解决什么问题如果你是个Java开发者哪怕只是写过几个学期的课程设计也一定体会过那种“拷个项目过来却跑不起来”的痛苦。别人发给你一个zip包里面塞满了jar文件你解压到自己电脑上报各种ClassNotFoundException然后你开始怀疑人生是不是JDK版本不对是不是漏了哪个包是不是别人的代码有问题其实大概率只是依赖没配对。Maven就是来解决这个问题的。它是一个构建工具更准确地说是一套“依赖管理 项目构建 生命周期规范”的标准。它通过一个叫POMProject Object Model的文件把项目需要哪些第三方库、需要什么插件、怎么打包、怎么跑测试全部描述清楚。你只要拿到源码机器上装好Maven执行一条mvn clean install它就能自动去仓库下载依赖、编译源码、跑测试、打jar包。整个过程不需要你手动去找jar、拷jar、配classpath。很多初学者会把Maven和Gradle搞混其实两者解决的问题是一样的只是配置语言和扩展方式不同。Maven用XML描述项目Gradle用Groovy或Kotlin DSL。在Java老牌企业项目里Maven依然是绝对主力招聘JD里“熟练掌握Maven”出现的频率远高于Gradle。所以从这个工具入手性价比很高。这篇文章不是教你背命令而是从零开始把Maven的下载安装、核心配置、日常命令、IDEA集成以及高频报错这条线完整捋一遍。我自己在Windows、macOS、老旧的Windows 7机器上都装过Maven也在公司私有私服和阿里云镜像之间来回折腾过踩过的坑会专门标出来。不管你是刚入门的学生还是工作几年想系统补基础的开发这篇应该都能帮到你。2. 从下载到环境变量装对Maven比想象中更讲究2.1 官网下载别随便找个镜像站Maven官网是maven.apache.org下载页在Download那个栏目下。点进去会看到两个下载入口Binary zip archive和Binary tar.gz archive。Windows用户下zipmacOS用户zip和tar.gz都可以Linux一般用tar.gz。先说一个容易踩的坑网上搜“maven 3.7下载”会出来一堆第三方站点但Maven官方其实没有3.7这个版本。版本演进大致是3.6.x、3.8.x、3.9.x目前稳定线是3.9.x。你下载时看准Binary类型不要下载Source那是源码包不是运行包。下载完解压后目录结构大概是apache-maven-3.9.x/ ├── bin/ ├── boot/ ├── conf/ ├── lib/ └── README.txtbin下面有mvn脚本macOS/Linux和mvn.cmdWindowsconf下面有全局配置文件settings.xmllib里面是Maven运行自身需要的jar。整个工具本身是Java写的所以你必须先装好JDK。2.2 JAVA_HOME和PATH配置Maven运行需要JAVA_HOME环境变量指向JDK安装目录同时需要MAVEN_HOME或M2_HOME指向Maven解压目录。Windows上操作方式是此电脑右键 - 属性 - 高级系统设置 - 环境变量然后新建系统变量JAVA_HOME D:\Program Files\Java\jdk-17 MAVEN_HOME D:\Program Files\apache-maven-3.9.x然后在Path中新增两条%JAVA_HOME%\bin %MAVEN_HOME%\binmacOS上配置是在~/.zshrc如果你用的是zsh里追加export JAVA_HOME$(/usr/libexec/java_home) export MAVEN_HOME/opt/apache-maven-3.9.x export PATH$PATH:$MAVEN_HOME/bin配置完在终端执行mvn -v能看到Maven版本、JDK版本和本地仓库默认路径就说明装好了。2.3 Windows 7老系统上安装要注意的点热搜词里出现了“老系统Windows7中安装Maven安装与配置”这个我必须单独说一句因为真的有人还要维护老机器。Windows 7能装的最大JDK版本是JDK 8JDK 11官方就不支持Win7了虽然有人通过特殊手段跑起来但很不稳定。所以老机器上请用JDK 8 Maven 3.6.x的组合Maven 3.9.x虽然也可以在JDK 8上运行但为了省心我实测推荐3.6.3搭配JDK 8。另外Win7上配置环境变量后如果终端不识别mvn命令记得新开一个cmd窗口环境变量不会自动刷新到已打开的窗口里。还有一个经常被忽略的问题Maven下载依赖时需要访问远程仓库老机器如果证书过期或者HTTPS握手失败会出现PKIX path building failed报错。这种时候优先更新JDK 8的小版本比如升级到JDK 8u391或者检查系统时间是否正确。我见过有人排查了一下午最后发现是老机器电池没电导致系统时间停留在2019年。3. settings.xml才是真正要花时间研究的东西3.1 用户级配置和全局配置优先用哪个Maven配置分为两层全局的在MAVEN_HOME/conf/settings.xml用户级的在~/.m2/settings.xmlWindows下是C:\Users\你的用户名\.m2\settings.xml。如果两个文件都存在用户级配置会覆盖全局配置。实际工作中我强烈建议你只改用户级配置不要动全局配置。原因很简单一台机器上可能有好几个项目项目A要用阿里云镜像加速项目B要连公司私服你如果改全局的换项目时就得来回改。用户级配置是当前登录用户全局生效的比全局级更灵活而且重装Maven时不用重新配置。那些问“.m2中没有maven setting.xml文件”的同学注意Maven首次运行前确实不会自动生成用户级settings.xml你需要手动创建。最简单的办法是把全局的settings.xml复制一份到~/.m2/下再改。不要自己从零写容易漏标签。3.2 阿里云仓库镜像解决80%下载慢问题国内下载Maven依赖慢是众所周知的痛点特别是Spring、MyBatis这些大依赖国际网络波动一下能卡半天。阿里云提供了一个公共镜像仓库地址是mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror放到settings.xml的mirrors标签里保存后再mvn clean install下载速度会肉眼可见地提升。老一点的教程会让你配https://maven.aliyun.com/nexus/content/groups/public这个地址现在已经迁移了新项目统一用上面那个。这里补充一个原理mirrorOf*/mirrorOf表示所有请求都走这个镜像不管中央仓库还是其他仓库源。这种配置最简单但也有副作用——如果你同时要连公司私服私服里的某些包用*镜像去阿里云找是找不到的。后面我会讲多镜像的正确写法。3.3 多镜像仓库的优先级和匹配规则热搜里那条“maven配置多个镜像仓库”很常见因为公司项目经常既要访问内网私服又要从中央仓库下载开源依赖。很多人会写两个mirror节点然后发现根本不起作用——Maven对同一个mirrorOf匹配的仓库只取第一个匹配到的mirror后面的直接忽略。正确做法是给不同的仓库源配置不同的mirrorOf匹配规则。比如公司私服地址是http://repo.company.com/maven/它的仓库ID是company-repo你希望只拦截这个地址其他都走阿里云mirrors mirror idcompany-mirror/id mirrorOfcompany-repo/mirrorOf urlhttp://repo.company.com/maven//url /mirror mirror idaliyunmaven/id mirrorOf*/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors这样写着company-repo这个仓库的请求会命中第一个mirror其他所有的请求都落到阿里云。mirrorOf支持多种写法*匹配所有external:*匹配除了本地文件系统之外的所有仓库repo1,repo2匹配多个仓库ID!repo1排除某个仓库。多镜像场景下把具体私有仓库ID放前面把通配的阿里云放最后是最稳妥的顺序。3.4 localRepository本地仓库到底该放在哪里本地仓库是Maven把下载过的依赖存到本地磁盘的位置默认在~/.m2/repository。如果你C盘空间紧张或者公司开发机经常重置用户目录建议改到其他盘localRepositoryD:/maven_repository/localRepository很多“Maven本地有包但是引不进来”的问题根源就在这里。比如你在A项目里用了某个依赖Maven把它下载到了C:\用户\张三\.m2\repository你的IDEA配置的User settings file指向的却是公司另一个settings.xml它的localRepository是D:\repo两边路径不一致当然永远找不到包。IDEA里的Maven设置、命令行里的mvn、以及你手动配的localRepository这三者必须指向同一个地方。如果你使用IDEA自带的Maven要注意它默认用的User settings file路径可能和你的不一样。所以项目组成员协作时强烈建议每个人都看一眼IDEA里Maven设置面板的三行路径统一配置。4. pom.xml坐标、依赖和JDK编译的完整细节4.1 坐标体系groupId、artifactId和versionpom.xml是Maven项目的核心它的根节点是project里面最重要的三件套是groupIdcom.example/groupId artifactIdmy-service/artifactId version1.0.0-SNAPSHOT/version这三点合起来叫坐标Coordinate是Maven定位一个构建产物的唯一标识。groupId一般是你公司域名的反写artifactId是项目名version是版本号。两个项目如果groupId和artifactId相同但version不同Maven会认为它们是同一个库的不同版本如果version完全相同但groupId不同Maven会认为它们是两个完全不同的库。所以命名规范直接影响依赖解析的正确性。版本号后面常跟的SNAPSHOT后缀表示快照版本也就是还在持续开发的不稳定版本。与之相对的是RELEASE正式版本。SNAPSHOT版本有个特性每次构建都会去远程仓库检查是否有更新而RELEASE版本一旦下载到本地就再也不更新了。这就是为什么有些开发者改了私有依赖的代码重新deploy到私服后别人本地一直引用旧代码——因为引用的正式版本已经被缓存了。4.2 依赖管理和传递依赖dependencies标签下面每个dependency都是一条依赖声明。只写坐标还不够有时候还需要配scope。scope决定依赖在哪个阶段生效常见的有scope生效阶段典型场景compile编译、测试、运行默认值大多数业务库provided编译、测试Servlet APITomcat自带runtime测试、运行MySQL驱动运行时才加载test测试JUnit、Mockitosystem编译、测试本地系统jar包配合systemPath使用不推荐很多人只见过compile和test容易把MySQL驱动写成compile。实际上MySQL驱动只需要运行时用写runtime更合适这样编译期classpath里没有驱动能强制你通过接口编程而不是直接依赖驱动内部类。当然这是规范洁癖很多项目写compile也能跑但碰到某些依赖冲突时这种细节就是分水岭。Maven还有一个很重要的机制叫传递依赖你引入A库A库内部又依赖了B库Maven会自动把B库也拉进来不需要你手动声明。这个机制极大方便了依赖管理但也带来了“依赖冲突”问题。比如A库依赖了logback 1.2.0C库依赖了logback 1.3.5Maven默认采用“最短路径优先”和“先声明优先”两个原则来裁决最终只有一个版本生效。如果你不确定项目里最终用的是哪个版本用mvn dependency:tree看依赖树最清楚。4.3 高频依赖解析失败mysql-connector报错的真相热搜里有条很典型的报错maven artifact com.mysql:mysql-connector-j:release cannot be resolved in e...。这个报错我在不少群里见过很多人第一反应是“网络问题重新下载”其实真正的坑在于坐标写法。MySQL官方最新驱动坐标是这样的dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.4.0/version /dependency注意artifactId是mysql-connector-j不是以前老项目的mysql-connector-java。如果你在搜索教程时看到了旧坐标com.mysql:mysql-connector-java这个坐标虽然还能用但版本号停留在8.0.x官方文档已经不推荐新项目使用了。报错里出现release字样说明有人写的是versionrelease/version这是Gradle脚本里的写法不是Maven的。Maven的version必须是一个明确的版本号写release会导致解析失败。如果你不确定最新版本号去mvnrepository.com搜一下别凭记忆写。类似的情况还出现在langchain4j和spring-ai这类新兴AI框架上。它们的Maven坐标和版本经常变动网上教程版本可能已经过期直接照抄轻则解析失败重则引入了有安全漏洞的旧版。统一原则任何依赖坐标都以官方文档或mvnrepository为准。4.4 Maven内嵌的JDK编译级别为什么总是提示source/target很多初学者写过这样一个场景项目代码用了lambda表达式编译时报错“错误: -source 8 中不支持 lambda 表达式”。这是Maven默认编译级别太低导致的。Maven本身不会自动使用你系统里最新的JDK版本它在Java 8时代默认source/target是1.6在Java 17时代可能只是1.8。正确做法是显式指定编译参数。在pom.xml里加上properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties这里顺便说一下java.version标签和maven.compiler.source的区别。Spring Boot的父POM里经常看到java.version17/java.version这个标签本身是Spring Boot定义的属性用来传递到编译插件中。如果你不用Spring Boot父POM只写java.version不写maven.compiler.source编译级别是不会变的。所以要么两个都写要么用更严格的release参数properties maven.compiler.release17/maven.compiler.release /propertiesrelease比source/target更安全因为source和target只控制语言级别和字节码版本实际编译时如果代码误用了新JDK的API在旧JDK上运行依然会报NoSuchMethodError而release会强制你只能使用指定JDK版本的API。5. 命令行里最常用的五个场景clean install、打jar包、跑JUnit测试5.1 mvn clean install到底执行了什么很多教程让你执行mvn clean install你照着敲了看到BUILD SUCCESS就以为完事了。其实这条命令背后隐藏了Maven最重要的概念——生命周期Lifecycle。Maven有三种内置生命周期clean、default、site。clean是用来清理的default是真正干活的install是default生命周期最后阶段的一个绑定动作。mvn clean install等于先执行clean生命周期再执行default生命周期。default生命周期再细分有validate、compile、test、package、verify、install这些阶段。每个阶段执行之前它前面的阶段都会按顺序先执行一遍。所以mvn install实际会经历编译源码 - 运行测试 - 打包 - 安装到本地仓库。这就是为什么第一次执行时耗时很长第二次执行时会跳过没变化的步骤速度快很多。如果你只是想编译一下代码看有没有语法错误用mvn compile。只是想跑测试用mvn test。只是想打jar包但不想安装到本地用mvn package。刻意使用更具体的命令能节省不少时间尤其是大型项目里clean install一次可能要跑好几分钟。5.2 跳过测试的两种方式执行mvn install时Maven会运行项目里所有的JUnit测试类如果某个测试失败构建就会中断。这在有些场景下很烦人比如你只是想快速验证打包流程或者测试代码本身写得很烂一跑就挂。跳过测试有两种方式建议别混用# 跳过测试运行但仍然编译测试代码 mvn clean install -DskipTests # 跳过测试编译连测试代码都不编译 mvn clean install -Dmaven.test.skiptrue-DskipTests是交给Surefire插件处理的它不会执行测试但测试类还是会编译能及时发现测试代码的编译错误。-Dmaven.test.skiptrue是跳过编译阶段速度更快但风险也大——如果你顺手改了测试类里引用的生产代码签名这里不会报错等你以后想跑测试时才会炸出来。5.3 打包成可执行jarspring-boot-maven-plugin的作用普通Java项目执行mvn package打出来的jar包里面只有你自己项目的class文件不包含依赖的第三方库。这种jar运行时会报“找不到主类”或者“ClassNotFoundException”因为依赖不在classpath里。Spring Boot项目有专门的打包插件build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build它会把项目打包成可执行的fat jar所有依赖都嵌进去里面还有内嵌的Tomcat等服务器直接java -jar xxx.jar就能跑。注意这种fat jar不能作为普通依赖被其他项目引用所以如果你是给其他项目提供公共库记得只打普通jar不要用Spring Boot的repackage。5.4 搭建JUnit测试环境热搜里提到“搭建junit测试环境”这里也一并说清楚。现在主流的JUnit版本是JUnit 5Jupiter坐标是dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.10.2/version scopetest/scope /dependency注意scope是test它不会打进最终的构建产物。测试类放在src/test/java目录下方法上标Test注解Maven的test阶段会自动扫描并运行它们。如果你的测试类运行不起来最常见的原因是Maven没识别到JUnit依赖或者Surefire插件版本和JUnit版本不匹配。JUnit 5要求Surefire插件版本在2.22以上如果你用的是老项目的pom很可能还在用2.12.x。6. IDEA里配置Maven与高频报错的完整排查链路6.1 IDEA中Maven配置面板的四个关键项IDEA的Maven设置入口在File - Settings - Build, Execution, Deployment - Build Tools - Maven。面板里有几个关键路径Maven home pathMaven安装目录如果你用IDEA自带的Maven这里显示的是IDEA内置路径。User settings filesettings.xml文件路径一般是你自定义的~/.m2/settings.xml。Local repository本地仓库路径它会读取settings.xml里的localRepository配置。JDK for importer用于导入Maven项目的JDK版本。很多新手直接在项目里导入别人给的pom.xmlIDEA开始下载依赖下载半天后还是全红。第一步就是检查这个面板里的User settings file路径是否指向了你自己的settings.xml。如果你没有自定义settings.xmlIDEA默认会用~/.m2/settings.xml这个文件不存在时它相当于空配置。6.2 IDEA识别不了Maven项目工具栏也不见了打开一个项目右侧没有Maven工具栏右键pom.xml也没有“Add as Maven Project”选项这在IDEA里偶有发生。处理方式按顺序排查检查pom.xml文件是否位于项目根目录如果嵌套在子目录里IDEA可能不会自动识别。尝试右键pom.xml文件 - Add as Maven Project手动把它加入Maven管理。如果右键也没有打开File - Project Structure - Facets删除已有的Maven相关配置后重新导入。还是不行的话执行File - Invalidate Caches / Restart清缓存重启绝大部分“工具面板诡异消失”的问题都能解决。IDEA的Maven工具栏一般在IDEA窗口的右侧边缘一个“M”字母的竖排标签。2021版之后的新版IDEA将Maven面板入口收进了右侧的插件图标里如果找不到就看看是不是折叠起来了。6.3 IDEA里Maven依赖全爆红的排查流程“Maven文件全爆红”可以说是Java开发群里最高频的问题了。完整的排查链路应该是第一步先看IDEA的Event Log和Maven面板的报错输出。如果是Cannot resolve ...说明依赖确实没下载成功。第二步检查网络。如果刚才没配阿里云镜像打开settings.xml确认镜像配置然后执行mvn clean install -U强制更新快照。第三步确认本地仓库目录权限。Windows下如果用户目录有权限锁Maven无法写入.m2目录也会爆红把localRepository改到普通目录就能解决。第四步如果本地明明有jar包但IDEA还是找不到去本地仓库对应路径下看看有没有_remote.repositories这类标记文件。有时候这些文件会记录依赖是从哪个远程仓库拉取的如果镜像配置变了Maven会认为本地缓存的不可信于是重新去远程下载下载失败就报红。最简单的解决问题是删除该依赖目录下的_remote.repositories和_lastUpdated后缀文件再重新导入。这里分享一个我实测很有效的土办法如果你不想清理整个.m2目录只清理出问题的依赖。在.m2/repository下找到对应的groupId路径把那个目录直接删掉回到IDEA点击Maven面板上的Reload All Maven Projects让它重新下载。不要用右键的“Download Source and Documentation”那个不会强制重新下载依赖本身。6.4 老项目编译报错找不到com.sun.image.codec.jpeg.JPEGCodec热搜里那条“maven编译项目报找不到类com.sun.image.codec.jpeg.jpegcodec”这是一个很典型的JDK版本迁移问题。com.sun.image.codec.jpeg.JPEGCodec是JDK 8及之前版本自带的内部类它所在的位置叫rt.jar。从JDK 9开始Oracle从JDK里移除了这个内部API所以任何在JDK 9以上环境编译的项目如果引用了它都会报类找不到。报错出现时先别急着骂Maven。Maven只是执行编译的工具出错的根源是代码依赖了JDK内部接口。解决办法有三个方向一是把代码里的图片压缩处理改写为javax.imageio.ImageIO标准API这个最规范但需要改代码二是临时把项目编译级别降到JDK 8并安装JDK 8三是把旧的rt.jar塞进编译路径这个操作我极度不推荐它会污染classpath还会在未来升级时埋雷。6.5 Eclipse和其他IDE里的Maven虽然现在IDEA是主流但热搜里也出现了Eclipse的报错an internal error occurred during: updating maven project。这个在Eclipse里也见过很多次多数是Eclipse自带的Maven插件和项目配置文件不兼容。解决思路是右键项目 - Maven - Update Project勾选Force Update of Snapshots/Releases。如果一直更新失败删掉项目里的.classpath和.project文件重新导入Maven项目。另外一个热词“dbeaver 设置maven地址”很多人可能奇怪数据库客户端为什么要配Maven。DBeaver本身是Java写的它依赖Maven来下载JDBC驱动管理插件所以你需要在DBeaver的设置里指定一个可用的settings.xml比如配了阿里云镜像的那个这样它下载驱动才会快。理解了“Maven只是个依赖管理器任何Java工具都能复用”这一层你就不会对这种跨界配置感到奇怪了。同理Cursor这种AI编辑器要配置Java和Maven本质也是告诉它JDK安装路径和Maven路径它内部会调用mvn命令来做编译和索引。关于Maven配置我最后再强调一件事环境变量、settings.xml、IDEA配置、命令行参数这四者的优先级和生效范围一定要心里有数。命令行用-s xxx指定settings.xml会覆盖IDEA里的User settings fileIDEA里的Maven路径设置会覆盖系统的MAVEN_HOME。在多个环境之间切换时我在本地仓库根目录放了一个readme.txt记录每次改动的原因和时间避免一个月后自己都忘了为什么配了这么奇怪的镜像顺序。