乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 介绍与核心概念

    • Maven是什么
    • 约定优于配置
  • 第2章 安装与配置

    • 安装与验证
    • settings.xml
    • 本地仓库与镜像
  • 第3章 POM与项目坐标

    • POM
    • GAV坐标
    • packaging
  • 第4章 标准目录布局

    • 标准目录布局
  • 第5章 依赖机制

    • dependencies
    • scope
    • 依赖传递
    • 依赖冲突与调解
    • exclusions
    • optional
    • dependencyManagement
  • 第6章 仓库

    • 仓库体系
    • 本地仓库
    • 远程仓库与镜像
    • 私服
  • 第7章 构建生命周期

    • 生命周期概述
    • clean 生命周期
    • default 生命周期
    • site 生命周期
    • 生命周期与插件绑定
  • 第8章 插件

    • 插件概述
    • maven-compiler-plugin
    • maven-surefire-plugin
    • maven-war-plugin
  • 第9章 继承与聚合

    • parent继承
    • 聚合
    • BOM
    • properties
  • 第10章 属性与资源过滤

    • 资源过滤
    • Profile
  • 第11章 常用命令

    • mvn compile
    • mvn test
    • mvn package
    • mvn clean
    • mvn install
    • mvn dependency:tree
  • 第12章 常见问题与最佳实践

    • 依赖冲突排查
    • 最佳实践

生命周期与插件绑定

本章是"构建生命周期"主题的收官章,打通"生命周期阶段"与"插件目标"的绑定关系。理解默认绑定和自定义绑定的机制,是读懂 Maven 底层执行逻辑、编写自定义插件、优化构建流程的关键。


核心机制

构建阶段由插件目标组成。插件目标代表一项具体的任务,为项目的构建和管理做出贡献。解释了三者的关系:

一个目标可以绑定到零个或多个构建阶段;一个阶段可以有零个或多个目标绑定到它。如果目标没有绑定到任何阶段,它只能在生命周期之外被调用(如 mvn dependency:tree)。

生命周期阶段如何绑定插件目标

Maven 的构建执行遵循"阶段驱动、目标执行"的模型:

  1. 开发者执行 mvn <phase>(如 mvn compile)
  2. Maven 解析该阶段所属的生命周期,确定需要执行的阶段链(如 validate → compile)
  3. 对每个阶段,查找所有绑定到该阶段的插件目标
  4. 按绑定顺序执行这些插件目标

绑定关系存储在两个地方:

  • 默认绑定:Maven 核心内置,无需配置(如 compile 阶段默认绑定 maven-compiler-plugin:compile)
  • 自定义绑定:在 pom.xml 中显式声明(如将 maven-source-plugin:jar-no-fork 绑定到 package 阶段)

默认绑定关系

Maven 为常用插件和阶段建立了默认绑定,让新项目"开箱即用":

生命周期阶段默认绑定的插件目标作用
cleancleanmaven-clean-plugin:clean删除构建输出目录
defaultprocess-resourcesmaven-resources-plugin:resources复制主资源文件到 target/classes
defaultcompilemaven-compiler-plugin:compile编译主源代码
defaultprocess-test-resourcesmaven-resources-plugin:testResources复制测试资源文件
defaulttest-compilemaven-compiler-plugin:testCompile编译测试源代码
defaulttestmaven-surefire-plugin:test运行单元测试
defaultpackagemaven-jar-plugin:jar(JAR 项目)打包 JAR
defaultpackagemaven-war-plugin:war(WAR 项目)打包 WAR
defaultinstallmaven-install-plugin:install安装到本地仓库
defaultdeploymaven-deploy-plugin:deploy发布到远程仓库
sitesitemaven-site-plugin:site生成项目站点
sitesite-deploymaven-site-plugin:deploy部署站点到服务器

自定义绑定

当默认绑定不能满足需求时,可以在 pom.xml 中自定义绑定。常见场景包括:

  1. 在现有阶段附加新目标:如打包时同时生成源码 JAR
  2. 在新阶段绑定目标:如创建自定义生命周期阶段
  3. 替换默认绑定:如用 maven-scala-plugin 替代 maven-compiler-plugin

自定义绑定的配置语法:

<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>
                    <!-- 绑定到 package 阶段 -->
                    <phase>package</phase>
                    <goals>
                        <!-- 执行 jar-no-fork 目标 -->
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

上述配置将 maven-source-plugin:jar-no-fork 绑定到 package 阶段。执行 mvn package 时,除了默认的 maven-jar-plugin:jar,还会执行 maven-source-plugin:jar-no-fork,生成 *-sources.jar。

生活类比:剧场演出的节目单

想象一场剧院演出(Maven 构建):

  • 生命周期 = 演出季:春季演出季(default)、秋季清理季(clean)、冬季特展季(site)
  • 阶段 = 演出当晚的节目顺序:第一幕、第二幕、中场休息、第三幕、谢幕
  • 插件目标 = 每个节目上的表演者:第一幕默认由 A 剧团表演(默认绑定),但导演(开发者)可以换成 B 剧团(自定义绑定),或者让 A 剧团和 B 剧团同台演出(一个阶段绑定多个目标)
  • 直接调用插件 = 加演:演出季结束后,观众要求"再来一个",导演临时安排 C 剧团加演一段(mvn dependency:tree 不经过任何生命周期阶段,直接调用插件)

这个类比的关键在于:阶段是"时间槽",插件目标是"表演者"。默认绑定相当于"剧组默认安排",自定义绑定相当于"导演重新排班"。同一个时间槽(阶段)可以安排多个节目(目标),也可以替换默认的表演者。


图示

上图展示了阶段-插件-目标的绑定关系。package 阶段同时绑定了三个目标:默认的 maven-jar-plugin:jar,以及自定义的 maven-source-plugin:jar-no-fork 和 maven-javadoc-plugin:jar。执行 mvn package 时,这三个目标会按顺序执行。


完整示例

场景

飞翔科技的 employee-system 项目需要发布到 Maven 中央仓库(开源版本)。CTO 大翔要求发布的构件必须包含:

  1. 主 JAR(employee-system-1.0.0.jar)
  2. 源码 JAR(employee-system-1.0.0-sources.jar)
  3. Javadoc JAR(employee-system-1.0.0-javadoc.jar)

架构师白歌需要在 package 阶段附加额外的插件目标,同时向团队解释绑定机制。

操作前:默认绑定只生成主 JAR

默认情况下,mvn package 只执行 maven-jar-plugin:jar,生成 target/employee-system-1.0.0.jar。源码和 Javadoc 需要手动生成:

mvn source:jar      # 生成源码 JAR
mvn javadoc:jar     # 生成 Javadoc JAR

这两个命令是直接调用插件目标,不经过生命周期,容易遗漏,且无法与 CI/CD 流水线集成。

操作步骤

步骤 1:在 pom.xml 中自定义绑定

白歌在 pom.xml 的 <build> 段中添加两个插件的自定义绑定:

<build>
    <plugins>
        <!-- 默认编译插件,无需额外配置 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.10.1</version>
        </plugin>

        <!-- 自定义绑定:在 package 阶段生成源码 JAR -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.3.0</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>package</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

        <!-- 自定义绑定:在 package 阶段生成 Javadoc JAR -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>3.5.0</version>
            <executions>
                <execution>
                    <id>attach-javadocs</id>
                    <phase>package</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

配置要点:

  • <execution>:一次绑定的定义,包含 ID、阶段、目标
  • <phase>package</phase>:声明绑定到 package 阶段
  • <goals>:声明要执行的插件目标
  • jar-no-fork vs jar:jar-no-fork 不会 fork 新的 Maven 生命周期,避免与主构建冲突

步骤 2:执行 package 阶段,观察绑定效果

小崔执行:

mvn clean package

控制台输出(节选):

[INFO] --- maven-resources-plugin:3.3.0:resources (default-resources) @ employee-system ---
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ employee-system ---
[INFO] --- maven-resources-plugin:3.3.0:testResources (default-testResources) @ employee-system ---
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ employee-system ---
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ employee-system ---
[INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ employee-system ---
[INFO] Building jar: /home/xiaocui/employee-system/target/employee-system-1.0.0.jar
[INFO] --- maven-source-plugin:3.3.0:jar-no-fork (attach-sources) @ employee-system ---
[INFO] Building jar: /home/xiaocui/employee-system/target/employee-system-1.0.0-sources.jar
[INFO] --- maven-javadoc-plugin:3.5.0:jar (attach-javadocs) @ employee-system ---
[INFO] Building jar: /home/xiaocui/employee-system/target/employee-system-1.0.0-javadoc.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

步骤 3:验证输出产物

target/
├── employee-system-1.0.0.jar              # ✅ 默认绑定生成的主 JAR
├── employee-system-1.0.0-sources.jar    # ✅ 自定义绑定生成的源码 JAR
└── employee-system-1.0.0-javadoc.jar    # ✅ 自定义绑定生成的 Javadoc JAR

操作结果

变化分析:

  • 小崔理解了"默认绑定"和"自定义绑定"的区别:默认绑定无需配置,开箱即用;自定义绑定通过 <executions> 扩展构建能力
  • 黄俪明白了为什么 mvn package 的输出会随项目不同而变化——因为每个项目的 pom.xml 可能绑定了不同的插件目标
  • 李眉在 Jenkins 流水线中不再需要单独执行 mvn source:jar 和 mvn javadoc:jar,mvn clean deploy 会自动触发 package 阶段的所有绑定目标,源码和 Javadoc JAR 随主 JAR 一起发布到私服
  • 大翔确认开源版本的发布包完整,符合 Maven 中央仓库的"三件套"要求

易错点与常见问题

误区一:阶段和 goal 是一回事

错误认知:"mvn compile 和 mvn compiler:compile 效果一样,都是编译代码。"

纠正:两者有本质区别:

维度mvn compilemvn compiler:compile
调用方式调用生命周期阶段直接调用插件目标
执行范围执行 validate → compile 的所有阶段只执行 maven-compiler-plugin:compile
前置操作先验证项目、复制资源无前置操作
使用场景日常构建快速编译、调试插件

mvn compiler:compile 跳过了资源复制,如果 src/main/resources/ 里有配置文件,它们不会被复制到 target/classes/,导致运行时找不到配置。日常构建应始终使用 mvn compile。

误区二:自定义绑定会覆盖默认绑定

错误认知:"我在 package 阶段绑定了 maven-source-plugin,会不会把默认的 maven-jar-plugin 挤掉?"

纠正:自定义绑定不会覆盖默认绑定,而是追加。一个阶段可以绑定多个目标,它们按声明顺序执行。在 package 阶段,默认的 maven-jar-plugin:jar 会先执行,然后你自定义的 maven-source-plugin:jar-no-fork 和 maven-javadoc-plugin:jar 依次执行。如果你确实想替换默认绑定(如用 maven-shade-plugin 替代 maven-jar-plugin),需要显式配置 <execution> 并设置 <phase> 相同,同时调整插件优先级。

误区三:任何插件目标都可以绑定到任何阶段

错误认知:"我想把 maven-deploy-plugin:deploy 绑定到 compile 阶段,这样每次编译完就自动发布。"

纠正:技术上你可以把任何目标绑定到任何阶段,但逻辑上必须合理。deploy 目标需要 target/*.jar 作为输入,而 compile 阶段还没有生成 JAR。把 deploy 绑定到 compile 会导致构建失败。绑定阶段的选择应遵循"目标所需的输入在绑定阶段已经就绪"的原则。


小结

生命周期与插件的绑定是 Maven 构建执行的底层机制。阶段是"时间槽",插件目标是"执行者"。Maven 通过默认绑定让新项目开箱即用,通过自定义绑定让开发者扩展构建能力。理解"阶段驱动、目标执行"的模型,是精准控制构建流程、排查"为什么这个插件没运行"等问题的关键。

本章与全局的关系:本章完成了"构建生命周期"主题的闭环——从生命周期全景,到 clean、default、site 三套生命周期的阶段细节,再到阶段与插件的绑定机制。至此,你已经掌握了 Maven 构建流程的完整原理。后续章节将进入"插件"和"多模块项目"等进阶主题。

上一页
site 生命周期