Maven 是一种声明式项目管理工具,通过在 POM 中配置 "who","what","where"等信息,即可满足编译、测试、打包、发布等项目构建需求。 声明式的好处是,用户无需关心构建工具的实现细节,只需在 pom.xml
中配置好项目名,依赖等基础信息即可。坏处是,实现自定义的构建逻辑,相对复杂。(Maven 也提供了插件,如:maven-antrun-plugin
,来运行用户自定义脚本。当然,插件最终 apply 到 Maven 的方式最终仍然是声明式的,即需要在 POM 中声明插件运行时机和插件相关配置。)
相对应地,Make 和 Ant 等构建工具是过程式项目管理工具,用户需要编写构建脚本并组织各脚本的依赖关系。过程式项目管理工具好处是,用户自由度很大;坏处是,项目管理经验无法复用,构建脚本编写较为复杂。
POM 文件是Maven的核心文件,包含项目构建相关的所有配置信息,如:项目源代码目录,class 文件输出目录等。Maven 执行 goal 时,会首先读取当前目录的 POM 文件,然后执行对应 goal。
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>
pom.xml
,便于做公共配置管理。Super POM 是 Project Inheritance
的典型例子。module
。下图展示了 Presto 的 POM 结构,Presto 项目中既有项目继承(继承自io.airlift:airbase:80
)也有项目聚合(多个模块)。
Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。Maven 生命周期包含项目清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。
Lifecycle 是抽象的概念,生命周期本身不做任何实际工作。Maven 设计中,实际工作都交由插件完成。
Maven 有三套独立的 Lifecycle:default
、clean
和 site
,每个 Lifecycle 包含多个 Phase。下图详细的展示了Lifecycle 和 Phase的关系。
default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分,其包含的阶段如下:
ANTLR
插件会根据语法文件生成对应的 Java 源代码。ANTLR
。clean 生命周期的目的主要是清理项目。
site 生命周期用于生成代码站点文档并发布至对应Web Server。
一个 Plugin 包含多个goal,来完成项目构建的实际工作,如:Compiler plugin 有两个goal compile 和 testCompile,分别用于编译main代码与编译test代码。下图是default生命周期的插件内置绑定与具体goal的绑定关系:
maven-shade-plugin
:maven-shade-plugin
是一个很强大的 Maven 插件,可以用来relocate 包名,解决依赖冲突问题;也可以生成一个可执行Jar包(又称 Uber Jar)。下面我们就简单介绍下这两个功能使用方法。
我们以shade hive-exec 中的guava包进行说明。(hive-exec 会依赖calcite的代码,calcite代码也会依赖guava包)
shaded-exec
pom.xml:
shaded-exec
中 calcite 代码:
结论:maven-shade-plugin
会对满足对应pattern的所有class文件进行relocate,不会区分该class文件是否是本项目代码编译产生。
结论:默认情况下,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:当插件目标被绑定到不同的生命周期阶段的时候,其执行顺序会由生命周期阶段的先后顺序决定。如果多个目标被绑定到同一个阶段,它们的执行顺序会是怎样?答:当多个插件目标绑定到同一个阶段的时候,这些插件声明的先后顺序决定了目标的执行顺序。
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作用有两个:1. 限制依赖传递:2. 控制依赖是否出现在各个classpath中。Maven 中有五种依赖scope,分别是:compile
,provided
,runtime
,test
和system
。 下面是引用自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:
结合依赖的scope和四类classpath,总结出 classpath 与 依赖的关系,如下图:
当在A中配置:
<dependency>
<groupId>com.B</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
</dependency>
则会自动导入 C 包, 详细的关系传递如下表 :
依赖调解遵循以下两大原则:路径最短优先、声明顺序优先,
A --> B --> X(1.1) // dist(A->X) = 2
A --> C --> D --> X(1.0) // dist(A->X) = 3
此时,Maven可以按照第一原则自动调解依赖,结果是使用X(1.1)作为依赖。
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文件最终引入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>
网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。
加入交流群
请使用微信扫一扫!