搜 索

Maven从入门到放弃

  • 18阅读
  • 2022年11月13日
  • 0评论
首页 / 编程 / 正文

写在前面

如果说 Java 开发者的一生是一部史诗,那 Maven 绝对是其中最虐心的章节之一。它像极了你的前任:刚认识时觉得"这人真不错,帮我管理一切",深入了解后才发现"卧槽这依赖地狱是什么鬼"。

但不管怎么说,在 Gradle 还没有完全一统江湖的今天,Maven 依然是大多数 Java 项目的标配。就像你可能不喜欢 996,但工资到账的那一刻,你还是会说"真香"。

本文将带你从 Maven 的基础概念一路杀到发布自己的包到中央仓库,中间穿插各种踩坑经验和最佳实践。准备好了吗?Let's 放弃!


一、Maven 是个啥?

Maven(读作 ['meɪvn],不是"马文")是 Apache 基金会的一个开源项目管理工具,主要用于:

  • 依赖管理:自动下载 jar 包,告别手动复制粘贴的蛮荒时代
  • 项目构建:编译、测试、打包、部署一条龙服务
  • 项目信息管理:生成文档、报告等(虽然很少有人用)

用人话说就是:Maven 帮你下载别人写的代码,然后把你写的代码打包好

graph LR A[你写的代码] --> B[Maven] C[别人写的代码
依赖] --> 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 包。

graph TD subgraph Maven仓库 A[cn.underestimated] --> B[payment-gateway] B --> C[1.0.0] B --> D[1.0.1] B --> E[2.0.0-SNAPSHOT] end F[你的项目] -->|依赖| C

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 定义了依赖在什么阶段可用,这是很多新手容易忽略的知识点。

graph TB subgraph 依赖范围 A[compile
默认] --> |编译+测试+运行| 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。

graph LR A[你的项目] --> B[spring-boot-starter-web] B --> C[spring-boot-starter] B --> D[spring-webmvc] B --> E[spring-boot-starter-tomcat] C --> F[spring-boot] C --> G[spring-core] D --> G style A fill:#f96,stroke:#333,stroke-width:2px

问题来了:如果 A 依赖 guava:30.0,B 依赖 guava:31.0,最终用哪个版本?

Maven 的仲裁规则:

  1. 最短路径优先:谁离你近用谁
  2. 先声明优先:路径一样长的话,先在 pom 中声明的优先
graph TD A[你的项目] --> B[依赖A] A --> C[依赖B] B --> D[guava:30.0] C --> E[guava:31.0] A -.-> |路径都是2| F{冲突!} F --> |先声明优先| G[取决于A和B谁先声明]

解决依赖冲突的方法

方法一:排除依赖(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 定义了三套独立的生命周期:cleandefaultsite

4.1 生命周期阶段

graph LR subgraph Clean生命周期 A1[pre-clean] --> A2[clean] --> A3[post-clean] end subgraph Default生命周期 B1[validate] --> B2[compile] --> B3[test] --> B4[package] --> B5[verify] --> B6[install] --> B7[deploy] end subgraph Site生命周期 C1[pre-site] --> C2[site] --> C3[post-site] --> C4[site-deploy] end

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=javadoc

4.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.xml

6.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>
graph TB subgraph 多模块项目依赖关系 A[platform-web] --> B[platform-service] B --> C[platform-api] B --> D[platform-common] C --> D end E[父 POM] -.->|统一管理版本| A E -.-> B E -.-> C E -.-> D

七、发布到 Maven 中央仓库

这是很多开发者的梦想:让全世界都能用上自己写的库。下面是完整的发布流程。

7.1 准备工作

graph LR A[注册 Sonatype
JIRA 账号] --> B[提交 Issue
申请 GroupId] B --> C[等待审核
一般1-2天] C --> D[配置 GPG
签名] D --> E[配置 pom.xml] E --> F[配置 settings.xml] F --> G[发布!]

Step 1:注册 Sonatype 账号

  1. 访问 https://issues.sonatype.org/
  2. 注册账号
  3. 创建 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_ID

7.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:perform

7.5 发布后的验证

发布成功后,你的库大约需要 10-30 分钟才能在 Maven Central 搜索到:

sequenceDiagram participant Dev as 开发者 participant Local as 本地Maven participant OSSRH as Sonatype OSSRH participant Central as Maven Central Dev->>Local: mvn deploy Local->>OSSRH: 上传 jar/pom/sources/javadoc Local->>OSSRH: 上传 GPG 签名 OSSRH->>OSSRH: 自动校验 OSSRH->>Central: 同步到中央仓库 Note over Central: 10-30分钟后可搜索到

八、高级特性与骚操作

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,fast

8.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 依赖管理

mindmap root((Maven 最佳实践)) 依赖管理 使用 BOM 统一版本 定期更新依赖 避免 SNAPSHOT 上生产 用 scope 控制范围 项目结构 遵循标准目录 合理拆分模块 父 POM 管理通用配置 安全性 不要在 pom.xml 写密码 使用 settings.xml 管理凭证 定期审计依赖漏洞 CI/CD 本地构建要能在 CI 跑通 版本号自动化管理 缓存依赖加速构建

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:check

4. 加速构建

<!-- 并行构建 -->
<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-jre

10.2 依赖冲突排查

# 查看完整依赖树
mvn dependency:tree

# 只看特定依赖
mvn dependency:tree -Dincludes=com.google.guava

# 输出到文件方便分析
mvn dependency:tree -DoutputFile=deps.txt

10.3 Maven 版本问题

# 查看 Maven 版本
mvn -version

# 使用 Maven Wrapper(推荐)
mvn wrapper:wrapper -Dmaven=3.9.5

# 之后用 ./mvnw 代替 mvn
./mvnw clean install

10.4 常见错误速查

错误信息可能原因解决方案
Could not resolve dependencies网络问题/仓库配置错误检查网络,配置镜像
Non-resolvable parent POM父 POM 找不到先 install 父模块
Package does not exist依赖没引入/scope 错误检查依赖配置
Source option X is no longer supportedJDK 版本不兼容修改 compiler 插件配置

写在最后

恭喜你读到了这里!如果你还没放弃,说明你对 Maven 的理解已经超过了 80% 的 Java 程序员。

Maven 确实不完美——配置繁琐、XML 冗长、依赖地狱令人抓狂。但在你彻底转向 Gradle 之前,理解 Maven 的工作原理是很有必要的。毕竟,大量的企业项目还在用 Maven,大量的开源库也是用 Maven 构建的。

最后送你一句话:

"Maven 虐我千百遍,我待 Maven 如初恋。因为它虽然烦人,但至少每次报错都是一样的错。"
评论区
暂无评论
avatar