把 Maven 单独拎出来整理一篇核心知识,是我一直想做的事。原因很简单:面试的时候十个 Java 岗有八个会问依赖管理,平时开发十个工程有九个离不开 Maven,但真正能把 settings.xml 和 pom.xml 说明白的人,其实没那么多。这篇不是我临时查文档拼出来的,而是这些年从手动导 jar 包、到踩了无数依赖冲突和下载缓慢的坑、再到现在顺手就能搭好一套项目结构,沉淀下来的核心知识。
Maven 是干嘛的?一句话概括:一个 Java 项目的构建工具和依赖管理工具。它帮你统一管理第三方 jar 包、统一编译打包流程、统一项目目录结构。如果你刚开始学 Java、准备从手动导包过渡到规范工程化开发,或者你想把 IDEA 里 Maven 的配置弄得明明白白,这篇内容基本够用了。本文会从最基础的原理讲起,再把手上的安装、配置、依赖管理、IDEA 集成和排错经验一次说完。
1. 先搞清楚 Maven 到底解决了什么问题
1.1 没有 Maven 的时代,Java 项目是怎么管理的
很多新人不理解为什么非要引入 Maven,觉得 IDE 里也能导包、也能编译、也能运行。这个想法我太熟悉了,因为我刚入行时也这么干过。那时候建一个 Java Web 项目,第一件事就是去网上搜"Spring jar 包下载",然后下载一个几十 MB 的压缩包,解压之后把里面一坨 jar 全扔进 WEB-INF/lib 目录。麻烦马上就来:jar 包之间版本会冲突,你根本说不清 A 依赖的是 B 的哪个版本;换一台电脑,所有导包操作要重来一遍;时间一长,lib 目录里躺着几十个根本用不上的 jar,谁也不敢删。
这还只是导包环节。到了编译打包环节,传统的做法是 IDE 里点按钮,开发机器上能跑、构建服务器上一跑就报错,因为两台机器的依赖环境不一样。部署的时候更原始,手动把 war 包拖来拖去,没有任何自动化可言。团队协作时,代码提交到 Git 上,新成员拉下来第一件事就是折腾本机环境,半天起步。
Maven 解决的就是这套混乱。它用"坐标"唯一定位每一个依赖,用"仓库"统一管理 jar 包的下载和缓存,用"生命周期"把编译、测试、打包、部署串成标准流程。你不再需要手动下载 jar 包,只需在 pom.xml 里声明依赖的坐标,Maven 会自动从仓库拉取;你也不再需要告诉别人"你要手动加哪个 jar",一切由配置自动完成。这才是真正工程化开发该有的样子。
1.2 Maven 的核心思想:约定优于配置
Maven 能流行起来,除了能管依赖,还有一个关键原因:它强行规定了一套项目目录结构。这种做法叫"约定优于配置"。意思是,与其让你配置一堆路径,不如直接把标准和默认规则定好,你在规定的位置放代码就行了。
标准 Maven 目录结构大概是这样的:
text复制project-root
├── pom.xml
├── src
│ ├── main
│ │ ├── java # 项目源码
│ │ ├── resources # 配置文件
│ │ └── webapp # Web 项目前端资源
│ └── test
│ ├── java # 测试代码
│ └── resources # 测试资源
└── target # 构建输出目录
刚开始你可能觉得不习惯:为什么 Java 代码非要放在 src/main/java 下面?但坚持用一段时间之后你会发现好处很实在。第一,任何一个小组成员拉下代码,不看文档也能立刻知道哪里放源码、哪里放配置、哪里写测试。第二,Maven 插件按这些固定目录去编译、打包,整个过程是自动且稳定复现的。第三,你换 IDE 或者换电脑,项目依然能构建起来,因为标准是一致的。
如果你确实想把目录改掉,也可以在 pom.xml 的 build 节点里面用 sourceDirectory、testSourceDirectory 等配置去覆盖默认路径。但我建议新手上路阶段千万别这么干,除非遇到那种老古董项目,否则默认目录就是最优解,省事、可读性高、生态兼容性好。
1.3 认识 Maven 三大核心概念:坐标、仓库、生命周期
Maven 之所以能让全世界 Java 开发者共享同一个 jar 管理体系,靠的是三样东西:坐标、仓库、生命周期。这三样你理解了,Maven 就通了一半。
先看坐标,就是每个依赖的唯一标识。一个完整的 Maven 坐标由 groupId、artifactId、version 三部分组成,类比一下就是"公司域名 + 项目名 + 版本号"。比如:
xml复制<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.4</version>
</dependency>
这就是 Jackson JSON 库的坐标,Maven 看到这组信息就知道该去哪个位置找这个 jar。坐标是 Maven 里最基础的概念,任何一个依赖、任何一个你自己发布的模块,都靠坐标来唯一定位。
再看仓库,它是 jar 包存储和分发的场所。Maven 的仓库分成三类:本地仓库、中央仓库、远程仓库。本地仓库默认在用户目录下的 .m2/repository 里,是你机器上所有依赖的缓存;中央仓库是 Maven 官方维护的全球公共仓库,地址是 repo.maven.apache.org,包罗了绝大多数开源库;远程仓库也叫私服,是公司内部架设的仓库,通常用来放内部插件和构件。下载顺序也很有讲究:先查本地仓库,找不到再去配置的镜像或远程仓库下载,下载完存进本地仓库。
最后看生命周期,它是 Maven 定义的一套标准构建流程。最重要的几个生命周期阶段按顺序是:validate、compile、test、package、verify、install、deploy。你运行一条"mvn package"命令,Maven 就会从 validate 开始,把之前的阶段依次执行完,然后打包输出到 target 目录。为什么能一条命令完成整个流程,就是生命周期的功劳。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 本地环境安装与配置文件实操
2.1 Windows 和 macOS 下的安装步骤
安装 Maven 真的不难,但网上信息鱼龙混杂,不少人卡在 JDK 版本兼容上。先说一个原则:先装好 JDK,再装 Maven,并且保证 JDK 版本在 Maven 支持范围内。目前主流的 Maven 3.8.x 和 3.9.x 要求 JDK 8 以上,如果你本地是 JDK 8、11、17,那基本都没问题。
Windows 上的安装流程是这样的:先去 Maven 官网下载 Binary zip archive,注意不要下载带 source 的那个,那个是源码包。把压缩包解压到一个路径清爽的目录,比如 D:\tools\apache-maven-3.9.6。接着配置环境变量,新建一个 MAVEN_HOME 指向刚才的解压目录,再把 %MAVEN_HOME%\bin 加到 Path 变量里。Linux 和 macOS 上操作本质一样,下载 tar.gz 后解压到 /usr/local 或者 ~/tools,然后在 .bashrc、.zshrc 里加一行:
bash复制export MAVEN_HOME=/usr/local/apache-maven-3.9.6
export PATH=$MAVEN_HOME/bin:$PATH
如果你用 macOS 并且装了 Homebrew,也可以一行命令搞定:brew install maven。这种方式对新手比较友好,因为它自动帮你把路径配置好了。安装完打开新终端执行 mvn -v,能看到 Maven 版本和 Java 版本就说明环境变量没问题。
这里提醒一个隐蔽的坑:有些开发机上同时装了多个 JDK,Maven 默认用的是 JAVA_HOME 环境变量指定的那一个。如果你发现 mvn -v 显示的 Java 版本和你预期的不一样,先检查系统 JAVA_HOME 到底指向哪儿。还有,千万别图省事直接把 Maven 解压包里 lib 目录下的 jar 拿出来手动扔到项目里,那样会把整个依赖机制搞乱。
2.2 settings.xml 里的关键配置项详解
Maven 的全局配置文件叫 settings.xml,它决定了 Maven 在你这台机器上的行为,包括本地仓库位置、镜像仓库地址、认证信息、JDK 编译级别等。这个文件有两个层级,一个是 Maven 安装目录下的 conf/settings.xml,叫全局配置;另一个是用户目录下的 ~/.m2/settings.xml,叫用户配置。实际生效时,用户配置的优先级高于全局配置,两个文件都存在的时候,Maven 会把它们合并使用。
新手最容易忽略的是 localRepository 配置。默认本地仓库路径是 ~/.m2/repository,如果你不想占 C 盘,可以把它改到其他盘符,比如 Windows 下改成 D:/maven/repository。配置方法很简单:
xml复制<settings>
<localRepository>D:/maven/repository</localRepository>
</settings>
这个路径决定了所有下载下来的依赖缓存放到哪里。改完之后你会发现,之前下载过的 jar 包全在新目录下,C 盘空间一下子就释放出来了。我见过有同事第一次见本地仓库的目录结构,被一堆散落的文件吓到,其实这些都是 Maven 的缓存管理机制,不要手动乱删它们。
settings.xml 里还有一个经常会用到的节点叫 profiles,它允许你针对不同的环境激活不同的配置。最常见到的一个用法是指定 JDK 版本。比如你机器上装了一堆 JDK,但是某个老项目必须用 1.8 编译,不配置 profiles 很容易被 Maven 默认的编译版本坑到。在 profiles 里声明一个 JDK 1.8 的 profile,然后激活它,Maven 编译时就按 1.8 的级别来。不过说实话,我更推荐在 pom.xml 里通过 maven-compiler-plugin 来固定编译级别,因为这个配置会跟着项目走,其他人拉下代码也不会踩坑。
2.3 配置阿里云镜像仓库的正确姿势
Maven 默认去中央仓库下载依赖,但中央仓库服务器在国外,国内网络环境下要么下载速度慢得离谱,要么直接超时失败。解决办法就是配置镜像仓库,把中央仓库的下载请求转发到国内速度快、服务稳定的镜像地址上,比如阿里云。
配置方式非常简单,在 settings.xml 的 mirrors 节点下添加一个 mirror:
xml复制<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
这里最关键的一行是 mirrorOf 标签,它决定了这些仓库对哪些请求生效。我写的 * 表示拦截所有远程仓库的请求,也就是中央仓库、Spring 仓库等外部仓库的下载都会走阿里云的这个镜像。如果你只希望某个仓库走阿里云,其他仓库走别的地址,可以把 mirrorOf 写成具体仓库的 id,比如 central。但新手阶段我还是推荐直接用 *,简单粗暴,效果最好。
如果项目里还用到了一些特定类型的仓库,比如 Google 的 Android 相关依赖,也可以额外添加一个阿里云的 Google 仓库镜像。阿里云的公共仓库已经聚合了 maven central、jcenter、google 等主流源,日常开发基本一个 url 就够了。配好之后,可以执行 mvn help:effective-settings 查看当前生效的配置,确认镜像是否已经生效。执行完看到你写的 mirror 出现在控制台里,就说明配置成功,下载速度立刻不一样。
3. pom.xml 依赖管理与构建生命周期
3.1 pom.xml 的基本结构和坐标定义
pom.xml 是 Maven 项目的心脏,所有依赖、插件、构建配置都写在这里。新建一个 Maven 项目时,默认生成的 pom.xml 结构并不复杂,你需要重点理解最核心的几个节点。先看一段典型的例子:
xml复制<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
modelVersion 基本是固定值 4.0.0,不用改。groupId、artifactId、version 就是项目的坐标,它们组合起来决定这个项目在 Maven 世界里的身份。groupId 一般用公司域名的倒写,比如 com.example;artifactId 是项目名;version 是版本号,开发阶段经常看到 1.0-SNAPSHOT 这种带 SNAPSHOT 标识的版本,它代表快照版本,随时可以重新构建发布。
packaging 节点表示打包方式,最常见的三个值是 jar、war、pom。普通工具类库用 jar,Java Web 项目用 war,多模块聚合工程的父工程用 pom。properties 节点可以用来统一定义变量,比如编译版本、编码格式、依赖版本号等。把依赖版本抽到 properties 里是个好习惯,比如:
xml复制<properties>
<spring.version>5.3.27</spring.version>
</properties>
后面引用的时候写 ${spring.version} 就行,升级版本时只需改一处,不用全局搜索替换。
3.2 依赖范围、传递与冲突解决
依赖是 pom.xml 里最常用的节点。声明一个依赖,除了写 groupId、artifactId、version,还可以指定一个很重要的属性:scope,也就是依赖生效的范围。不同的 scope 决定了这个 jar 在编译、测试、运行时是否可见。常见的有下面几种:
| scope 值 | 编译期 | 测试期 | 运行期 | 典型例子 |
|---|---|---|---|---|
| compile | 有效 | 有效 | 有效 | spring-core、commons-lang3 |
| provided | 有效 | 有效 | 无效 | servlet-api、lombok |
| runtime | 无效 | 有效 | 有效 | mysql-connector-java |
| test | 无效 | 有效 | 无效 | junit、mockito |
| system | 有效 | 有效 | 按配置 | 本地自定义 jar(很少用) |
seletor-provided 是最容易理解错的。拿 javax.servlet-api 举例,编译代码的时候需要它,但真正部署到 Tomcat 时,容器自己带了 servlet 相关的类,如果你在依赖里声明成 compile,打包时就会把 jar 也塞进去,最终和容器自带类冲突。设成 provided,IDEA 和 Maven 编译时可以用,但打包时不会带上它。
再说依赖传递。你的项目 A 依赖了 B,B 又依赖了 C,那么 C 会被自动传给 A。这个机制很方便,但也带来了麻烦,最典型的就是依赖冲突。假设 A 项目依赖了 spring-web 5.2.0,另一个依赖又传入了 spring-core 5.1.0,那么到底用哪个版本,Maven 有它自己的仲裁规则。规则只有两个:路径短的优先;路径一样长的情况下,先声明的优先。这个听起来很简单,但实际情况里经常出现你预期之外的版本被选中,最后运行时报 NoSuchMethodError,就是因为一个类的源码在某个版本里改了方法签名,而你依赖的却是另一个版本。
解决依赖冲突最有效的办法就是查看依赖树。在项目根目录执行 mvn dependency:tree,你就能看到整个项目的依赖继承和传递关系。找到冲突的具体依赖,然后在你自己的 pom.xml 里用 exclusions 把不需要的传递依赖排除掉:
xml复制<dependency>
<groupId>com.example</groupId>
<artifactId>some-service</artifactId>
<version>2.0</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
排除之后,如果你确实还需要这个功能,就单独引入你想要的那个版本的 jar,这样做的控制力最强。
3.3 Maven 生命周期与常用构建命令
Maven 的构建过程被抽象成三个阶段:clean、default、site。default 生命周期是重点,按顺序包含 validate、initialize、compile、test、package、verify、install、deploy 等阶段。当你执行某条命令时,Maven 会从该命令对应的阶段开始,依次向前执行经过的所有阶段。
实际开发里最常用的命令组合是 mvn clean install。clean 单独属于 clean 生命周期,目的是删除旧的 target 目录;install 会把项目构建产物安装到本地仓库,供其他本地项目引用。这条命令执行的过程大致是:先清理 target,然后编译 main 代码,运行 test 代码,把代码打包成 jar 或 war,最后安装到本地仓库。一条命令能把所有事干完,关键就是生命周期的顺序约定。
关于跳过测试,我有一个明确建议:本地想快速验证编译能不能通过,就用 -DskipTests 跳过测试执行但保留测试代码编译;如果你连测试代码编译都嫌慢,用 -Dmaven.test.skip=true,它会把测试代码的编译和执行全部跳过。两者的区别一定要分清楚,我见过有人把 -DskipTests 当成跳过测试编译,结果还是报编译错误。
常用命令我再整理一份速查:
bash复制mvn clean # 清理 target 目录
mvn compile # 编译 src/main/java
mvn test # 运行测试
mvn package # 打包成 jar/war
mvn install # 打包并安装到本地仓库
mvn clean install # 完整重建并安装
mvn dependency:tree # 查看依赖树
mvn dependency:analyze # 分析依赖使用情况
mvn help:effective-pom # 查看最终生效的 pom 配置
到这一步,你已经能用 Maven 管理一个标准项目了。但要说真正好用,必须把它接入 IDE,同时掌握命令行构建时的排错技巧,这也是日常开发里最容易折腾人的部分。
4. IDEA 集成与命令行实操
4.1 在 IDEA 里正确配置 Maven
IDEA 内置了 Maven,所以很多人图省事直接用默认配置,结果遇到了"本地仓库路径不是自己想要的""settings.xml 改了不生效"这类问题。正确做法是在 IDEA 的 Settings 里手动指定你自己的 Maven。打开路径是:File -> Settings -> Build, Execution, Deployment -> Build Tools -> Maven。
进入页面以后,你会看到 Maven home path、User settings file、Local repository 三项。Maven home path 要选成你自己解压的 Maven 目录,而不是 IDEA 自带的那个。User settings file 要勾选右侧的 Override,然后手动选择你本机的 settings.xml 路径。操作完之后,往下看 Local repository 会自动跟着 settings.xml 里配置的路径变化,如果没变,点一下后面的刷新按钮让它重新读取。
这里特别提醒一个新手容易踩的坑:在 Maven 安装目录的 conf/settings.xml 里和 ~/.m2/settings.xml 里各写了一份配置,两边不一致,结果改了 A 却发现没生效,因为 IDEA 里选的是 B。所以你应该先定好"到底用哪一个文件",然后在 IDEA 里明确指向它,不要两边同时维护。我个人习惯是统一用用户目录下的 ~/.m2/settings.xml,因为这个文件对当前机器上所有 Maven 项目全局生效,而且改起来不用去动 Maven 安装目录,升级 Maven 版本时配置也不会丢。
4.2 用好 IDEA 的 Maven 面板
配置好之后,IDEA 右侧会有一个 Maven 面板,这是日常开发里最高频使用的工具。我第一次见这个面板时,只以为它就是个"刷新按钮",后来才发现里面的信息密度极高。Maven 面板按照项目模块展示,展开一个模块后能看到 Lifecycle、Dependencies、Plugins 等几个快捷分组。Lifecycle 分组里列出了 clean、validate、compile、test、package、install 等 Maven 生命周期阶段,双击某个阶段就会在 IDEA 里直接执行对应命令,不用切到终端敲命令。
Dependencies 分组展示的是当前项目的所有依赖,包括直接依赖和传递依赖。你可以在这里快速看某个包到底引了哪个版本,右键还能跳到更多操作。这个视图在排查依赖冲突时帮过我好几次,图形化展示比命令行敲 dependency:tree 更直观。Plugins 分组里是项目用到的所有插件,点开还能看每个插件的生命周期绑定和执行目标,适合进阶研究。
右上角有几个快捷键也很讲究。刷新按钮是重新导入 Maven 项目,当你改了 pom.xml 之后必须点一下刷新,否则 IDEA 的依赖提示不会更新。旁边有一个跳过测试模式的按钮,点开后执行的 package、install 都会自动跳过测试,适合本地临时快速构建。还有一个类似大象的图标,点击会打开 maven 设置快捷入口,可以快速切换 profile。反正记住一点:改了 pom.xml,第一时间刷新 Maven 项目;执行构建报错了,先看 Maven 面板有没有同步最新依赖。
4.3 命令行构建的常用姿势与结果解读
虽然 IDEA 里点按钮很方便,但命令行构建依然是最值得掌握的能力。CI 服务器上打包、服务器上离线构建、排查别人机器上死活构建不过去的问题,全都依赖命令行。命令行构建的最大优势是环境统一,不会因为 IDE 设置不同产生差异。
最常见的操作是在项目根目录执行:
bash复制mvn clean install -DskipTests
这条命令执行的时候,你会看到控制台滚动大量日志。新手第一次见到会慌,其实只需要关注几个关键信息。执行到某个模块,如果打印 [INFO] BUILD SUCCESS,就说明构建成功;如果打印 [INFO] BUILD FAILURE,说明失败了,需要往上翻找到 [ERROR] 开头的内容,那才是真正的问题所在。千万别从日志开头往下读,Maven 日志是分阶段的,你只要抓住 ERROR 和 BUILD SUCCESS 这两处就行。
构建成功后,target 目录会出现打包产物。jar 项目的产物是个可执行的 Jar 文件,war 项目会生成一个 war 包。你可以在 target 目录下看到 classes、generated-sources、maven-status 等目录,这些都是 Maven 生命周期中各个阶段自动生成的。如果构建失败,常见的解决思路是先看 ERROR 的具体位置,然后判断是代码编译问题、依赖下载问题还是测试失败问题,而不是直接重跑一遍。盲目重跑不仅浪费时间,还会让日志堆叠,更难定位问题。
5. 常见问题与排错实录
5.1 依赖下载失败与本地仓库缓存清理
国内开发环境下,依赖下载失败是特别高频的问题。最常见的现象是 IDEA 报 Cannot resolve dependency 或者 Unable to import maven project,背后的原因多半是网络访问中央仓库超时。
解决思路分几步。第一步,确认 settings.xml 里是否配置了阿里云镜像,如果没配,优先补上。第二步,检查本地仓库里是否残留了下载失败的标记文件。Maven 下载依赖时如果异常中断,会在本地仓库的对应目录下生成一个 .lastUpdated 结尾的文件,而且会缓存一段时间。即使你修复了网络再重新构建,它也可能因为发现有这个文件而跳过重新下载。这时候可以手动删除失败依赖对应的整个目录,也可以用下面的命令强制刷新:
bash复制mvn clean install -U
-U 参数表示强制检查远程仓库更新,它会忽略本地缓存的过期判断,强制重新拉取 SNAPSHOT 依赖和损坏的构件。这个参数在开发联调阶段特别常用,但也要注意:它会消耗更多下载时间和网络流量,不是每次构建都建议加。如果项目里某个依赖就是反复失败,还可以在 IDEA 的 Maven 设置里打开自动导入,或者直接重启 IDEA,让项目重新加载一遍。
5.2 依赖冲突的定位与排除实战
依赖冲突是 Maven 项目里最容易让新人崩溃的问题。常见报错有 java.lang.NoSuchMethodError、java.lang.NoClassDefFoundError、ClassNotFoundException,运行时报错往往在启动或调用某个功能时出现,但编译期却是正常的,因为编译期 IDE 用的是它认为正确的 jar,运行期类加载器加载的却是另一个版本的 jar。
定位冲突的唯一标准姿势是看依赖树。在项目根目录执行:
bash复制mvn dependency:tree
如果你觉得输出太长,可以加 -Dincludes 只过滤某个依赖,比如:
bash复制mvn dependency:tree -Dincludes=com.google.guava:guava
这样能快速看到 guava 在整个依赖链里被哪些模块引用了、各自是什么版本。找到多个版本之后,确认哪一个是当初需要的,然后按照前面说的两种方式处理:如果多个依赖都传入了同一个 jar 的不同版本,优先用 Maven 的仲裁规则,让路径最短的胜出;如果你需要强制指定某个版本,可以在自己项目的 pom.xml 里直接用 dependencyManagement 来统一管理版本。多模块项目里,dependencyManagement 配合父 pom 是治理依赖版本的最佳方案,建议至少理解它的作用:
xml复制<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
</dependencyManagement>
dependencyManagement 不会把依赖直接传递给模块,它只负责"凡是没有声明版本号的依赖,统一按这个版本处理"。父工程定好版本,子模块只写 groupId 和 artifactId,就能避免整个项目出现五花八门的版本号。
5.3 编译版本、编码问题与其他细节坑
除了依赖问题,Maven 使用中还有几个高频细节坑,个个都能让人卡半天。
第一个是编译级别不对。在 IDEA 里右键运行项目没问题,但命令行 mvn package 之后部署到服务器,一启动就报 UnsupportedClassVersionError,说明本机编译用的 JDK 版本高于服务器运行环境。这种问题的根源在于 pom.xml 里没有显式指定编译版本,Maven 使用默认的 JDK 版本编译。解决办法是在 pom.xml 里固定编译版本:
xml复制<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
如果你用 JDK 9 以上的版本,推荐使用 release 参数:
xml复制<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>8</release>
</configuration>
</plugin>
release 参数能避免 source 和 target 在某些 JDK 版本下仍然编译出高版本字节码的问题,这是我自己实测踩过坑以后总结出来的经验。
第二个是乱码问题。Maven 编译过程中如果涉及资源文件复制和测试执行,字符集不一致就会产生中文乱码。推荐在 properties 里固定编码:
xml复制<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
第三是 settings.xml 配置改了但不生效。大概率是 IDEA 的 Maven 设置里没有勾选 Override,或者你同时存在多个 settings.xml 文件。排查方式是执行 mvn help:effective-settings,看看 Maven 到底用的是哪个配置文件。
关于这些零散问题,我最后整理成一张速查表,方便你日常对照:
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 依赖下载慢 | 中央仓库网络不稳定 | 配置阿里云镜像 |
| 构建报错有 .lastUpdated 文件 | 上次下载中断 | 删除文件或用 -U 刷新 |
| UnsupportedClassVersionError | 编译版本高于运行版本 | 固定 maven.compiler.source/target |
| 中文乱码 | 字符集未指定 | 设置 UTF-8 编码 |
| IDEA 中依赖不更新 | pom 改动未刷新 | 点击 Maven 面板刷新按钮 |
| 本地两个 settings.xml 冲突 | 配置不统一 | 只保留一个并指定使用 |
| 打包多了 servlet-api | 依赖作用域写错 | 将 scope 改为 provided |
| 运行 NoSuchMethodError | 依赖冲突 | 用 dependency:tree 定位并排除 |
按我个人的习惯,只要搭新项目,我会先花十分钟把三件事做掉:本机 Maven 配置成阿里云镜像、IDEA 里指定用户级 settings.xml、pom.xml 里把编译版本和编码固定好。做完这三步,后面基本能少踩百分之八十的 Maven 坑。Maven 这个工具本身不难,难的是静下心把配置文件吃透,而这份功夫一次花下去,长期都是省时间的。
