一文搞懂 Maven 原理


prtyaa
prtyaa 2023-12-31 22:18:36 50576
分类专栏: 资讯

Maven 简介

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

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

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.0</modelVersion>
 <!-- NOTE: 依赖 repo 和 插件repo默认仓库地址-->
  <repositories>
<!-- ....默认repo -->
  </repositories>
  <pluginRepositories>
    <pluginRepository>
<!-- ....默认repo -->
    </pluginRepository>
  </pluginRepositories>
 <!-- NOTE: 项目默认配置,如:mian代码目录,-->
  <build>
    <directory>${project.basedir}/target</directory>
    <outputDirectory>${project.build.directory}/classes</outputDirectory>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
    <scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/resources</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>${project.basedir}/src/test/resources</directory>
      </testResource>
    </testResources>
    <pluginManagement>
      <!-- NOTE: These plugins will be removed from future versions of the super POM -->
      <!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
      <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)也有项目聚合(多个模块)。

Lifecycle

Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。Maven 生命周期包含项目清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。

Lifecycle 是抽象的概念,生命周期本身不做任何实际工作。Maven 设计中,实际工作都交由插件完成

Lifecycles VS Phases

Maven 有三套独立的 Lifecycle:defaultclean 和 site,每个 Lifecycle 包含多个 Phase。下图详细的展示了Lifecycle 和 Phase的关系。

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的绑定关系:

常用 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:

shaded-exec 中 calcite 代码:

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

Executable Jar

结论:默认情况下,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.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>3.0.0</version>
        <executions>
          <execution>
            <id>compile</id>
            <phase>compile</phase>
            <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>run</goal>
            </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>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <type>jar</type>
      <scope>test</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>mygroup</groupId>
    <artifactId>myjar</artifactId>
    <version>1.0</version>
    <classifier>jdk11</classifier>
    </dependency>
    <dependency>
      <groupId>org.apache.hive</groupId>
      <artifactId>hive-common</artifactId>
      <version>1.0</version>
      <scope>test</scope>
      <type>test-jar</type>
    </dependency>
  </dependencies>
</project>

依赖 Scope

依赖Scope作用有两个:1. 限制依赖传递:2. 控制依赖是否出现在各个classpath中。Maven 中有五种依赖scope,分别是:compile,provided,runtime,testsystem。 下面是引用自Maven官网的说明:

compile - this is the default scope, used if none is specified. Compile dependencies are available in all classpaths. Furthermore, those dependencies are propagated to dependent projects.
provided - this is much like compile, but indicates you expect the JDK or a container to provide it at runtime. It is only available on the compilation and test classpath, and is not transitive.
runtime - this scope indicates that the dependency is not required for compilation, but is for execution. It is in the runtime and test classpaths, but not the compile classpath.
test - this scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases. It is not transitive.
system - this scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository.

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

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

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

依赖传递

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

当在A中配置:

<dependency>  
            <groupId>com.B</groupId>  
            <artifactId>B</artifactId>  
            <version>1.0</version>  
</dependency>

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

依赖调节

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

  • 第一原则:路径最近者优先。 把当前模块当作顶层模块,直接依赖的包则作为次层模块,间接依赖的包则作为次层模块的次层模块,依次递推...,最后构成一棵引用依赖树。假设当前模块是A,两种依赖路径如下所示:
A --> B --> X(1.1)         // dist(A->X) = 2
A --> 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文件中的先后顺序来判定使用那条依赖路径,如果次级模块相同则向下级模块推,直至可以判断先后位置为止。

<!-- A pom.xml -->
<dependencies>
    ...
    dependency B
    ...
    dependency C
</dependencies>

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

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

<dependencies>
  <dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.2</version>
  </dependency>
  <dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.4</version>
  </dependency>
  <dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.3</version>
  </dependency>
</dependencies>

总结

  1. Maven 是一种声明式的项目管理工具。
  2. Maven 有3套独立的生命周期,具体的工作交由插件 goal 完成。
  3. 修改依赖Scope,并不能控制Jar的内容。

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=33377
赞同 0
评论 0 条
prtyaaL2
粉丝 1 发表 2553 + 关注 私信
上周热门
如何使用 StarRocks 管理和优化数据湖中的数据?  2962
【软件正版化】软件正版化工作要点  2881
统信UOS试玩黑神话:悟空  2848
信刻光盘安全隔离与信息交换系统  2740
镜舟科技与中启乘数科技达成战略合作,共筑数据服务新生态  1274
grub引导程序无法找到指定设备和分区  1239
华为全联接大会2024丨软通动力分论坛精彩议程抢先看!  165
2024海洋能源产业融合发展论坛暨博览会同期活动-海洋能源与数字化智能化论坛成功举办  164
点击报名 | 京东2025校招进校行程预告  164
华为纯血鸿蒙正式版9月底见!但Mate 70的内情还得接着挖...  159
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

加入交流群

请使用微信扫一扫!