前言
最近解决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-pommvn 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、versiondependenciesdevelopers and contributorsplugin lists (including reports)plugin executions with matching idsplugin configurationresources

因此所有的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>标签声明该插件即可。