maven添加打包插件 maven打包插件原理_maven添加打包插件

Maven 简介

Maven 是一种声明式项目管理工具,通过在 POM 中配置 "who","what","where"等信息,即可满足编译、测试、打包、发布等项目构建需求。声明式的好处是,用户无需关心构建工具的实现细节,只需在 pom.xml 中配置好项目名,依赖等基础信息即可。坏处是,实现自定义的构建逻辑,相对复杂。(Maven 也提供了插件,如:maven-antrun-plugin,来运行用户自定义脚本。当然,插件最终 apply 到 Maven 的方式最终仍然是声明式的,即需要在 POM 中声明插件运行时机和插件相关配置。)

相对应地,Make 和 Ant 等构建工具是过程式项目管理工具,用户需要编写构建脚本并组织各脚本的依赖关系。过程式项目管理工具好处是,用户自由度很大;坏处是,项目管理经验无法复用,构建脚本编写较为复杂。

maven添加打包插件 maven打包插件原理_maven打包跳过test_02

POM

基本概念

POM 文件是Maven的核心文件,包含项目构建相关的所有配置信息,如:项目源代码目录,class 文件输出目录等。Maven 执行 goal 时,会首先读取当前目录的 POM 文件,然后执行对应 goal。

Super POM

Super POM 是 Maven 的默认 POM。除显式声明以外,所有的POM都继承自Super POM. Super POM 中配置了默认的仓库地址,基本的 Plugin 和源代码路径等配置。Super POM 内容如下:

<project>  <modelVersion>4.0.0modelVersion>   <repositories>  repositories>  <pluginRepositories>    <pluginRepository>    pluginRepository>  pluginRepositories>   <build>    <directory>${project.basedir}/targetdirectory>    <outputDirectory>${project.build.directory}/classesoutputDirectory>    <finalName>${project.artifactId}-${project.version}finalName>    <testOutputDirectory>${project.build.directory}/test-classestestOutputDirectory>    <sourceDirectory>${project.basedir}/src/main/javasourceDirectory>    <scriptSourceDirectory>${project.basedir}/src/main/scriptsscriptSourceDirectory>    <testSourceDirectory>${project.basedir}/src/test/javatestSourceDirectory>    <resources>      <resource>        <directory>${project.basedir}/src/main/resourcesdirectory>      resource>    resources>    <testResources>      <testResource>        <directory>${project.basedir}/src/test/resourcesdirectory>      testResource>    testResources>    <pluginManagement>                  <plugins>      plugins>    pluginManagement>  build>project>

Project Inheritance VS Project Aggregation

  • Project Inheritance: 继承已有pom.xml,便于做公共配置管理。Super POM 是 Project Inheritance 的典型例子。
  • Project Aggregation: 同一项目中存在多个module。

下图展示了 Presto 的 POM 结构,Presto 项目中既有项目继承(继承自io.airlift:airbase:80)也有项目聚合(多个模块)。

maven添加打包插件 maven打包插件原理_maven打包跳过test_03

Lifecycle

Maven的生命周期就是为了对所有的构建过程进行抽象和统一。

Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。

Maven 生命周期包含项目清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。

Lifecycle 是抽象的概念,生命周期本身不做任何实际工作。

Maven 设计中,实际工作都交由插件完成。

Lifecycles VS Phases

Maven 有三套独立的 Lifecycle:default、clean 和 site,每个 Lifecycle 包含多个 Phase。

下图详细的展示了Lifecycle 和 Phase的关系。

maven添加打包插件 maven打包插件原理_maven添加打包插件_04

Default

default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分,其包含的阶段如下:

  • validate:验证项目是否合法以及项目构建信息是否完备。
  • initialize:初始化。
  • generate-sources: 生成源代码,如:ANTLR 插件会根据语法文件生成对应的 Java 源代码。
    process-sources: 处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
  • generate-resources:生成资源文件。
  • process-resources:处理资源文件。
  • compile:编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
  • process-classes:处理class文件,如:字节码增强。
  • generate-test-sources:生成测试源代码,如:ANTLR。
  • process-test-sources:处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
  • generate-test-resources:生成测试资源文件。
  • process-test-resources:拷贝或处理测试资源文件至目标测试目录。
  • test-compile:编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
  • process-test-classes:处理 test class文件。
  • test:使用单元测试框架运行测试,测试代码不会被打包或部署。
  • prepare-package:打包前置工作。
  • package:接受编译好的代码,打包成可发布的格式,如JAR,WAR等。
  • pre-integration-test:集成测试的前置工作
  • integration-test:集成测试。
  • post-integration-test :集成测试后,需要做的一些事情。
  • verify:检测所有的集成测试结果是否符合预期,保障代码质量。
  • install:将包安装到Maven本地仓库,供本地其他Maven项目使用。
  • deploy:将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。

Clean

clean 生命周期的目的主要是清理项目。

  • pre-clean:执行一些清理前需要完成的工作。
  • clean:清理上一次构建生成的文件。
  • post-clean:执行一些清理后需要完成的工作。

Site

site 生命周期用于生成代码站点文档并发布至对应Web Server。

  • pre-site:前置工作。
  • site:生成代码对应的站点文档。
  • post-site:site后置工作,deploy 前置工作。
  • site-deploy:发布站点文档至对应的Web Server。

Plugins

一个 Plugin 包含多个goal,来完成项目构建的实际工作,如:Compiler plugin 有两个goal compile 和 testCompile,分别用于编译main代码与编译test代码。下图是default生命周期的插件内置绑定与具体goal的绑定关系:

maven添加打包插件 maven打包插件原理_使用maven的好处_05

常用 Plugin

maven-shade-plugin

maven-shade-plugin 是一个很强大的 Maven 插件,可以用来relocate 包名,解决依赖冲突问题;也可以生成一个可执行Jar包(又称 Uber Jar)。下面我们就简单介绍下这两个功能使用方法。

Relocation

我们以shade hive-exec 中的guava包进行说明。(hive-exec 会依赖calcite的代码,calcite代码也会依赖guava包)。

shaded-exec pom.xml:

maven添加打包插件 maven打包插件原理_maven deploy plugin_06

shaded-exec 中 calcite 代码:

maven添加打包插件 maven打包插件原理_maven deploy plugin_07

结论:maven-shade-plugin 会对满足对应pattern的所有class文件进行relocate,不会区分该class文件是否是本项目代码编译产生。

Executable Jar

maven添加打包插件 maven打包插件原理_maven deploy plugin_08

maven添加打包插件 maven打包插件原理_使用maven的好处_09

结论:默认情况下,maven-shade-plugin 产生的 shaded jar,包含当前项目class文件以及compile依赖。这也是为什么有时候可以通过修改依赖的scope,即可影响JAR内容的。然而,如果项目中仅使用了默认的jar plugin,那么修改依赖scope,将不会影响输出的Jar的内容了,里面将永远只包含本项目的class。

maven-antrun-plugin

maven-antrun-plugin 使Maven可以运行我们自定义的脚本,灵活控制构建过程。

如下所示 pom.xml 就通过 maven-antrun-plugin 在 compile 阶段打印出 Maven 的四类class path,帮助我们定位编译问题。

<project>  <build>    <plugins>      <plugin>        <groupId>org.apache.maven.pluginsgroupId>        <artifactId>maven-antrun-pluginartifactId>        <version>3.0.0version>        <executions>          <execution>            <id>compileid>            <phase>compilephase>            <configuration>              <target>                <property name="compile_classpath" refid="maven.compile.classpath"/>                <property name="runtime_classpath" refid="maven.runtime.classpath"/>                <property name="test_classpath" refid="maven.test.classpath"/>                <property name="plugin_classpath" refid="maven.plugin.classpath"/>                <echo message="compile classpath: ${compile_classpath}"/>                <echo message="runtime classpath: ${runtime_classpath}"/>                <echo message="test classpath:    ${test_classpath}"/>                <echo message="plugin classpath:  ${plugin_classpath}"/>              target>            configuration>            <goals>              <goal>rungoal>            goals>          execution>        executions>      plugin>    plugins>  build>project>

PS:当插件目标被绑定到不同的生命周期阶段的时候,其执行顺序会由生命周期阶段的先后顺序决定。如果多个目标被绑定到同一个阶段,它们的执行顺序会是怎样?

答:当多个插件目标绑定到同一个阶段的时候,这些插件声明的先后顺序决定了目标的执行顺序。

Dependency Mechanism

Maven 作为一个项目管理工具,依赖管理是必不可少的。

Maven 依赖坐标

Maven 坐标为各种构件引入了秩序,任何一个构件都必须明确定义自己的坐标,而一组Maven坐标是通过一些元素定义的。Maven 坐标一般可以认为是一个五元组,即:(groupId,artifactId,version,type,classifier)。下面进行详细说明:

  • groupId:Maven 项目隶属的实际项目名称。
  • artifactId:实际项目中的一个模块名称。
  • version:版本。
  • type:
     –jar: jar包,包含依赖项目主代码 class文件。
     –test-jar:jar包,classifier 为 tests,包含依赖项目测试代码 class 文件。用于复用测试代码。
  • classifier: 用于区分从同一个POM中,构建出的不同 artifacts。比如:同一个项目可能同时提供 jdk11 和 jdk 8 对应的依赖,同一个项目可能同时提供shaded 和 un-shaded 的依赖版本等。

上述五个元素中,groupId、artifactId、version是必须定义的,type是可选的(默认为jar),而classifier是取决于对应依赖是否提供。

<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">  <dependencies>    <dependency>      <groupId>junitgroupId>      <artifactId>junitartifactId>      <version>4.12version>      <type>jartype>      <scope>testscope>      <optional>trueoptional>    dependency>    <dependency>    <groupId>mygroupgroupId>    <artifactId>myjarartifactId>    <version>1.0version>    <classifier>jdk11classifier>    dependency>    <dependency>      <groupId>org.apache.hivegroupId>      <artifactId>hive-commonartifactId>      <version>1.0version>      <scope>testscope>      <type>test-jartype>    dependency>  dependencies>project>

依赖 Scope

依赖Scope作用有两个:

1. 限制依赖传递

2. 控制依赖是否出现在各个classpath中

Maven 中有五种依赖scope,分别是:compile,provided,runtime,test和system。

下面是引用自Maven官网的说明:

maven添加打包插件 maven打包插件原理_maven deploy plugin_10

Maven中大致可以分成四类class path:

  • main 代码编译classpath:编译main代码的classpath
  • main 代码运行classpath:运行main代码的classpath
  • test 代码编译classpath:编译测试代码的classpath
  • test 代码运行classpath:运行测试代码的classpath

结合依赖的scope和四类classpath,总结出 classpath 与 依赖的关系,如下图:

maven添加打包插件 maven打包插件原理_maven打包跳过test_11

依赖传递

  • A -> B (compile) 第一关系 : A 依赖 B compile
  • B -> C (compile) 第二关系 : B 依赖 C compile

当在A中配置:  

<dependency>              <groupId>com.BgroupId>              <artifactId>BartifactId>              <version>1.0version>  dependency>

则会自动导入 C 包, 详细的关系传递如下表 :

maven添加打包插件 maven打包插件原理_maven添加打包插件_12

依赖调节

依赖调解遵循以下两大原则:路径最短优先、声明顺序优先。

  • 第一原则:路径最近者优先。把当前模块当作顶层模块,直接依赖的包则作为次层模块,间接依赖的包则作为次层模块的次层模块,依次递推...,最后构成一棵引用依赖树。假设当前模块是A,两种依赖路径如下所示:
A --> B --> X(1.1)         // dist(A->X) = 2A --> C --> D --> X(1.0)   // dist(A->X) = 3

此时,Maven可以按照第一原则自动调解依赖,结果是使用X(1.1)作为依赖。

  • 第二原则:第一声明者优先。若冲突依赖的路径长度相同,那么第一原则就无法起作用了。假设当前模块是A,两种依赖路径如下所示:
A --> B --> X(1.1)   // dist(A->X) = 2 A --> C --> X(1.0)   // dist(A->X) = 2

当路径长度相同,则需要根据A直接依赖包在pom文件中的先后顺序来判定使用那条依赖路径,如果次级模块相同则向下级模块推,直至可以判断先后位置为止。

<dependencies>    ...    dependency B    ...    dependency Cdependencies>

假设依赖B位置在依赖C之前,则最终会选择X(1.1)依赖。

  • 其它情况:覆盖策略。若相同类型但版本不同的依赖存在于同一个pom文件,依赖调解两大原则都不起作用,需要采用覆盖策略来调解依赖冲突,最终会引入最后一个声明的依赖。
<dependencies>  <dependency>    <groupId>commons-cligroupId>    <artifactId>commons-cliartifactId>    <version>1.2version>  dependency>  <dependency>    <groupId>commons-cligroupId>    <artifactId>commons-cliartifactId>    <version>1.4version>  dependency>  <dependency>    <groupId>commons-cligroupId>    <artifactId>commons-cliartifactId>    <version>1.3version>  dependency>dependencies>

总结

1.Maven 是一种声明式的项目管理工具。

2.Maven 有3套独立的生命周期,具体的工作交由插件 goal 完成。

3.修改依赖Scope,并不能控制Jar的内容。