1. Maven核心概念:坐标与依赖、仓库、生命周期与插件、继承与聚合

各位同学,咱们今天聊聊Maven的核心概念。说实话,我刚开始用Maven的时候,也觉得这东西不就是个构建工具嘛,能有多复杂?结果真到项目里一用,才发现里面的门道不少。今天我就把这些年踩过的坑、积累的经验,一次性讲清楚。

1.1 坐标与依赖:Maven的“身份证”

Maven的坐标,说白了就是每个项目的唯一标识。就像我们每个人都有身份证号一样,Maven用groupIdartifactIdversion这三个要素来定位一个构件。

坐标三要素:

  • groupId:公司或组织的域名倒写,比如 com.example
  • artifactId:项目名称,比如 my-app
  • version:版本号,比如 1.0.0

我记得刚带团队那会儿,有个同事把groupId写成了com.example.myapp,结果私服上死活找不到依赖。后来排查了半天,才发现是坐标写错了。嗯,这里要注意:坐标必须全局唯一,否则Maven会一脸懵。

依赖管理这块,我习惯用<dependencyManagement>来统一管理版本号。你想想看,如果每个子模块都自己写版本号,升级依赖的时候得改多少个地方?

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

避坑指南:我曾经在项目中遇到过依赖冲突的问题——两个不同版本的log4j同时存在,导致运行时ClassNotFoundException。后来我养成了一个习惯:每次引入新依赖,都用mvn dependency:tree看看依赖树,确保没有版本冲突。

1.2 仓库:本地、中央、私服

Maven的仓库体系,其实就是一个“下载-缓存-共享”的机制。我个人把它分为三层:

仓库类型 位置 作用
本地仓库 ~/.m2/repository 缓存下载的依赖,避免重复下载
中央仓库 repo.maven.apache.org 官方仓库,包含绝大多数开源依赖
私服 公司内部服务器(如Nexus) 存放公司内部构件,加速下载

为什么要有私服?说白了,就是不想让每个开发人员都去中央仓库下载。你想想看,如果公司有100个开发人员,每个人都要从中央仓库下载一遍Spring Boot,那得浪费多少带宽?而且万一中央仓库挂了,整个团队都得停工。

我建议在公司内部搭建Nexus私服,配置方式很简单:

<mirrors>
    <mirror>
        <id>nexus</id>
        <mirrorOf>*</mirrorOf>
        <url>http://nexus.company.com/repository/maven-public/</url>
    </mirror>
</mirrors>

注意:我曾经遇到过一个问题:私服配置了,但某些依赖还是从中央仓库下载。后来发现是<mirrorOf>*</mirrorOf>这个通配符没写对。记住:*表示所有仓库都走镜像,central表示只镜像中央仓库。

1.3 生命周期与插件:Maven的“流水线”

Maven的生命周期,其实就是一套标准化的构建流程。我个人觉得,理解生命周期是掌握Maven的关键。Maven有三个内置生命周期:

  • clean:清理项目,删除target目录
  • default:核心构建流程,包括编译、测试、打包、部署
  • site:生成项目文档

其中default生命周期包含多个阶段,我挑几个常用的说说:

阶段 作用 常用命令
compile 编译源代码 mvn compile
test 运行单元测试 mvn test
package 打包成jar/war mvn package
install 安装到本地仓库 mvn install
deploy 部署到私服 mvn deploy

插件是Maven的“灵魂”。生命周期定义了“做什么”,插件定义了“怎么做”。比如maven-compiler-plugin负责编译,maven-surefire-plugin负责运行测试。

我的经验:我习惯在pom.xml中显式声明插件版本,而不是依赖Maven的默认版本。为什么?因为默认版本可能会随着Maven版本升级而变化,导致构建行为不一致。比如:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.10.1</version>
    <configuration>
        <source>11</source>
        <target>11</target>
    </configuration>
</plugin>

1.4 继承与聚合:Maven的“父子关系”

继承和聚合,是Maven管理多模块项目的两大法宝。我刚开始接触多模块项目时,总觉得这两个概念差不多,后来才发现它们各有各的用处。

继承:子模块从父模块继承配置。说白了,就是“爸爸说了算”。父模块定义公共的依赖版本、插件配置、属性等,子模块直接拿来用。

<!-- 父模块pom.xml -->
<parent>
    <groupId>com.example</groupId>
    <artifactId>parent-project</artifactId>
    <version>1.0.0</version>
</parent>

<!-- 子模块pom.xml -->
<parent>
    <groupId>com.example</groupId>
    <artifactId>parent-project</artifactId>
    <version>1.0.0</version>
    <relativePath>../pom.xml</relativePath>
</parent>

聚合:父模块把多个子模块组合在一起,一次性构建。你想想看,如果每个子模块都要单独执行mvn install,那得多麻烦?聚合模块可以一键构建所有子模块。

<!-- 聚合模块pom.xml -->
<modules>
    <module>module-a</module>
    <module>module-b</module>
    <module>module-c</module>
</modules>

避坑指南:我曾经在项目中遇到过一个问题:子模块的版本号没有继承父模块,导致构建出来的jar版本不一致。后来我规定:所有子模块的版本号必须从父模块继承,不允许单独声明。这样既统一了版本,又避免了人为错误。

继承和聚合可以同时使用。我习惯的做法是:创建一个父模块,既作为聚合模块(声明<modules>),又作为父模块(声明<dependencyManagement><pluginManagement>)。这样既统一了配置,又实现了批量构建。

好了,Maven的核心概念就讲到这里。下一章咱们聊聊Gradle,看看它和Maven有什么不同,以及如何在实际项目中做选择。