写在前面
如果说 Java 开发者的一生是一部史诗,那 Maven 绝对是其中最虐心的章节之一。它像极了你的前任:刚认识时觉得"这人真不错,帮我管理一切",深入了解后才发现"卧槽这依赖地狱是什么鬼"。
但不管怎么说,在 Gradle 还没有完全一统江湖的今天,Maven 依然是大多数 Java 项目的标配。就像你可能不喜欢 996,但工资到账的那一刻,你还是会说"真香"。
本文将带你从 Maven 的基础概念一路杀到发布自己的包到中央仓库,中间穿插各种踩坑经验和最佳实践。准备好了吗?Let's 放弃!
一、Maven 是个啥?
Maven(读作 ['meɪvn],不是"马文")是 Apache 基金会的一个开源项目管理工具,主要用于:
- 依赖管理:自动下载 jar 包,告别手动复制粘贴的蛮荒时代
- 项目构建:编译、测试、打包、部署一条龙服务
- 项目信息管理:生成文档、报告等(虽然很少有人用)
用人话说就是:Maven 帮你下载别人写的代码,然后把你写的代码打包好。
依赖] --> B B --> D[可执行的 JAR/WAR] B --> E[自动化测试] B --> F[部署到服务器] style B fill:#f9f,stroke:#333,stroke-width:2px
1.1 Maven 的哲学:约定优于配置
Maven 最核心的思想是 Convention Over Configuration(约定优于配置)。简单来说就是:"我说的就是标准,你按我说的做就行。"
标准的 Maven 项目结构长这样:
my-project/
├── pom.xml # 项目的"身份证"
├── src/
│ ├── main/
│ │ ├── java/ # 你的 Java 代码
│ │ ├── resources/ # 配置文件、静态资源
│ │ └── webapp/ # Web 项目的前端资源
│ └── test/
│ ├── java/ # 测试代码
│ └── resources/ # 测试用的配置文件
└── target/ # 编译输出(Maven 生成的)为什么要这样约定? 因为这样大家写的项目结构都一样,换个公司也能立刻上手。就像全国的红绿灯都长一个样,你不需要到了新城市还要学一遍交通规则。
二、POM:Maven 的心脏
POM(Project Object Model)是 Maven 的核心配置文件。每个 Maven 项目的根目录都有一个 pom.xml,它定义了项目的一切:坐标、依赖、插件、构建配置等。
2.1 最小可运行的 pom.xml
<?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>
<!-- 项目坐标:Maven 世界的身份证号 -->
<groupId>cn.underestimated</groupId>
<artifactId>my-awesome-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>My Awesome Project</name>
<description>一个注定要被放弃的项目</description>
</project>2.2 Maven 坐标(GAV)
Maven 用三个维度来唯一标识一个项目/依赖,俗称 GAV:
| 元素 | 说明 | 例子 |
|---|---|---|
| GroupId | 组织/公司标识,通常是域名反写 | cn.underestimated |
| ArtifactId | 项目/模块名称 | payment-gateway |
| Version | 版本号 | 1.0.0-SNAPSHOT |
这三个组合起来,就像是 Maven 仓库里的 GPS 坐标,能精确定位到任何一个 jar 包。
2.3 版本号的潜规则
Maven 的版本号有一些约定俗成的规则:
- SNAPSHOT:快照版本,表示"还在开发中,随时可能变"
- RELEASE:正式发布版本(也可以不写 RELEASE 后缀)
语义化版本:
主版本.次版本.修订版本- 主版本:不兼容的 API 变更
- 次版本:向后兼容的功能新增
- 修订版本:向后兼容的 bug 修复
1.0.0-SNAPSHOT → 开发中
1.0.0-RC1 → 候选发布版
1.0.0 → 正式版
1.0.1 → 修复了一些bug
1.1.0 → 加了新功能
2.0.0 → 大改版,不向后兼容血泪教训:生产环境永远不要依赖 SNAPSHOT 版本!除非你想体验"昨天还能跑,今天就报错"的刺激。
三、依赖管理:Maven 的灵魂
依赖管理是 Maven 最核心的功能,也是让无数程序员掉头发的元凶。
3.1 添加依赖
在 pom.xml 中添加依赖非常简单:
<dependencies>
<!-- 单元测试框架 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>3.2 依赖范围(Scope)—— 重点!
Scope 定义了依赖在什么阶段可用,这是很多新手容易忽略的知识点。
默认] --> |编译+测试+运行| Z[全程参与] B[provided] --> |编译+测试| Y[运行时由容器提供] C[runtime] --> |测试+运行| X[编译时不需要] D[test] --> |仅测试| W[测试专用] E[system] --> |编译+测试| V[本地jar,不推荐] F[import] --> |dependencyManagement| U[仅用于BOM导入] end
详细解释各个 scope:
| Scope | 编译 | 测试 | 运行 | 说明 |
|---|---|---|---|---|
| compile | ✅ | ✅ | ✅ | 默认值,全程参与 |
| provided | ✅ | ✅ | ❌ | 运行时由容器提供(如 Servlet API) |
| runtime | ❌ | ✅ | ✅ | 编译时不需要,运行时才要(如 JDBC 驱动) |
| test | ❌ | ✅ | ❌ | 只在测试时使用(如 JUnit) |
| system | ✅ | ✅ | ❌ | 本地 jar,需要配合 systemPath(不推荐) |
| import | - | - | - | 仅用于 dependencyManagement 中导入 BOM |
实战案例
<dependencies>
<!-- compile(默认):这个库贯穿项目始终 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
<!-- scope 默认就是 compile,可以不写 -->
</dependency>
<!-- provided:Tomcat 会提供这个,我只是编译时用用 -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!-- runtime:MySQL 驱动,编译时不需要,运行时才加载 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- test:测试框架,只在跑测试时使用 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>为什么 JDBC 驱动是 runtime? 因为你代码里写的是 java.sql.Connection,这是 JDK 自带的接口。具体用 MySQL 还是 PostgreSQL,是运行时通过反射加载驱动决定的。编译时根本不需要知道。3.3 依赖传递与冲突
Maven 的依赖是可以传递的。你依赖 A,A 依赖 B,那你也间接依赖了 B。
问题来了:如果 A 依赖 guava:30.0,B 依赖 guava:31.0,最终用哪个版本?
Maven 的仲裁规则:
- 最短路径优先:谁离你近用谁
- 先声明优先:路径一样长的话,先在 pom 中声明的优先
解决依赖冲突的方法
方法一:排除依赖(exclusions)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
<exclusions>
<!-- 排除自带的 Tomcat,我要用 Undertow -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入 Undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<version>3.2.0</version>
</dependency>方法二:显式声明版本(锁版本)
直接在你的 pom 中声明想要的版本,覆盖传递依赖的版本:
<dependencies>
<!-- 显式声明,锁定 guava 版本 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
</dependencies>方法三:使用 dependencyManagement(推荐)
在父 pom 中统一管理版本,子模块直接引用,无需指定版本:
<!-- 父 pom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 子模块 pom -->
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<!-- 不需要写 version,从父 pom 继承 -->
</dependency>
</dependencies>3.4 BOM(Bill of Materials)
BOM 是一种特殊的 pom,只用来管理一组依赖的版本。Spring Boot 和很多大型项目都提供 BOM。
<dependencyManagement>
<dependencies>
<!-- 导入 Spring Boot 的 BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 导入 Spring Cloud 的 BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 之后引用依赖时不需要写版本号 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本由 BOM 管理 -->
</dependency>
</dependencies>四、Maven 生命周期与常用命令
Maven 定义了三套独立的生命周期:clean、default、site。
4.1 生命周期阶段
Default 生命周期详解(最常用):
| 阶段 | 说明 |
|---|---|
| validate | 验证项目是否正确 |
| compile | 编译源代码 |
| test | 运行单元测试 |
| package | 打包(jar/war) |
| verify | 运行集成测试 |
| install | 安装到本地仓库 |
| deploy | 部署到远程仓库 |
重要规则:执行后面的阶段会自动执行前面的阶段。比如 mvn package 会自动执行 validate → compile → test → package。
4.2 常用命令
# 清理 target 目录
mvn clean
# 编译
mvn compile
# 运行测试
mvn test
# 打包(跳过测试)
mvn package -DskipTests
# 打包(跳过测试编译和运行)
mvn package -Dmaven.test.skip=true
# 安装到本地仓库
mvn install
# 部署到远程仓库
mvn deploy
# 查看依赖树(排查冲突神器)
mvn dependency:tree
# 查看有效 POM(调试配置问题)
mvn help:effective-pom
# 分析依赖问题
mvn dependency:analyze
# 下载源码和文档(IDE 友好)
mvn dependency:sources
mvn dependency:resolve -Dclassifier=javadoc4.3 -DskipTests vs -Dmaven.test.skip
这俩经常被搞混:
| 参数 | 编译测试代码 | 运行测试 |
|---|---|---|
-DskipTests | ✅ | ❌ |
-Dmaven.test.skip=true | ❌ | ❌ |
一般推荐用 -DskipTests,因为如果测试代码编译都过不了,说明代码有问题。
五、插件系统
Maven 的所有功能都是通过插件实现的。即使是最基本的编译、打包,背后也是插件在干活。
5.1 常用插件配置
<build>
<plugins>
<!-- 编译插件:指定 Java 版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 打包时包含源码 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 生成 Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 打 Fat Jar(包含所有依赖)-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>cn.underestimated.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>5.2 Spring Boot 项目的打包插件
Spring Boot 项目用专门的打包插件:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>六、多模块项目
大型项目通常会拆分成多个模块,Maven 天然支持多模块管理。
6.1 项目结构
my-platform/
├── pom.xml # 父 POM
├── platform-common/ # 公共模块
│ └── pom.xml
├── platform-api/ # API 接口定义
│ └── pom.xml
├── platform-service/ # 业务逻辑
│ └── pom.xml
└── platform-web/ # Web 层
└── pom.xml6.2 父 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>cn.underestimated</groupId>
<artifactId>my-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <!-- 注意:父模块打包类型是 pom -->
<!-- 子模块列表 -->
<modules>
<module>platform-common</module>
<module>platform-api</module>
<module>platform-service</module>
<module>platform-web</module>
</modules>
<!-- 统一管理属性 -->
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
<!-- 统一管理依赖版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 内部模块版本管理 -->
<dependency>
<groupId>cn.underestimated</groupId>
<artifactId>platform-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.underestimated</groupId>
<artifactId>platform-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>6.3 子模块 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>
<!-- 继承父 POM -->
<parent>
<groupId>cn.underestimated</groupId>
<artifactId>my-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>platform-service</artifactId>
<dependencies>
<!-- 依赖内部模块,无需写版本 -->
<dependency>
<groupId>cn.underestimated</groupId>
<artifactId>platform-common</artifactId>
</dependency>
<dependency>
<groupId>cn.underestimated</groupId>
<artifactId>platform-api</artifactId>
</dependency>
</dependencies>
</project>七、发布到 Maven 中央仓库
这是很多开发者的梦想:让全世界都能用上自己写的库。下面是完整的发布流程。
7.1 准备工作
JIRA 账号] --> B[提交 Issue
申请 GroupId] B --> C[等待审核
一般1-2天] C --> D[配置 GPG
签名] D --> E[配置 pom.xml] E --> F[配置 settings.xml] F --> G[发布!]
Step 1:注册 Sonatype 账号
- 访问 https://issues.sonatype.org/
- 注册账号
- 创建 Issue,申请你的 groupId(比如
cn.underestimated)
Step 2:证明域名所有权
如果你用自己的域名作为 groupId,需要证明你拥有这个域名:
- 方式一:添加 DNS TXT 记录
- 方式二:在域名下创建一个指向 GitHub 的跳转
Step 3:安装和配置 GPG
# macOS
brew install gnupg
# Ubuntu
sudo apt-get install gnupg
# 生成密钥对
gpg --gen-key
# 查看密钥
gpg --list-keys
# 将公钥发布到公钥服务器
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID7.2 配置 pom.xml
<?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>cn.underestimated</groupId>
<artifactId>awesome-utils</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Awesome Utils</name>
<description>一个让你少写代码的工具库</description>
<url>https://github.com/yourname/awesome-utils</url>
<!-- 开源协议(必须) -->
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<!-- 开发者信息(必须) -->
<developers>
<developer>
<id>joey</id>
<name>Joey</name>
<email>joey@underestimated.cn</email>
</developer>
</developers>
<!-- SCM 信息(必须) -->
<scm>
<connection>scm:git:git://github.com/yourname/awesome-utils.git</connection>
<developerConnection>scm:git:ssh://github.com:yourname/awesome-utils.git</developerConnection>
<url>https://github.com/yourname/awesome-utils/tree/main</url>
</scm>
<!-- 发布管理 -->
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<build>
<plugins>
<!-- 源码插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Javadoc 插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- GPG 签名插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Sonatype 发布插件 -->
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</project>7.3 配置 settings.xml
在 ~/.m2/settings.xml 中配置认证信息:
<settings>
<servers>
<server>
<id>ossrh</id>
<username>你的Sonatype用户名</username>
<password>你的Sonatype密码</password>
</server>
</servers>
<profiles>
<profile>
<id>ossrh</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.executable>gpg</gpg.executable>
<gpg.passphrase>你的GPG密码</gpg.passphrase>
</properties>
</profile>
</profiles>
</settings>7.4 执行发布
# 发布 SNAPSHOT 版本
mvn clean deploy
# 发布正式版本
# 1. 修改版本号,去掉 -SNAPSHOT
# 2. 执行发布
mvn clean deploy -P release
# 或者使用 release 插件自动化
mvn release:prepare
mvn release:perform7.5 发布后的验证
发布成功后,你的库大约需要 10-30 分钟才能在 Maven Central 搜索到:
八、高级特性与骚操作
8.1 Profile:环境切换
Profile 可以让你针对不同环境使用不同的配置:
<profiles>
<!-- 开发环境 -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<env>dev</env>
<db.url>jdbc:mysql://localhost:3306/dev_db</db.url>
</properties>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<env>prod</env>
<db.url>jdbc:mysql://prod-server:3306/prod_db</db.url>
</properties>
</profile>
<!-- 跳过测试的 Profile -->
<profile>
<id>fast</id>
<properties>
<maven.test.skip>true</maven.test.skip>
</properties>
</profile>
</profiles>使用方式:
# 使用 prod profile
mvn package -P prod
# 同时激活多个 profile
mvn package -P prod,fast8.2 资源过滤
在配置文件中使用 Maven 属性:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering> <!-- 开启过滤 -->
</resource>
</resources>
</build>application.properties:
app.version=${project.version}
app.env=${env}
db.url=${db.url}Maven 打包时会自动替换这些占位符。
8.3 Optional 依赖
有时候你的库支持多种实现,但不想强制用户引入所有依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
<optional>true</optional> <!-- 可选依赖 -->
</dependency>optional=true 的依赖不会传递给使用你的库的人。如果他们需要 Jackson 功能,需要自己显式引入。
8.4 Classifier:同一个项目的多个变体
有时候需要发布同一个项目的不同变体:
<!-- 主 jar -->
<dependency>
<groupId>cn.underestimated</groupId>
<artifactId>my-lib</artifactId>
<version>1.0.0</version>
</dependency>
<!-- JDK 8 特别版 -->
<dependency>
<groupId>cn.underestimated</groupId>
<artifactId>my-lib</artifactId>
<version>1.0.0</version>
<classifier>jdk8</classifier>
</dependency>
<!-- 源码 -->
<dependency>
<groupId>cn.underestimated</groupId>
<artifactId>my-lib</artifactId>
<version>1.0.0</version>
<classifier>sources</classifier>
</dependency>8.5 使用私有仓库
公司内部通常会搭建私有 Maven 仓库(Nexus、Artifactory 等):
<repositories>
<repository>
<id>company-nexus</id>
<name>Company Internal Repository</name>
<url>https://nexus.company.com/repository/maven-public/</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>company-nexus-releases</id>
<url>https://nexus.company.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>company-nexus-snapshots</id>
<url>https://nexus.company.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>九、最佳实践
9.1 依赖管理
9.2 具体建议
1. 版本管理
<!-- ✅ 推荐:用属性集中管理版本 -->
<properties>
<spring-boot.version>3.2.0</spring-boot.version>
<guava.version>32.1.3-jre</guava.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
<!-- ❌ 不推荐:版本号散落各处 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>2. 正确使用 scope
<!-- ✅ Lombok 只在编译时用 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- ✅ 测试依赖用 test scope -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>3. 依赖检查
# 定期运行依赖分析
mvn dependency:analyze
# 检查依赖更新
mvn versions:display-dependency-updates
# 检查安全漏洞(需要 OWASP 插件)
mvn org.owasp:dependency-check-maven:check4. 加速构建
<!-- 并行构建 -->
<properties>
<maven.build.cache.enabled>true</maven.build.cache.enabled>
</properties># 使用多线程构建
mvn -T 4 clean install
# 或者根据 CPU 核心数
mvn -T 1C clean install十、常见问题排查
10.1 依赖下载失败
# 清理本地仓库中损坏的依赖
mvn dependency:purge-local-repository
# 强制更新 SNAPSHOT
mvn clean install -U
# 指定仓库下载
mvn dependency:get -DremoteRepositories=https://repo1.maven.org/maven2/ \
-DgroupId=com.google.guava \
-DartifactId=guava \
-Dversion=32.1.3-jre10.2 依赖冲突排查
# 查看完整依赖树
mvn dependency:tree
# 只看特定依赖
mvn dependency:tree -Dincludes=com.google.guava
# 输出到文件方便分析
mvn dependency:tree -DoutputFile=deps.txt10.3 Maven 版本问题
# 查看 Maven 版本
mvn -version
# 使用 Maven Wrapper(推荐)
mvn wrapper:wrapper -Dmaven=3.9.5
# 之后用 ./mvnw 代替 mvn
./mvnw clean install10.4 常见错误速查
| 错误信息 | 可能原因 | 解决方案 |
|---|---|---|
Could not resolve dependencies | 网络问题/仓库配置错误 | 检查网络,配置镜像 |
Non-resolvable parent POM | 父 POM 找不到 | 先 install 父模块 |
Package does not exist | 依赖没引入/scope 错误 | 检查依赖配置 |
Source option X is no longer supported | JDK 版本不兼容 | 修改 compiler 插件配置 |
写在最后
恭喜你读到了这里!如果你还没放弃,说明你对 Maven 的理解已经超过了 80% 的 Java 程序员。
Maven 确实不完美——配置繁琐、XML 冗长、依赖地狱令人抓狂。但在你彻底转向 Gradle 之前,理解 Maven 的工作原理是很有必要的。毕竟,大量的企业项目还在用 Maven,大量的开源库也是用 Maven 构建的。
最后送你一句话:
"Maven 虐我千百遍,我待 Maven 如初恋。因为它虽然烦人,但至少每次报错都是一样的错。"