Maven是每个Java程序都会遇到的包管理工具,今天整理一下Maven的相关知识。
官方网站说了好多,整的多复杂一样,简单说:maven是一个管理包的工具。
Maven 存在的必要性是什么呐?想想开源的jar包如此之多,版本又如此之多,在没有Maven之前,我们管理jar包全部都是下载之后创建一个lib的文件夹,然后项目进行引用,在其他的项目成员需要修改一个jar的时候需要到处拷贝,在部署的时候也很麻烦,问题存在就要解决,因此出现了Maven,统一管理,统一的仓库,只需要配置是要哪个版本的包,直接下载就够了,不用拷贝,是不是很方便。
现在大的问题解决了,怎么定位一个jar包呐?

Maven 仓库有三种类型:
- 本地(local)
- 中央(central)
- 远程(remote)
步骤 1 - 在本地仓库中搜索,如果找不到,执行步骤 2,如果找到了则执行其他操作。
步骤 2 - 在中央仓库中搜索,如果找不到,并且有一个或多个远程仓库已经设置,则执行步骤 4,如果找到了则下载到本地仓库中以备将来引用。
步骤 3 - 如果远程仓库没有被设置,Maven 将简单的停滞处理并抛出错误(无法找到依赖的文件)。步骤 4 - 在一个或多个远程仓库中搜索依赖的文件,如果找到则下载到本地仓库以备将来引用,否则 Maven 将停止处理并抛出错误(无法找到依赖的文件)
pom.xml
另外maven还生成了一个重要的文件pom.xml,maven就是通过这个文件来来管理整个project,可以理解位类似于eclipse的.project文件。pom.xml文件的POM全称是Project Object Model,这个文件对于maven的使用者来说是一个和maven交互的渠道,pom.xml包含了一个maven project的配置,一个project该如何编译打包,project有哪些依赖项等等。默认生成的pom.xml文件的内容如下:
/* 1-1 */ <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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.1.0.1</version> <packaging>jar</packaging> <name>myapp</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
解释一下这个xml文件的内容:
- modelVersion: 这个XML文件所使用的POM格式的版本
- groupId: 相当于这个project的所有者或者机构的一个标识,一般是com.company.xxx这种格式
- artifactId: 这个project最后所生成的文档(jar、war)的名字,比如对于junit这个开源的project,它的artifactId就是junit
- packaging: 这个project的打包的类型,一般是war、jar等值
- version: project的版本
- name: project的名字,生成文档等内容的时候会用的这个名字
maven 中的 lifecycle
Maven和gradle应该是现代java程序员中使用的最多的两种构建工具。在它们出现之前,则是ant的天下。
Maven为我们封装了很多构建中非常有用的操作,我们只需要执行简单的几个mvn命令即可。
今天我们要讨论一下mvn命令之下的生命周期的构建。
maven对构建(build)的过程进行了抽象和定义,这个过程被称为构建的生命周期(lifecycle)。生命周期(lifecycle)由多个阶段(phase)组成,每个阶段(phase)会挂接一到多个goal。goal是maven里定义任务的最小单元,goal分为两类,一类是绑定phase的,就是执行到某个phase,那么这个goal就会触发,另外一类不绑定,就是单独任务。
lifecycle和Phases

所谓lifecycle,可以理解为可以执行一组命令的集合,用来执行具体的某些操作。
Maven默认有三种lifecycle:default,clean和site。default主要用来处理项目的开发,clean主要用来负责项目的清理,site主要用来生成项目的文档。
Maven预设了三个Lifecycle ,各包含了下列Phases. Clean Lifecycle pre-clean clean post-clean Default Lifecycle validate initialize generate-sources process-sources generate-resources process-resources compile process-classes generate-test-sources process-test-sources process-test-resources test-compile process-test-classes test prepare-package package pre-integration-test integration-test post-integration-test verify install deploy Site Lifecycle pre-site site post-site site-deploy
lifecycle是由一个或者多个phase组成的。
以default为例,它大概由23个phases组成,这些phases将会按顺序执行来完成default的lifecycle。
我们选取default lifecycle中非常常见的几个phase来说明一下:
- validate – 用来验证项目是否正确或者项目所需要的信息是否可用。
- compile – 用来编译项目代码
- test – 执行代码中的单元测试
- package – 将编译后的代码进行打包,打包可有很多种方式,比如:jar,war等
- verify – 执行集成测试
- install – 将项目安装到本地仓库中,供有依赖关系的其他项目使用
- deploy – 将项目部署到远程仓库,以便共享给其他的用户
上面的phase执行是有顺序的,比如我们如果执行mvn verify,则会顺序执行validate,compile,test和package。
mvn clean mvn compile mvn test mvn package
表明maven会执行到某个生命周期(lifecycle)的某个阶段(phase)
这个phase以及它前面所有phase绑定的目标(goal)都会执行, 每个phase都会邦定maven默认的goal或者没有goal, 或者自定义的goal。
也可以通过传入参数跳过(skip)某些phase,例如:
mvn install -Dmaven.test.skip=true
Phases和Goals
Phases是一种任务的集合,它是由一个或者多个Goals组成的。Goals可以包含在Phases里面执行,也可以单独用命令执行。
那么Goals又是从哪里来的呢?Goals是定义在maven中的plugin中的。
每个生命周期包含了多个步骤(phase),而 goal 则是绑定到 phase 上的,每一个 phase 都对应 1 个或多个 goal。
goal 是存在于 maven plugin 中,因此,大多数的 maven 功能实际上是存在于插件中,一个 maven 插件提供了一组可以被运行的 goal。
我们看下面一张直观的图:

下图列出了现有lifecycle中的phase,和相应phase所对应的plugin。
我们可以看到基本每个phase都和一个plugin中的golas是相对于应的。
phase 和 goal 的不同在于:
运行某个 phase 的时,必须把生命周期中的所有的前置 phase 都会运行一遍。
而运行 goal,可以脱离生命周期这个概念,通过 maven 插件,单独的运行某个 goal 或一组 goal。
maven 中可以通过下面的命令格式运行 goal:
mvn [plugin-name]:[goal-name]
比如:
mvn compiler:compile,运行 compiler 插件中的 compile goal。
事实上 compiler:compile.,正是对应于 compile phase 的,即运行 compile phase 就等于运行了 mvn compiler:compile.
在 eclipse 中可以配置运行 maven 中指定的 goal。

phase 和 goal 的不同在于:
运行某个 phase 的时,必须把生命周期中的所有的前置 phase 都会运行一遍。
而运行 goal,可以脱离生命周期这个概念,通过 maven 插件,单独的运行某个 goal 或一组 goal。
这类就是没有绑定phase的goal,但是这类goal却通常会有个执行前提,就是project必须执行到某个phase,
那么执行这个goal,其实也会触发maven执行到前提要求的phase。
例如jetty:run是个非绑定phase的goal,它的前提是test-compile,这个前提是由plugin的代码逻辑制定的。
maven的内置插件
Maven的生命周期与插件相互绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务。例如项目编译这一任务,它对应default生命周期的compile这一阶段,而maven-compiler-plugin这一插件的compile目标能够完成该任务。因此将他们绑定,就能实现项目编译的目的。
为了能让用户几乎不用任何配置就能构建Maven项目,Maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。
clean生命周期仅有pre-clean,clean和post-clean三个阶段,其中的clean与maven-clean-plugin:clean绑定。maven-clean-plugin仅有clean这一个目标,其作用就是删除项目的输出目录。
clean生命周期阶段与插件目标的绑定关系如下图:

site生命周期有pre-site,site,post-site和site-deploy四个阶段,其中,site和maven-site-plugin:site相互绑定,site-deploy和maven-site-plugin:deploy相互绑定。maven-site-plugin有很多目标,其中,site目标用来生成项目站点,deploy目标用来将项目站点部署到远程服务器上。
site生命周期阶段与插件目标的绑定关系如下图:

相对于clean和site生命周期来说,default生命周期与插件目标的绑定关系就显得复杂一些。这是因为对于任何项目来说,例如jar项目和war项目,它们的项目清理和站点生成任务是一样的,不过构建过程会有区别。例如jar项目需要打成jar包,而war项目需要打成war包。
因为项目的打包类型会影响构建的具体过程,因此,default生命周期的阶段与插件目标的绑定关系由项目打包类型所决定,打包类型是通过POM中的packaging元素定义的。最常见,最重要的打包类型是jar,它也是默认的打包类型。
基于该打包类型的项目,其default生命周期的内置插件绑定关系及具体任务如下图:

注意,上表只列出了拥有插件绑定关系的阶段,default生命周期还有很多其他阶段,默认它们没有绑定任何插件,因此也没有任何实际行为。
除了默认的打包类型jar之外,常见的打包类型还有war,pom,maven-plugin和ear等。它们的default生命周期与插件目标的绑定关系可参阅Maven官方文档。
maven构建流程使用插件
前面介绍了maven的lifecycle、phase和goal,使用maven构建项目就是执行lifecycle,直至执行到指定的phase为止,每个phase会执行自己默认的一个或者多个goal,goal是最小任务单元。
我们以compile这个phase为例,如果执行:
mvn compile
Maven将执行compile这个phase,这个phase
会调用compiler插件
执行关联的compiler:compile这个goal。
实际上,执行每个phase,都是通过某个插件(plugin)
来执行的,Maven本身其实并不知道如何执行compile,它只是负责找到对应的compiler插件
,然后执行默认的compiler:compile这个goal来完成编译。
所以,使用Maven,实际上就是配置好需要使用的插件,然后通过phase调用它们。

如果标准插件无法满足需求,我们还可以使用自定义插件
。使用自定义插件
的时候,需要声明。例如,使用maven-shade-plugin
可以创建一个可执行的jar
,要使用这个插件,需要在pom.xml
中声明它
<project> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> ... </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
自定义插件往往需要一些配置,例如,maven-shade-plugin
需要指定Java程序的入口,它的配置是:
<configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.itranswarp.learnjava.Main</mainClass> </transformer> </transformers> </configuration>
注意,Maven自带的标准插件例如compiler是无需声明的,只有引入其它的插件才需要声明。
下面列举了一些常用的插件:
- maven-shade-plugin:打包所有依赖包并生成可执行jar;
- cobertura-maven-plugin:生成单元测试覆盖率报告;
- findbugs-maven-plugin:对Java源码进行静态分析以找出潜在问题。
其他plugin介绍
这里我们介绍两个非常常用的maven plugin,maven-dependency-plugin和maven-jar-plugin。
- maven-dependency-plugin
maven中的依赖jar包是存放在maven的本地仓库中的,如果项目中依赖了某些jar包,在部署的时候还需要这些依赖的jar包拷贝出来,非常不方便,有了maven-dependency-plugin,则可以借用它的copy-dependencies来将项目的依赖jar包拷贝出啦,如下所示:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.1.2</version> <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory> ${project.build.directory}/lib </outputDirectory> </configuration> </execution> </executions> </plugin>
goals是和相应的phase相关联的,在上面的例子中,我们将copy-dependencies和package相关联,则在我们执行mvn package的时候就会自动执行copy-dependencies,从配置文件可以知道,我们将会把项目的依赖jar包拷贝到项目的build目录的lib目录下。
- maven-jar-plugin
有了依赖的lib,可以将main程序打包成为一个可执行的jar包。这时候我们就需要使用到maven-jar-plugin。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifest> <mainClass>com.flydean.MavenClass</mainClass> </manifest> </archive> </configuration> </plugin>
为了生成可执行的jar包,我们需要在MANIFEST.MF文件中添加mainClass文件的路径,这样在执行jar包的时候,无需额外的参数即可运行。
遗憾的是,如果我们的class文件用到了外部jar包的依赖时候,jar包直接运行会出错,因为找不到所依赖的jar包。
在介绍maven-dependency-plugin的时候,我们已经把所用到的lib拷贝出来了,这里我们可以直接使用:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.flydean.MavenClass</mainClass> </manifest> </archive> </configuration> </plugin>
多加了两个addClasspath的参数,我们将打包好的jar包解压缩。
可以看到里面多了一个MANIFEST.MF的文件:
Manifest-Version: 1.0 Created-By: Maven Jar Plugin 3.2.0 Build-Jdk-Spec: 14 Class-Path: lib/lombok-1.18.10.jar lib/logback-classic-1.2.3.jar lib/log back-core-1.2.3.jar lib/slf4j-api-1.7.25.jar Main-Class: com.flydean.MavenClass
这个文件里面包含了一些jar包的元数据,并且里面添加了Class-Path和Main-Class文件,这时候执行运行jar包就可以直接执行了。