1. Maven核心概念:坐标与依赖、仓库、生命周期与插件、继承与聚合
各位同学,咱们今天聊聊Maven的核心概念。说实话,我刚开始用Maven的时候,也觉得这东西不就是个构建工具嘛,能有多复杂?结果真到项目里一用,才发现里面的门道不少。今天我就把这些年踩过的坑、积累的经验,一次性讲清楚。
1.1 坐标与依赖:Maven的“身份证”
Maven的坐标,说白了就是每个项目的唯一标识。就像我们每个人都有身份证号一样,Maven用groupId、artifactId、version这三个要素来定位一个构件。
坐标三要素:
- 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有什么不同,以及如何在实际项目中做选择。