搞 Java 开发的朋友肯定都绕不开 Maven 这个名字。当年我刚入行的时候最头疼的就是项目里那一堆 jar 包A 依赖 BB 依赖 C版本稍微对不上跑起来就是一堆ClassNotFoundException。后来换了台电脑光是重新配环境、导依赖就能折腾一整天。Maven 就是来解决这些破事儿的它管依赖、管构建、管打包一条命令mvn clean install把编译、测试、打包全干了。这篇指南不打算给你念官方文档而是从实际干活的角度出发把 Maven 到底是什么、怎么装、怎么配、怎么用、踩过哪些坑一次说清楚。无论你是刚接触 Java 的新手还是被依赖问题折磨过的老手按着这篇走一遍基本就能上手干活了。1. 为什么项目里离不开 Maven先搞清楚它解决了什么问题1.1 没有 Maven 的时候Java 项目到底有多痛苦咱们先回到没有 Maven 的“原始社会”。那时候管理 Java 项目依赖最常见的方式就是手动下载 jar 包然后扔到项目目录下再在 IDE 里一个个“Add as Library”。听起来简单实际操作起来全是泪。第一个坑是依赖传递。你用了 SpringSpring 又依赖了 commons-loggingcommons-logging 又依赖别的包。你得手动把这一整条依赖链上的 jar 全找齐少一个都不行。第二个坑是版本冲突项目里有三个模块A 模块要用 commons-lang3 的 3.4 版本B 模块要用 3.9 版本最后到底以哪个为准全看谁先把 jar 塞进 classpath。第三个坑是构建流程编译、写测试、打 jar、部署每一步都要手动执行命令或者点 IDE 按钮步骤多了就容易漏。Maven 的出现把这几个痛点一次性解决了。它用一份pom.xml文件把项目的依赖、构建方式、插件配置全部声明清楚。你在文件里写“我要用 spring-core 5.3.20”Maven 就会自动把这个 jar 以及它依赖的所有间接 jar 全都拉下来放进本地仓库。版本冲突也有专门的机制去处理。构建更不用说mvn compile、mvn test、mvn package一条命令对应一个标准生命周期阶段不需要你记一堆杂七杂八的命令。1.2 Maven 的核心三板斧依赖管理、构建生命周期、仓库机制很多人刚接触 Maven 的时候觉得它只是个“jar 包下载器”其实只看到了冰山一角。Maven 真正值钱的是那一套生命周期模型。它把项目的整个构建过程抽象成几个固定阶段validate、compile、test、package、verify、install、deploy。你执行mvn install的时候Maven 会自动按照顺序执行它之前的所有阶段不需要你手动先编译再测试再打包。这套模型的直接好处是——标准化。不管项目是谁写的用什么框架只要是用 Maven 管理的构建方式就是那一条命令。新同事接手项目不用问“你们项目怎么启动、怎么打包”先看pom.xml和仓库里的 README 就够了。仓库机制则是 Maven 的“物流系统”。Maven 有三种仓库本地仓库默认在用户目录下的.m2/repository、中央仓库Maven Central、远程仓库可以是公司私服也可以是阿里云镜像。依赖查找顺序是本地仓库 → 中央仓库 → 远程仓库。理解了这个顺序后面很多问题排查就有方向了。2. Maven 安装与核心配置从零到能跑通第一个项目2.1 下载安装与环境变量配置别在这步翻车Maven 本身是个 Java 工具所以第一步是确保机器上已经装好了 JDK。注意Maven 3.3 要求 JDK 1.7 及以上Maven 3.9 建议 JDK 8 以上。如果你还在用 JDK 8推荐装 Maven 3.6.x 或 3.8.x如果用 JDK 17那可以直接上 Maven 3.9.x。这个搭配问题虽然小但经常有人忽略了导致各种诡异报错。下载地址去 Maven 官网的下载页面选apache-maven-3.9.x-bin.tar.gzLinux/macOS或apache-maven-3.9.x-bin.zipWindows。下载之后建议放在一个专门的目录比如 Windows 下的D:\tools\mavenmacOS 下的~/tools/maven。不要图省事直接解压到桌面后面配置环境变量的时候会很难受。环境变量配置这一步是新手最容易卡住的地方。Windows 上新增系统变量MAVEN_HOME值就是解压目录比如D:\tools\maven\apache-maven-3.9.6然后把%MAVEN_HOME%\bin添加到Path变量里。配置完一定要新开一个终端窗口才能生效旧窗口里敲mvn -v大概率还是“命令不存在”。macOS/Linux 则在~/.bashrc或~/.zshrc里加两行export MAVEN_HOME/Users/yourname/tools/maven/apache-maven-3.9.6 export PATH$MAVEN_HOME/bin:$PATH配置完成后再新开终端跑mvn -v能看到 Maven 版本和 Java 版本信息才算环境配置成功。注意看一下输出的 Java 版本如果和你期望的不一致说明 IDE 或者终端里的 JDK 被其他工具改过了这类“环境变量配置错误”的坑后面排查很耗时间。2.2 settings.xml 详解本地仓库、阿里云镜像、JDK 版本一次配好Maven 装好只是第一步真正决定体验的是conf/settings.xml这个配置文件。每个 Maven 用户都应该认真看一遍这几个配置项因为它们直接影响你日常开发的速度和稳定性。第一个是localRepository。默认本地仓库路径是~/.m2/repository如果你系统盘空间紧张或者想统一管理可以改到其他盘。我习惯改成D:/maven/repository好处是一旦重装系统这个目录不用动所有依赖都还在省去重新拉取的时间。第二个是镜像配置。由于中央仓库服务器在国外国内访问速度很慢直接使用默认配置下载依赖能等到怀疑人生。解决办法是配置阿里云镜像。在settings.xml的mirrors节点里加mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirrormirrorOf*/mirrorOf表示所有请求都走这个镜像。如果你公司有私服通常不会用通配符而是只镜像特定仓库比如mirrorOfcentral/mirrorOf只代理中央仓库其他仓库请求仍然走公司私服。这个配置是“Maven 配置多个镜像仓库”时最常被问到的点后面我单独讲。第三个是profiles里的 JDK 版本配置。很多老项目依赖编译版本是 Java 8但电脑上装了 JDK 17会导致编译报错。可以在 settings.xml 里加一个 profile把maven.compiler.source和maven.compiler.target默认设成 8。不过更稳妥的做法是在项目的pom.xml里显式指定避免这台机器配了、那台机器没配导致的“环境不一致”。2.3 一个隐藏但很重要的问题.m2 目录下没有 settings.xml 怎么办很多人在网上翻教程看到说“去 .m2 目录下改 settings.xml”结果打开发现这个目录要么不存在要么只有一个 repository 文件夹根本没有 settings.xml。这其实很正常Maven 的全局配置在安装目录的conf/settings.xml而用户级配置默认不创建需要手动从安装目录复制一份过去。推荐的做法是把conf/settings.xml复制到~/.m2/settings.xml。理由很简单Maven 升级时全局配置文件会被覆盖而用户级配置不会而且 IDE比如 IDEA默认读取的是用户级配置你改了全局配置IDE 不一定生效。复制过去之后再修改localRepository和镜像配置就一劳永逸了。3. 第一个 Maven 项目实战pom.xml 与常用命令3.1 手写第一个 pom.xml坐标、依赖、插件逐个说明很多新手一上来就喜欢用 IDE 的模板创建 Maven 项目这没问题但也导致很多人根本不知道 pom.xml 里写了什么。这里我建议你至少手写一次。新建一个文件夹在里面建一个最简单的pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdhello-maven/artifactId version1.0.0/version packagingjar/packaging properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties /projectgroupId、artifactId、version合在一起就是 Maven 的坐标相当于这个项目的“身份证号”。groupId一般是公司域名倒写artifactId是项目名version是版本号。你在项目里依赖别人的库就是在 pom.xml 里声明这三个字段。packaging默认是 jar也可以是 war 或者 pom。然后在src/main/java/com/example下建一个HelloWorld.java再执行mvn compile。第一次执行时你会看到 Maven 下载一堆插件这个过程可能有点慢因为编译器插件本身也要从仓库拉。编译成功后target/classes目录下就会出现编译好的 class 文件。这就是 Maven 最基本的构建流程。3.2 常用命令与生命周期从 clean 到 install 到底发生了什么Maven 的常用命令其实就那几个但很多人不知道它们之间的关系。这里直接上一张最实用的命令速查表命令作用典型使用场景mvn clean删除 target 目录清理旧构建产物mvn compile编译主代码到 target/classes快速检查代码能否编译通过mvn test执行 src/test/java 下的测试用例跑单元测试mvn package打包 jar/war 到 target 目录生成可交付产物mvn install打包并安装到本地仓库供本地其他项目引用mvn deploy上传到远程仓库/私服发布到团队共享仓库mvn clean install清理后执行完整构建最常用的构建命令这里重点说mvn clean install。它并不是“先 clean 再 install”这么简单因为 Maven 的生命周期保证了 install 阶段会依次执行 validate、compile、test、package也就是说 clean 之后会重新编译、测试、打包再安装到本地仓库。日常开发中最实用的就是这个命令改完代码、想确认整体没问题直接mvn clean install -DskipTests跳过测试快速构建。另外有两个常用参数必须知道。-DskipTests是跳过测试代码的编译-Dmaven.test.skiptrue是连测试代码都编译。两者的区别很微妙但实际用起来差别很大。如果测试类里有语法错误用-DskipTests照样会失败用-Dmaven.test.skiptrue则完全跳过编译测试代码这时候才能构建成功。3.3 依赖管理入门scope 的四个常用值必须掌握pom.xml 里最常写的就是dependency但这个标签里的scope很多人不明其意。scope 决定了依赖在哪些阶段生效、会不会被打进最终产物。最常见的四个 scopecompile默认编译、测试、运行都需要会打入最终 jar/war。比如 Spring、commons-lang 这些核心库。provided编译和测试时需要但运行时由容器提供打包时不包含。典型例子是 servlet-apiTomcat 里自带你要是打进了 war 包里反而可能冲突。runtime编译时不需要运行时需要。典型例子是 MySQL 驱动代码里只面向 JDBC 接口编程运行期才需要真正的驱动实现。test只在测试阶段使用不会打包进产物。典型例子是 JUnit。把这些搞清楚你的产物体积能缩小不少也能避免很多“打包后运行报错但在 IDE 里跑得好好的”的灵异问题。有个非常经典的坑就是把 MySQL 驱动配成compile然后让 war 包部署到 Tomcat结果因为驱动 jar 重复加载导致数据源初始化异常。这类问题一旦排查起来scope 往往是第一个要检查的对象。4. Maven 实战进阶IDEA 集成、多模块项目与镜像配置4.1 IDEA 里配置 Maven别再为每个项目手动导 jar 了现在绝大多数 Java 开发者都用 IntelliJ IDEA但很多人在 IDEA 里用的还是旧时代的“右键 Add as Library”操作方式。其实 IDEA 对 Maven 的支持非常成熟只要配置对了新建项目、管理依赖、打包发布都能在 IDE 里完成。IDEA 的 Maven 配置在Settings → Build, Execution, Deployment → Build Tools → Maven。重点看三个地方Maven home path指向你解压的 Maven 目录、User settings file指向~/.m2/settings.xml、Local repository自动读取 settings.xml 里的配置不需要手动填。很多 IDEA 的 Maven 问题比如依赖下载失败、下载的 jar 版本不对多半就是这里指向了 IDEA 自带的 Maven 而没指向你配置过镜像的那个 Maven。项目导入方面IDEA 可以直接打开带pom.xml的文件夹它会自动识别为 Maven 项目。右侧栏会出现 Maven 工具窗口里面能看到 Lifecycle、Dependencies、Plugins 列表。双击 Lifecycle 里的clean、install效果跟命令行一样。如果在命令行里能跑通但 IDEA 里报错先检查 IDEA 的 JDK 设置Project Structure → Project SDK很多时候 IDEA 默认用了自带的 JDK 版本和命令行环境不一致构建结果自然对不上。4.2 多模块项目父 pom、子模块与模块间依赖实际工作中的项目很少是单模块的基本都是多模块结构。比如一个电商项目可能会有common公共工具类、dal数据访问层、service业务逻辑层、web接口层这几个模块。模块间有依赖关系web 依赖 serviceservice 依赖 dal 和 common。多模块项目的核心是一个父 pom 加上若干子模块。父 pom 的关键配置是packagingpom/packaging和modules标签groupIdcom.example/groupId artifactIdshop-parent/artifactId version1.0.0/version packagingpom/packaging modules moduleshop-common/module moduleshop-dal/module moduleshop-service/module moduleshop-web/module /modules父 pom 里还可以用dependencyManagement统一管理依赖版本。子模块里写依赖时就不需要写version了这样全项目都用同一个版本彻底避免版本冲突。这是 Maven 的“依赖锁定”机制比在多个子模块里各写各的版本要靠谱得多。模块间引用非常简单子模块 A 要在它的 pom.xml 里声明依赖子模块 B把 B 当作普通的依赖即可。构建顺序不用担心Maven 会根据依赖关系自动计算 reactor 的构建顺序你只需要在父 pom 目录下执行mvn installMaven 会按依赖关系从前到后逐个构建。这里注意多模块构建建议用install而不是package因为各个模块之间依赖的是本地仓库里的 jar只有 install 才会把模块产物装进本地仓库其他模块才能引用到。4.3 配置多个镜像仓库阿里云为主、私服为辅的正确姿势前面提到过mirrorOf 通配符的问题这里展开讲一下多个镜像仓库的配置。很多公司内部有私服里面放着不对外发布的内部组件同时对外网的中央仓库访问很慢想用阿里云镜像加速。这时候简单的mirrorOf通配符就不够用了需要更精细的配置。推荐的做法是使用repositories而不是简单粗暴地配置 mirror。在 pom.xml 的repositories节点里显式声明仓库地址包括仓库 id、url 和 release/snapshot 策略repositories repository idcentral/id urlhttps://maven.aliyun.com/repository/central/url /repository repository idcompany-nexus/id urlhttp://nexus.company.com/repository/maven-public//url /repository /repositories这里的关键是一个依赖到底从哪个仓库下载取决于这个仓库是否已经包含该依赖的元数据。Maven 会依次检查各个仓库。如果公司私服有内部组件但公共依赖又多最简单的方案是在私服上配置仓库代理和镜像聚合让私服从阿里云拉公共依赖。这样开发者只需要配置一个私服地址就够了。这是在团队开发里更常见、也更省心的方案。如果你是自己一个人开发没有私服那直接用阿里云镜像就够了不必纠结多仓库。4.4 实战命令行构建一个可运行的 Spring Boot 项目直接讲理论知识太干这里给一个最小可运行的 Spring Boot 项目从零到一键打包的完整过程。首先在 pom.xml 里引入 Spring Boot 父依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent然后加一个 web 依赖和构建插件dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build创建一个启动类package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; SpringBootApplication RestController public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } GetMapping(/hello) public String hello() { return Hello Maven; } }在项目根目录执行mvn clean package -Dmaven.test.skiptrue然后java -jar target/demo-0.0.1-SNAPSHOT.jar浏览器访问http://localhost:8080/hello就能看到返回的字符串。这个过程中最值得说的坑是Spring Boot 打包出来的 jar 和普通 jar 不一样它是个 fat jar里面把 tomcat 内嵌运行时都打进了一个 jar 文件里。如果直接mvn package后出来的 jar 运行时报no main manifest attribute通常是因为没有配置spring-boot-maven-pluginIDEA 的 Maven 窗口里重新执行一下package就能解决。还有一个小细节是打包后 target 目录下会有两个 jar一个以.jar.original结尾一个正常.jar正常那个才是 Spring Boot 的 fat jar。5. 常见问题与排查技巧实录那些年踩过的 Maven 坑5.1 本地仓库明明有包却报“找不到依赖”三个排查方向这是搜索热词里非常靠前的问题Maven 本地仓库明明有 jar 包但是项目引用时就是报缺失依赖。我在工作中也频繁遇到先别急着删.m2/repository按这三个方向排查。第一个方向是仓库坐标不一致。本地仓库里的目录结构是按 groupId/artifactId/version 组织的比如com/example/my-lib/1.0.0/my-lib-1.0.0.jar。如果你看到_remote.repositories文件记录了这个 jar 是从哪个仓库下载的而 Maven 当前访问的仓库列表里又不包含那个仓库它可能“认定”这个 jar 不可用。解决办法是删除对应目录下的_remote.repositories文件再重新构建。第二个方向是 IDEA 缓存没刷新。IDEA 里有 Maven 的索引缓存新加的依赖不一定马上出现在右边的 Dependencies 列表里。遇到这种情况在 IDEA 的 Maven 工具窗口点一下刷新按钮圆形箭头或者File → Invalidate Caches清一下缓存。很多时候“本地有包但引不进来”就是这个原因。第三个方向是lastUpdated文件作祟。依赖下载失败时 Maven 会在本地仓库生成.lastUpdated结尾的文件标识这个版本的依赖下载失败过。即使之后网络恢复了Maven 在短时间内仍然认为这个版本不可用。解决方法是找到对应目录删除所有*.lastUpdated文件或者加上-U参数强制更新快照。5.2 编译报错找不到类 com.sun.image.codec.jpeg.JPEGCodec 怎么解决这是一个非常经典的老项目问题。这个类属于 JDK 内部实现JDK 8 及之前版本在rt.jar里能直接找到JDK 9 开始模块化之后这个内部 API 就被移除了不再对外开放。所以老项目从 JDK 8 迁移到高版本 JDK 时编译阶段报找不到这个类就成了家常便饭。解决方案不是去网上找 jar 包硬塞进依赖这在现代 JDK 上根本行不通。真正的解决思路是把这个老式 API 的调用代码改成现代替代方案。JPEGCodec主要是用来做 JPEG 编码的替代品是ImageIO类// 旧写法 JPEGEncodeParam param JPEGCodec.getDefaultJPEGEncodeParam(img); // 新写法 ImageIO.write(img, jpeg, outputStream);如果项目实在改不了代码退一步可以考虑用--add-exports参数强行开放 JDK 内部包但这不是长久之计。更现实的方案是让项目继续用 JDK 8 编译然后把运行时环境升级到高版本这就涉及容器和 JDK 分发的问题了。实际上很多老项目停留在 JDK 8 上很大程度上就是因为这种历史 API 的迁移成本太高。如果有人硬要你用 JDK 17 编一个十年前的老项目先看看代码里有没有这类内部 API 的调用。5.3 依赖版本冲突NoSuchMethodError 和 ClassCastException 的幕后黑手运行期出现NoSuchMethodError或者ClassCastException十有八九是依赖版本冲突。Maven 虽然会自动做依赖仲裁但规则比较无脑路径最短者优先路径相同的情况下先声明者优先。这也就是说如果你直接依赖了 A 1.0而另一个模块间接依赖了 A 2.0Maven 可能选择了 A 1.0但代码里调用了只有 A 2.0 才有的方法运行时就炸了。排查这类问题mvn dependency:tree是最有用的命令。它会打印出完整的依赖树你可以直观地看到某个依赖到底被引用了哪些版本冲突是如何被仲裁的。想要强制排除某个传递依赖可以在对应的 dependency 里用exclusionsdependency groupIdcom.example/groupId artifactIdsome-lib/artifactId version1.0/version exclusions exclusion groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId /exclusion /exclusions /dependency搞定了依赖仲裁的规则遇到奇怪的运行期异常时你就知道该去查dependency:tree而不是对着堆栈发呆。5.4 常见问题速查表问题现象可能原因快速处理下载依赖极慢未配置国内镜像settings.xml 配置阿里云镜像依赖下载失败报 lastUpdated网络中断或仓库不可达删除 .lastUpdated 文件后加 -UIDEA 里依赖列表不更新IDEA Maven 缓存刷新 Maven 工程或 Invalidate Caches打包后运行报 no main manifest缺少 spring-boot-maven-plugin添加打包插件后重新 package多模块构建报找不到依赖模块模块未 install 到本地仓库在父目录执行 mvn install编译报错 JDK 版本不支持IDE 使用了错误的 JDK检查 Project Structure 里的 SDK5.5 命令行效率技巧把这几个 alias 存进你的 shell最后分享一个提升日常效率的小经验。开发过程中最常用的是mvn clean install -DskipTests但每次敲这么长一串确实麻烦。我在~/.zshrc里存了几个 alias你们如果也习惯命令行操作可以直接抄走alias mcmvn clean alias mcimvn clean install -DskipTests alias mcitmvn clean install alias mvtmvn dependency:tree alias mpmvn package -Dmaven.test.skiptrue还有一个小技巧是在构建速度方面Maven 默认是单线程的如果你的机器核数足够并且项目模块之间没有复杂依赖可以试试并行构建mvn -T 4 clean install这个参数会让 Maven 用 4 个线程并行构建多模块项目场景下提速非常明显。但要注意如果模块之间的依赖关系交错复杂并行构建反而可能因为模块等待而变慢。实际用的时候可以先在几个项目里试一下对比构建时间再决定要不要长期使用。我个人在实际操作中的体会是Maven 这东西真不是光看文档就能学会的。它那种“先构建、再报错、再排查”的循环本身就是最好的学习路径。配置方面记得把settings.xml放进自己的 dotfiles 仓库里换电脑的时候直接拉下来复制省得每次配镜像配到手麻。最后再提醒一句遇到依赖问题先不要急着删本地仓库先看清楚是什么原因触发的删仓库是终极手段也是耗时最猛的手段不到万不得已别用。
阅读完成 · 觉得有帮助?