Maven介绍
Maven介绍
说了怎么多Maven的优势,Maven到底是什么呢?
Maven 是 Apache 软件基金会组织维护的一个项目管理和构建自动化工具,主要用于 Java 项目(也支持其他语言),核心功能是标准化项目结构、自动化构建流程、统一依赖管理。Maven这个单词的含义是专家,行家。
标准化项目结构
传统 Java 项目中,不同开发者可能会自定义目录结构(如源码放src还是java,配置文件放conf还是resources),导致团队协作时需要花时间适应彼此的结构,降低效率。
Maven 的解决方式:强制规定了标准化的项目目录结构,所有 Maven 项目都遵循统一规范
例如:
开发目录src
- 源码目录:
src/main/java - 测试代码目录:
src/test/java - 资源文件目录:
src/main/resources
输出目录target
- 类目录(源码编译后的字节码
.class文件所在目录):target/classes - 测试类目录(测试代码编译后的字节码
.class文件所在目录):target/test-classes
配置文件pom.xml
- 项目的
Maven有关配置的文件pom.xml放在项目根目录下。
开发者直接按标准开发,不要关注目录设计。
自动化构建
构建就是以我们编写的 Java 代码、框架配置文件、国际化等其他资源文件、前端页面和图片等静态资源作为“原材料”,去“生产”出一个可以运行的项目的过程。Java 项目的构建流程包括编译(.java→.class)、测试(运行单元测试)、打包(生成 JAR/WAR)、部署(发布到服务器)等步骤,传统方式需要手动执行一系列命令或工具,步骤繁琐且易出错。
Maven 的解决方式:定义标准化的构建生命周期,一键自动构建项目
- 生命周期与插件机制:Maven 定义了一套项目的构建生命周期(如
clean(清理)→compile(编译)→test(测试)→package(打包)→deploy(部署)),每个阶段由对应的插件自动完成,开发者只需执行一个命令(如mvn package),即可触发整个流程,无需手动干预。
- 清理:删除以前的编译结果,为重新编译做好准备。
- 编译:将 Java 源程序编译为字节码文件。
- 测试:针对项目中的关键点进行测试,确保项目在迭代开发过程中关键点的正确性。
- 报告:在每一次测试后以标准的格式记录和展示测试结果。
- 打包:将一个包含诸多文件的工程封装为一个压缩文件用于安装或部署。Java 工程对应 jar 包,Web 工程对应 war 包。
- 安装:在 Maven 环境下特指将打包的结果——jar 包或 war 包安装到本地仓库中。
- 部署:将打包的结果部署到远程仓库或将 war 包部署到服务器上运行。
3个独立的生命周期
Maven 的项目构建生命周期(Build Lifecycle) 是其核心特性之一,它定义了一套标准化的项目构建流程,将编译、测试、打包、部署等环节串联成一个有序的流程,开发者只需执行简单命令即可触发一系列自动化操作。
Maven 有 3 个相互独立的生命周期,每个生命周期包含一系列阶段,彼此不干扰。
-
Clean Lifecycle(清理生命周期)
用于清理项目构建过程中生成的文件(如编译后的
.class文件、打包的 JAR/WAR 等)。核心阶段(按执行顺序排列):
阶段名称 作用描述 pre-clean执行清理前的准备工作(如备份文件)。 clean删除上一次构建生成的所有文件(默认删除 target目录)。post-clean执行清理后的收尾工作(如日志记录)。 -
Default Lifecycle(默认生命周期)
最核心的生命周期,涵盖了项目构建的主要流程(编译、测试、打包、部署等),包含多达 23 个阶段。
核心阶段(按执行顺序排列):
阶段名称 作用描述 validate验证项目是否完整(如检查 pom.xml配置是否正确、依赖是否存在)。initialize初始化构建状态(如设置变量、创建目录)。 compile编译项目主源码(将 src/main/java下的.java编译为.class,输出到target/classes)。test-compile编译测试源码(将 src/test/java下的测试代码编译为.class,输出到target/test-classes)。test运行单元测试(如 Junit 测试,不要求项目已打包)。 package将编译后的代码打包为可分发格式(如 JAR、WAR,输出到 target目录)。install将打包好的文件(如 JAR)安装到本地仓库(供本地其他项目依赖)。 deploy将打包好的文件部署到远程仓库(供团队其他成员或生产环境使用)。 -
Site Lifecycle(站点生命周期)
用于生成项目文档站点(如 API 文档、测试报告、项目说明等)。
核心阶段(按执行顺序排列):
阶段名称 作用描述 pre-site生成站点前的准备工作。 site生成项目站点文档(默认输出到 target/site)。post-site生成站点后的收尾工作(如检查文档完整性)。 site-deploy将生成的站点部署到远程服务器(如公司内网文档服务器)。
生命周期的执行规则
-
阶段触发,前面全执行
当执行某个阶段时,Maven 会自动执行该生命周期中所有在它之前的阶段。
例如:
- 执行
mvn package时,会先依次执行validate→initialize→compile→test-compile→test→package。 - 执行
mvn install时,会先依次执行validate→initialize→compile→test-compile→test→package→install。
- 执行
-
生命周期独立
3 个生命周期之间相互独立,执行一个生命周期的阶段不会影响其他生命周期。
例如:
mvn clean package会先执行clean生命周期的pre-clean→clean,再执行default生命周期到package。
生命周期与插件的关系
核心概念
- 插件(Plugin):
Maven的功能载体,每个插件包含多个目标(Goal)(如maven-compiler-plugin的compile目标负责编译代码)。 - 阶段(Phase):生命周期中的步骤(如
compile、package),插件目标可以绑定到某个阶段,执行阶段时自动触发插件目标。 - 绑定方式:分为默认绑定(Maven 预定义,如
compile阶段绑定compiler:compile)和自定义绑定(通过pom.xml手动配置)。
Maven 的生命周期本身只定义 “要做什么”(如compile阶段需要编译代码),而 “具体怎么做” 由插件(Plugin) 实现。这高度体现软件的依赖抽象而非具体的原则,上层使用者用户可以自定义阶段的操作流程,如自定义打包的执行细节等。
每个阶段默认绑定一个或多个插件的目标(Goal),例如:
compile阶段默认绑定maven-compiler-plugin:compile目标(负责编译主源码)。test阶段默认绑定maven-surefire-plugin:test目标(负责运行单元测试)。package阶段默认绑定maven-jar-plugin:jar目标(负责打包 JAR)。
开发者也可以通过pom.xml自定义阶段与插件的绑定(例如给package阶段添加压缩目标)。
自定义阶段与插件的绑定
在 Maven 中,通过pom.xml自定义阶段(Phase)与插件目标(Goal)的绑定,本质是将插件的具体功能(目标)关联到生命周期的某个阶段,使得执行该阶段时自动触发插件目标。这种机制让你可以扩展 Maven 的默认构建流程(如增加代码检查、自动添加代码、静态分析、文件压缩等步骤)。
通过pom.xml的<build><plugins>配置,为指定阶段绑定插件目标,核心配置结构如下:
<project>...<build><plugins><!-- 配置一个插件 --><plugin><groupId>插件所属组织ID</groupId><artifactId>插件名称ID</artifactId><version>插件版本</version><!-- 绑定插件目标到生命周期阶段 --><executions><execution><!-- 执行ID(唯一标识,可选) --><id>自定义ID</id><!-- 绑定到哪个阶段(如compile、package、install等) --><phase>目标阶段</phase><!-- 要执行的插件目标 --><goals><goal>插件目标名称</goal></goals><!-- (可选)目标的配置参数 --><configuration><!-- 插件特定的配置项 --></configuration></execution></executions></plugin></plugins></build>...
</project>
示例 1:在package阶段前执行代码检查(使用 Checkstyle 插件)
Checkstyle 是代码规范检查工具,希望在打包前自动检查代码是否符合规范,若不通过则中断构建。
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-checkstyle-plugin</artifactId><version>3.2.0</version><executions><execution><id>checkstyle-check</id><!-- 绑定到package阶段前的verify阶段(verify在package之后,这里故意用compile演示阶段顺序) --><phase>compile</phase> <!-- 执行compile时触发检查 --><goals><goal>check</goal> <!-- Checkstyle的check目标:执行检查并报错 --></goals><configuration><!-- 指定检查规则文件(可选,默认用Sun的规范) --><configLocation>checkstyle.xml</configLocation><!-- 若检查失败,中断构建 --><failOnViolation>true</failOnViolation></configuration></execution></executions>
</plugin>
示例 2:在package阶段后自动压缩 JAR 包(使用 Assembly 插件)
希望打包(package)后,自动将 JAR 包压缩成 ZIP 格式分发。
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.4.2</version><executions><execution><id>zip-assembly</id><phase>package</phase> <!-- 绑定到package阶段,打包后执行 --><goals><goal>single</goal> <!-- Assembly的single目标:执行一次打包 --></goals><configuration><!-- 配置压缩规则(可自定义assembly.xml) --><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef> <!-- 包含依赖的JAR --></descriptorRefs><archive><manifest><mainClass>com.example.MyMain</mainClass> <!-- 若需要可执行 --></manifest></archive><outputDirectory>${project.build.directory}/zip</outputDirectory> <!-- 输出到target/zip --></configuration></execution></executions>
</plugin>
示例3:排除特定依赖,不将其打包到最终的jar包(胖jar包)
排除特定依赖的<excludes>标签是最常用的配置之一,作用是排除特定依赖,不将其打包到最终的可执行 JAR 包 中。
lombok用于在代码中自动生成构造器,get/set方法等,它一般只存在于编译阶段,运行阶段不需要,所以可以将它的依赖jar包排除在最终的jar包外。
<plugin><!-- 1. 插件坐标(唯一标识插件) --><groupId>org.springframework.boot</groupId> <!-- 插件所属组织(Spring官方) --><artifactId>spring-boot-maven-plugin</artifactId> <!-- 插件名称 --><!-- 注意:通常Spring Boot项目会通过parent继承版本,这里可省略version --><!-- 2. 插件配置(configuration) --><configuration><!-- excludes:排除不需要打包到最终JAR中的依赖 --><excludes><exclude> <!-- 具体排除的依赖坐标 --><groupId>org.projectlombok</groupId> <!-- 依赖组织ID --><artifactId>lombok</artifactId> <!-- 依赖名称 --></exclude></excludes></configuration>
</plugin>
更多在pom.xml配置插件的配置API,这里不再详述,有机会再总结。
统一依赖管理
Maven 的统一依赖管理是其核心功能之一,通过标准化的依赖声明、传递依赖机制和版本控制策略,解决了传统 Java 开发中 “依赖散乱、版本冲突、重复引入” 等问题,实现了项目依赖的集中化、自动化管理。
统一依赖管理的核心目标:
- 标准化声明:用统一格式描述依赖(坐标),替代手动下载 JAR 包的方式。
- 自动化引入:根据声明自动从仓库下载(或引用)依赖,并处理依赖的依赖(传递依赖)。
- 版本集中控制:在一个地方管理所有依赖的版本,避免版本混乱。
- 冲突自动调解:通过规则解决多依赖间的版本冲突,减少手动干预。
依赖坐标
依赖的 “坐标”:唯一标识一个依赖(Maven 通过三要素(GAV) 唯一确定一个依赖,类似 “地址”):
groupId:组织 / 公司标识(如org.springframework.boot)。artifactId:项目 / 模块标识(如spring-boot-starter-web)。version:版本号(如2.7.0)。
在pom.xml中声明依赖的格式:
<project>...<!--存放多个坐标-->
<dependencies> <!--编写一个坐标--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.0</version></dependency>
</dependencies>...
</project>
依赖传递
当项目依赖 A,而 A 又依赖 B 时,Maven 会自动将 B 也引入项目(无需手动声明 B),这就是传递依赖。
例如:
- 声明
spring-boot-starter-web(A),它会自动传递引入spring-core、tomcat-embed-core等依赖(B、C…)。
传递规则:
-
依赖范围(
scope)会影响传递(如test范围的依赖不会传递到主程序)。 -
可通过
<exclusions>排除不需要的传递依赖(如排除冲突的低版本依赖):<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 排除传递的tomcat依赖(例如用jetty替代) --><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions> </dependency>
依赖冲突解决
当项目中同一依赖出现多个版本(如 A 依赖fastjson:1.2.60,B 依赖fastjson:1.2.83),Maven 会通过冲突调解规则自动选择一个版本:
- 最短路径优先:依赖路径短的版本被选中(如直接依赖的版本优先级高于传递依赖)。
- 例:项目直接依赖
fastjson:1.2.83(路径长度 1),而 A 依赖fastjson:1.2.60(路径长度 2),则选择 1.2.83。
- 例:项目直接依赖
- 声明顺序优先:若路径长度相同,
pom.xml中先声明的依赖版本被选中。 - 手动指定版本:若自动调解不符合预期,可在
dependencyManagement中手动声明版本,强制覆盖传递依赖的版本。
依赖范围(scope):控制依赖的生效范围
scope用于指定依赖在哪些构建阶段生效(如编译、测试、运行),避免不必要的依赖引入:
scope值 | 生效阶段(编译 / 测试 / 运行) | 传递性 | 典型场景 |
|---|---|---|---|
compile | 全部生效(默认值) | 是 | 主程序和测试都需要的依赖 |
test | 仅测试阶段(如 Junit) | 否 | 单元测试依赖 |
provided | 编译和测试生效,运行时不打包 | 否 | 容器已提供的依赖(如 servlet-api,Tomcat 已包含) |
runtime | 测试和运行生效,编译不生效 | 是 | 运行时依赖(如 JDBC 驱动) |
import | 仅在dependencyManagement中生效 | 否 | 导入其他pom的依赖管理 |
示例:
<!-- Junit仅在测试阶段生效 -->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope>
</dependency>
