前言
最近解决jar包冲突问题时,很头疼,发现自己对maven的理解太肤浅了,很多细节都一知半解,于是最近又学习了一把maven,总结如下:
基本概念
maven有两个最基本的概念: pom和lifecycle, 这里的pom不是maven构建过程中使用的pom文件,但他们之间有联系。 pom全称为Project Object Model, 简单说就是要对构建的项目进行建模,将要构建的项目看成是一个对象Object,既然是一个对象,这个对象有哪些属性呢? 在maven中一个项目使用唯一的坐标来表示,它包括groupId, artifactId, version, classifier, type(也叫packaging)这五部分,另外一个方面,一个项目肯定不是孤立存在的,可能会依赖其他项目,也就是说这个对象应该还有dependencies属性,用PO表示构建对象,使用java代码描述这个对象的话:
class PO{
private String groupId;
private String artifactId;
private String version;
private String classifier;
private String type;
private Set<PO> dependencies;
}
xml具有很强的表达能力,一个java对象可以用xml来描述,用xml表达上面这个java对象可以为:
<PO>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<classifier><classifier>
<type></type>
<dependencies>
<PO></PO>
<PO></PO>
...
</dependencies>
</PO>
这个是不是和pom.xml很类似? 其实pom.xml就是PO对象的xml描述,上面这个PO定义还不完整,我们知道在java中类是可以继承的,PO也有继承关系,PO对象存在父类父类对象,用parent表示,它会继承父类对象的所有属性。 另一方面,一个项目可能根据不同职责分为多个模块(module),所有模块其实也就是一个单独的项目,只不过这些项目会使用其父对象的一些属性来进行构建。我们将这些新的属性加到PO的定义中去:
class PO{
private String groupId;
private String artifactId;
private String version;
private String classifier;
private String type;
private Set<PO> dependencies;
private PO parent;
private Set<PO> modules;
}
再将这个定义用XML语言表示一下:
<PO>
<parent></parent>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<classifier><classifier>
<type></type>
<dependencies>
<PO></PO>
<PO></PO>
...
</dependencies>
<modules>
...
</modules>
</PO>
是不是更像pom.xml了? pom.xml其实就是对PO对象的xml描述!!
构建
项目的构建过程对应的是PO对象的build属性,对应pom.xml中也就是<build>元素中的内容,这里就有引入maven中第二个核心概念:Lifecycle。Lifecycle直译过来就是生命周期。我们平常会接触到哪些周期呢?一年中春夏秋冬就是一个周期。一个周期中可能分为多个阶段,比如这里的春夏秋冬。在maven中一个构建过程就对应一个Lifecycle,这个Lifecycle也分为多个阶段,每个阶段叫做phase。你可能会问,那这个Lifecycle中包含多少个phase呢?一个标准的构建Lifecycle包含了如下的phase:
validate: 用于验证项目的有效性和其项目所需要的内容是否具备
initialize:初始化操作,比如创建一些构建所需要的目录等。
generate-sources:用于生成一些源代码,这些源代码在compile phase中需要使用到
process-sources:对源代码进行一些操作,例如过滤一些源代码
generate-resources:生成资源文件(这些文件将被包含在最后的输入文件中)
process-resources:对资源文件进行处理
compile:对源代码进行编译
process-classes:对编译生成的文件进行处理
generate-test-sources:生成测试用的源代码
process-test-sources:对生成的测试源代码进行处理
generate-test-resources:生成测试用的资源文件
process-test-resources:对测试用的资源文件进行处理
test-compile:对测试用的源代码进行编译
process-test-classes:对测试源代码编译后的文件进行处理
test:进行单元测试
prepare-package:打包前置操作
package:打包
pre-integration-test:集成测试前置操作
integration-test:集成测试
post-integration-test:集成测试后置操作
install:将打包产物安装到本地maven仓库
deploy:将打包产物安装到远程仓库
在maven中,你执行任何一个phase时,maven会将其之前的phase都执行。例如 mvn install,那么maven会将deploy之外的所有phase按照他们出现的顺序一次执行。
Lifecycle还牵涉到另外一个非常重要的概念:goal。注意上面Lifecycle的定义,也就是说maven为程序的构建定义了一套规范流程:第一步需要validate,第二步需要initialize... ... compile,test,package,... ... install,deploy,但是并没有定义每一个phase具体应该如何操作。这里的phase的作用有点类似于Java语言中的接口,只协商了一个契约,但并没有定义具体的动作。比如说compile这个phase定义了在构建流程中需要经过编译这个阶段,但没有定义应该怎么编译(编译的输入是什么?用什么编译javac/gcc?)。这里具体的动作就是由goal来定义,一个goal在maven中就是一个Mojo(Maven old java object)。Mojo抽象类中定义了一个execute()方法,一个goal的具体动作就是在execute()方法中实现。实现的Mojo类应该放在哪里呢?答案是maven plugin里,所谓的plugin其实也就是一个maven项目,只不过这个项目会引用maven的一些API,plugin项目也具备maven坐标。
在执行具体的构建时,我们需要为lifecycle的每个phase都绑定一个goal,这样才能够在每个步骤执行一些具体的动作。比如在lifecycle中有个compile phase规定了构建的流程需要经过编译这个步骤,而maven-compile-plugin这个plugin有个compile goal就是用javac来将源文件编译为class文件的,我们需要做的就是将compile这个phase和maven-compile-plugin中的compile这个goal进行绑定,这样就可以实现Java源代码的编译了。那么有人就会问,在哪里绑定呢?答案是在pom.xml<build>元素中配置即可。例如:
<build>
<plugins>
<plugin>
<artifactId>maven-myquery-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<id>execution1</id>
<phase>test</phase>
<configuration>
<url>http://www.foo.com/query</url>
<timeout>10</timeout>
<options>
<option>one</option>
<option>two</option>
<option>three</option>
</options>
</configuration>
<goals>
<goal>query</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
就将maven-myquery-plugin中的query这个goal绑定到了test这个phase,后续在maven执行到test phase时就会执行query goal。还有有人可能会问,我都没有指定Java源文件的位置,编译啥?这就引出了maven的design principle。在maven中,有一个非常著名的principle就是convention over configuration(约定优于配置)。这一点和ant有非常大的区别,例如使用ant来进行编译时,我们需要指定源文件的位置,输出文件的位置,javac的位置,classpath... ...在maven中这些都是不需要,若没有手动配置,maven默认从<项目根目录>/src/main/java这个目录去查找Java源文件,编译后的class文件会保存在<项目根目录>/target/classes目录。在maven中,所有的PO都有一个根对象,就是Super POM。Super POM中定义了所有的默认的配置项,Super POM对应的pom.xml文件可以在maven安装目录下lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml中找到。用一张图来表示maven Lifecycle,phase,goal之间的关系:
插件
上面我们提到,Maven 将所有项目的构建过程统一抽象成一套生命周期: 项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成 … 几乎所有项目的构建,都能映射到这一组生命周期上. 但生命周期是抽象的(Maven的生命周期本身是不做任何实际工作), 任务执行(如编译源代码)均交由插件完成. 其中每个构建步骤都可以绑定一个或多个插件的目标,而且Maven为大多数构建步骤都编写并绑定了默认插件.当用户有特殊需要的时候, 也可以配置插件定制构建行为, 甚至自己编写插件.
再说生命周期
Maven 拥有三套相互独立的生命周期: clean、default 和 site, 而每个生命周期包含一些phase阶段, 阶段是有顺序的, 并且后面的阶段依赖于前面的阶段. 而三套生命周期相互之间却并没有前后依赖关系, 即调用site周期内的某个phase阶段并不会对clean产生任何影响.
clean
clean生命周期的目的是清理项目:
执行如$ mvn clean;
default
default生命周期定义了真正构建时所需要执行的所有步骤:
执行如$ mvn clean install;
site
site生命周期的目的是建立和发布项目站点: Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息
执行命令如$ mvn clean deploy site-deploy;
这三个lifecycle定义了其包含的phase。maven会在这三个lifecycle中匹配对应的phase。当执行某个phase时,maven会依次执行在这个phase之前的phase。
插件
生命周期的阶段phase与插件的目标goal相互绑定, 用以完成实际的构建任务. 而对于插件本身, 为了能够复用代码,它往往能够完成多个任务, 这些功能聚集在一个插件里,每个功能就是一个目标.
如:$ mvn compiler:compile: 冒号前是插件前缀, 后面是该插件目标(即: maven-compiler-plugin的compile目标).
而该目标绑定了default生命周期的compile阶段: 他们的绑定能够实现项目编译的目的.
内置绑定
为了能让用户几乎不用任何配置就能使用Maven构建项目, Maven 默认为一些核心的生命周期绑定了插件目标, 当用户通过命令调用生命周期阶段时, 对应的插件目标就会执行相应的逻辑.
上图只列出了打包方式为jar且拥有插件绑定关系的阶段(packaging 定义了Maven项目打包方式, 通常打包方式与所生成构件扩展名对应,有jar(默认)、war、pom、maven-plugin等., 其他打包类型生命周期的默认绑定关系可参考: Built-in Lifecycle Bindings、Plugin Bindings for default Lifecycle Reference.
自定义绑定
除了内置绑定以外, 用户还能够自定义将某个插件目标绑定到生命周期的某个阶段上. 如创建项目的源码包, maven-source-plugin插件的jar-no-fork目标能够将项目的主代码打包成jar文件, 可以将其绑定到verify阶段上:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
executions下每个execution子元素可以用来配置执行一个任务.
聚合与继承
Maven的聚合特性(aggregation)能够使项目的多个模块聚合在一起构建, 而继承特性(inheritance)能够帮助抽取各模块相同的依赖、插件等配置,在简化模块配置的同时, 保持各模块一致.
模块聚合
随着项目越来越复杂(需要解决的问题越来越多、功能越来越重), 我们更倾向于将一个项目划分几个模块并行开发, 如: 将feedcenter-push项目划分为client、core和web三个模块, 而我们又想一次构建所有模块, 而不是针对各模块分别执行$ mvn命令. 于是就有了Maven的模块聚合 -> 将feedcenter-push作为聚合模块将其他模块聚集到一起构建:
聚合POM
聚合模块POM仅仅是帮助聚合其他模块构建的工具, 本身并无实质内容:
<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.vdian.feedcenter</groupId>
<artifactId>feedcenter-push</artifactId>
<packaging>pom</packaging>
<version>1.0.0.SNAPSHOT</version>
<modules>
<module>feedcenter-push-client</module>
<module>feedcenter-push-core</module>
<module>feedcenter-push-web</module>
</modules>
</project>
通过在一个打包方式为pom的Maven项目中声明任意数量的module以实现模块聚合:
packaging: pom, 否则无法聚合构建.
modules: 实现聚合的核心,module值为被聚合模块相对于聚合POM的相对路径, 每个被聚合模块下还各自包含有pom.xml、src/main/java、src/test/java等内容, 离开聚合POM也能够独立构建
若<packaging>元素的内容是jar,那么我们很好理解,也就是说这个项目最终会被打包成一个jar包,那<packaging>元素为pom又是什么意思呢?从字面上的意思来看,这个项目将打包成一个pom。我们不妨去maven仓库里去瞧瞧(前提是已经在项目下运行了mvn install命令)。可以发现这个文件其实和项目中的pom.xml是同一个文件,这样做的目的是什么呢?上面我们说过PO对象也是有继承关系的,<packaging>pom</packaging>的作用就在这里,这就是maven中project inheritance的概念。当实际执行maven命令的时候,会根据project inheritance关系对项目的pom.xml进行转化,得到真正执行时所用到的pom.xml,即所谓的effective pom,因此可以得到一个结论:所有<packaging>元素为pom的项目其实并不会输出一个可供外部使用,类似于jar包的东西。这类项目的作用有两个:
管理子项目
例如这里的api和biz就是echo项目的两个module。若没有echo这个父项目,我们需要到api和biz两个项目下分别执行mvn install命令才能完成整个构建过程,而有了echo这个父项目之后,我们只需在echo项目中执行mvn install即可,maven会解析pom.xml,发现该项目有api和biz两个module,它会分别到这两个项目下去执行mvn install命令。当module数量比较多的时候,能大大提高构建的效率。
管理继承属性
比如A和B都需要某个依赖,那么在父类项目的pom.xml中声明即可,因为根据PO对象的继承关系,A和B项目会继承父类项目的依赖,这样就可以减少一些重复的输入。
effective pom包含了当前项目的PO对象,直到Super POM对应的PO对象中的信息。要看一个项目的effective pom,只需在项目中执行
mvn help:effective-pom
命令即可查看。这里顺带说一句,有的同学可能不理解上面这个命令是什么意思。maven命令的语法为
mvn [options] [goal(s)] [phase(s)]
goal和phase。maven允许你执行一个或者多个goals/phases。很明显这面的命令help:effective-pom并不是一个phase,那么也就是说它是一个goal。对这个goal只不过是采用了缩写的形式,其全称是这样的:
org.apache.maven.plugins:maven-help-plugin:2.2:effective-pom
以分号为分隔符,包含了groupId,artifactId,version,goal四部分。若groupId为org.apache.maven.plugins则可以使用上述的简写形式。也就是说
mvn help:effective-pom
mvn org.apache.maven.plugins:maven-help-plugin:2.2:effective-pom
是等价的,都是执行了maven-help-plugin这个plugin中的effective-pom这个goal。
我们知道一个plugin中可以包含多个goal,goal可以绑定到lifecycle中的某一个phase,这样在执行这个phase的时候就会调用该goal。那那些没有绑定到phase上的goal应该如何执行呢?这就是 mvn [goal(s)]
这里的goal也就是官方文档中所说的standalone goal,也就是说若一个plugin中的某个goal没有和一个phase进行绑定,可以通过这种方式来执行。可能有的读者使用过
mvn dependency:tree
这条命令,这里其实就是单独执行一个goal,这个goal的作用是分析该工程的依赖并使用树状的形式打印出来。这里的dependency:tree其实是一个简写的形式,其完×××式是:
mvn org.apache.maven.plugins:maven-dependency-plugin:<版本号信息>:tree
也就是说单独执行一个goal的方式是:
mvn <groupId>:<artifactId>:<version>:<goal>
每次都要敲这么长一串命令是很繁琐的,因此才有了上述的简写的形式。maven规定了对于plugin的artifactId是如下两种形式:
maven-{prefix}-maven-plugin
的可以使用简写的方式${prefix}来表示一个plugin.
模块继承
在面向对象中, 可以通过类继承实现复用. 在Maven中同样也可以创建POM的父子结构, 通过在父POM中声明一些配置供子POM继承来实现复用与消除重复
父pom
与聚合类似, 父POM的打包方式也是pom, 因此可以继续复用聚合模块的POM(这也是在开发中常用的方式):
<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.vdian.feedcenter</groupId>
<artifactId>feedcenter-push</artifactId>
<packaging>pom</packaging>
<version>1.0.0.SNAPSHOT</version>
<modules>
<module>feedcenter-push-client</module>
<module>feedcenter-push-core</module>
<module>feedcenter-push-web</module>
</modules>
<properties>
<finalName>feedcenter-push</finalName>
<warName>${finalName}.war</warName>
<spring.version>4.0.6.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<warExplodedDirectory>exploded/${warName}</warExplodedDirectory>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
dependencyManagement: 能让子POM继承父POM的配置的同时, 又能够保证子模块的灵活性: 在父POMdependencyManagement元素配置的依赖声明不会实际引入子模块中, 但能够约束子模块dependencies下的依赖的使用(子模块只需配置groupId与artifactId, 见下).
pluginManagement: 与dependencyManagement类似, 配置的插件不会造成实际插件的调用行为, 只有当子POM中配置了相关plugin元素, 才会影响实际的插件行为.
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<groupId>com.vdian.feedcenter</groupId>
<artifactId>feedcenter-push</artifactId>
<version>1.0.0.SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feedcenter-push-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
元素继承
可以看到, 子POM中并未定义模块groupId与version, 这是因为子POM默认会从父POM继承了如下元素:
groupId、version
dependencies
developers and contributors
plugin lists (including reports)
plugin executions with matching ids
plugin configuration
resources
因此所有的springframework都省去了version、junit还省去了scope, 而且插件还省去了executions与configuration配置, 因为完整的声明已经包含在父POM中.
优势: 当依赖、插件的版本、配置等信息在父POM中声明之后, 子模块在使用时就无须声明这些信息, 也就不会出现多个子模块使用的依赖版本不一致的情况, 也就降低了依赖冲突的几率. 另外如果子模块不显式声明依赖与插件的使用, 即使已经在父POM的dependencyManagement、pluginManagement中配置了, 也不会产生实际的效果.
推荐: 模块继承与模块聚合同时进行,这意味着, 你可以为你的所有模块指定一个父工程, 同时父工程中可以指定其余的Maven模块作为它的聚合模块. 但需要遵循以下三条规则:
在所有子POM中指定它们的父POM;
将父POM的packaging值设为pom;
在父POM中指定子模块/子POM的目录.
parent元素内还包含一个relativePath元素, 用于指定父POM的相对路径, 默认../pom.xml
超级pom-约定优先于配置
任何一个Maven项目都隐式地继承自超级POM, 因此超级POM的大量配置都会被所有的Maven项目继承, 这些配置也成为了Maven所提倡的约定.
<!-- START SNIPPET: superpom -->
<project>
<modelVersion>4.0.0</modelVersion>
<!-- 定义了中央仓库以及插件仓库, 均为:https://repo.maven.apache.org/maven2 -->
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
<!-- 依次定义了各类代码、资源、输出目录及最终构件名称格式, 这就是Maven项目结构的约定 -->
<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>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.3.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<!-- 定义项目报告输出路径 -->
<reporting>
<outputDirectory>${project.build.directory}/site</outputDirectory>
</reporting>
<!-- 定义release-profile, 为构件附上源码与文档 -->
<profiles>
<!-- NOTE: The release profile will be removed from future versions of the super POM -->
<profile>
<id>release-profile</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<inherited>true</inherited>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<updateReleaseInfo>true</updateReleaseInfo>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
<!-- END SNIPPET: superpom -->
Maven Plugin 开发
详细代码在maven plugin demo
创建plugin项目
mvn archetype:generate
-DgroupId=com.fq.plugins -DartifactId=lc-maven-plugin -Dversion=0.0.1-SNAPSHOT
-DarchetypeArtifactId=maven-archetype-plugin
-DinteractiveMode=false
-DarchetypeCatalog=internal
使用maven-archetype-plugin Archetype可以快速创建一个Maven插件项目。
pom.xml
插件本身也是Maven项目, 特殊之处在于packaging方式为maven-plugin:
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fq.plugins</groupId>
<artifactId>lc-maven-plugins</artifactId>
<packaging>maven-plugin</packaging>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.3</version>
</dependency>
</dependencies>
</project>
maven-plugin 打包方式能控制Maven为其在生命周期阶段绑定插件处理的相关目标.
编写目标Mojo
@Mojo(name = "lc", defaultPhase = LifecyclePhase.VERIFY)
public class LCMavenMojo extends AbstractMojo {
private static final List<String> DEFAULT_FILES = Arrays.asList("java", "xml", "properties");
@Parameter(defaultValue = "${project.basedir}", readonly = true)
private File baseDir;
@Parameter(defaultValue = "${project.build.sourceDirectory}", readonly = true)
private File srcDir;
@Parameter(defaultValue = "${project.build.testSourceDirectory}", readonly = true)
private File testSrcDir;
@Parameter(defaultValue = "${project.build.resources}", readonly = true)
private List<Resource> resources;
@Parameter(defaultValue = "${project.build.testResources}", readonly = true)
private List<Resource> testResources;
@Parameter(property = "lc.file.includes")
private Set<String> includes = new HashSet<>();
private Log logger = getLog();
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (includes.isEmpty()) {
logger.debug("includes/lc.file.includes is empty!");
includes.addAll(DEFAULT_FILES);
}
logger.info("includes: " + includes);
try {
long lines = 0;
lines += countDir(srcDir);
lines += countDir(testSrcDir);
for (Resource resource : resources) {
lines += countDir(new File(resource.getDirectory()));
}
for (Resource resource : testResources) {
lines += countDir(new File(resource.getDirectory()));
}
logger.info("total lines: " + lines);
} catch (IOException e) {
logger.error("error: ", e);
throw new MojoFailureException("execute failure: ", e);
}
}
private LineProcessor<Long> lp = new LineProcessor<Long>() {
private long line = 0;
@Override
public boolean processLine(String fileLine) throws IOException {
if (!Strings.isNullOrEmpty(fileLine)) {
++this.line;
}
return true;
}
@Override
public Long getResult() {
long result = line;
this.line = 0;
return result;
}
};
private long countDir(File directory) throws IOException {
long lines = 0;
if (directory.exists()) {
Set<File> files = new HashSet<>();
collectFiles(files, directory);
for (File file : files) {
lines += CharStreams.readLines(new FileReader(file), lp);
}
String path = directory.getAbsolutePath().substring(baseDir.getAbsolutePath().length());
logger.info("path: " + path + ", file count: " + files.size() + ", total line: " + lines);
logger.info("\t-> files: " + files.toString());
}
return lines;
}
private void collectFiles(Set<File> files, File file) {
if (file.isFile()) {
String fileName = file.getName();
int index = fileName.lastIndexOf(".");
if (index != -1 && includes.contains(fileName.substring(index + 1))) {
files.add(file);
}
} else {
File[] subFiles = file.listFiles();
for (int i = 0; subFiles != null && i < subFiles.length; ++i) {
collectFiles(files, subFiles[i]);
}
}
}
}
@Parameter: 配置点, 提供Mojo的可配置参数. 大部分Maven插件及其目标都是可配置的, 通过配置点, 用户可以自定义插件行为
<plugin>
<groupId>com.fq.plugins</groupId>
<artifactId>lc-maven-plugins</artifactId>
<version>0.0.1-SNAPSHOT</version>
<executions>
<execution>
<id>lc</id>
<phase>verify</phase>
<goals>
<goal>lc</goal>
</goals>
<configuration>
<includes>
<include>java</include>
<include>lua</include>
<include>json</include>
<include>xml</include>
<include>properties</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
execute(): 实际插件功能;
异常: execute()方法可以抛出以下两种异常:
MojoExecutionException: Maven执行目标遇到该异常会显示 BUILD FAILURE 错误信息, 表示在运行期间发生了预期的错误;
MojoFailureException: 表示运行期间遇到了未预期的错误, 显示 BUILD ERROR 信息
测试&执行
通过mvn clean install将插件安装到仓库后, 就可将其配置到实际Maven项目中, 用于统计项目代码了:
$ mvn com.fq.plugins:lc-maven-plugins:0.0.1-SNAPSHOT:lc
你可能注意到为了调用该插件的goal,我们需要给出该插件的所有坐标信息,包裹groupId, artifactId,version号,你可能之前已经执行过"mvn eclipase:eclipase"或"mvn idea:idea"这样简洁的命令,让我们也来将自己的插件调用变简单一点。要通过简单别名的方式调用Maven插件,我们需要做到以下两点:
插件的artifactId应该遵循-maven-plugin或maven--plugin命名规则,对于本文中的插件,我们已经遵循了。
需要将插件的groupId放在Maven默认的插件搜寻范围之内,默认情况下Maven只会在org.apache.maven.plugins和org.codehaus.mojo两个groupId下搜索插件,要让Maven同时搜索我们自己的groupId,我们需要在~/.m2/settings.xml中加入:
<pluginGroups>
<pluginGroup>com.fq.plugins</pluginGroup>
</pluginGroups>
在达到以上两点之后,我们便可以通过以下命令来调用自己的插件了:
mvn lc:lc
要在别的项目中应用插件也是简单的,我们只需要在该项目的pom.xml文件中使用上面<plugin>标签声明该插件即可。
转载于:https://blog.51cto.com/13981400/2386744