前言
最近解决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 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 dependencies; private PO parent; private Set 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中也就是元素中的内容,这里就有引入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>plugins> plugin> artifactId>maven-myquery-pluginartifactId> version>1.0version> executions> execution> id>execution1id> phase>testphase> configuration> url>http://www./queryurl> timeout>10timeout> options> option>oneoption> option>twooption> option>threeoption> options> configuration> goals> goal>querygoal> 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.pluginsgroupId> artifactId>maven-source-pluginartifactId> version>3.0.0version> executions> execution> id>attach-sourcesid> phase>verifyphase> goals> goal>jar-no-forkgoal> 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./POM/4.0.0' xmlns:xsi='http://www./2001/XMLSchema-instance' xsi:schemaLocation='http://maven./POM/4.0.0 http://maven./xsd/maven-4.0.0.xsd'> modelVersion>4.0.0modelVersion> groupId>com.vdian.feedcentergroupId> artifactId>feedcenter-pushartifactId> packaging>pompackaging> version>1.0.0.SNAPSHOTversion> modules> module>feedcenter-push-clientmodule> module>feedcenter-push-coremodule> module>feedcenter-push-webmodule> modules>project>
通过在一个打包方式为pom的Maven项目中声明任意数量的module以实现模块聚合:
- packaging: pom, 否则无法聚合构建.
- modules: 实现聚合的核心,module值为被聚合模块相对于聚合POM的相对路径, 每个被聚合模块下还各自包含有pom.xml、src/main/java、src/test/java等内容, 离开聚合POM也能够独立构建
若元素的内容是jar,那么我们很好理解,也就是说这个项目最终会被打包成一个jar包,那元素为pom又是什么意思呢?从字面上的意思来看,这个项目将打包成一个pom。我们不妨去maven仓库里去瞧瞧(前提是已经在项目下运行了mvn install命令)。可以发现这个文件其实和项目中的pom.xml是同一个文件,这样做的目的是什么呢?上面我们说过PO对象也是有继承关系的,pom的作用就在这里,这就是maven中project inheritance的概念。当实际执行maven命令的时候,会根据project inheritance关系对项目的pom.xml进行转化,得到真正执行时所用到的pom.xml,即所谓的effective pom,因此可以得到一个结论:所有元素为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 :::
每次都要敲这么长一串命令是很繁琐的,因此才有了上述的简写的形式。maven规定了对于plugin的artifactId是如下两种形式:
maven-{prefix}-maven-plugin
的可以使用简写的方式${prefix}来表示一个plugin.
模块继承
在面向对象中, 可以通过类继承实现复用. 在Maven中同样也可以创建POM的父子结构, 通过在父POM中声明一些配置供子POM继承来实现复用与消除重复
父pom
与聚合类似, 父POM的打包方式也是pom, 因此可以继续复用聚合模块的POM(这也是在开发中常用的方式):
project xmlns='http://maven./POM/4.0.0' xmlns:xsi='http://www./2001/XMLSchema-instance' xsi:schemaLocation='http://maven./POM/4.0.0 http://maven./xsd/maven-4.0.0.xsd'> modelVersion>4.0.0modelVersion> groupId>com.vdian.feedcentergroupId> artifactId>feedcenter-pushartifactId> packaging>pompackaging> version>1.0.0.SNAPSHOTversion> modules> module>feedcenter-push-clientmodule> module>feedcenter-push-coremodule> module>feedcenter-push-webmodule> modules> properties> finalName>feedcenter-pushfinalName> warName>${finalName}.warwarName> spring.version>4.0.6.RELEASEspring.version> junit.version>4.12junit.version> project.build.sourceEncoding>UTF-8project.build.sourceEncoding> warExplodedDirectory>exploded/${warName}warExplodedDirectory> properties> dependencyManagement> dependencies> dependency> groupId>org.springframeworkgroupId> artifactId>spring-coreartifactId> version>${spring.version}version> dependency> dependency> groupId>org.springframeworkgroupId> artifactId>spring-beansartifactId> version>${spring.version}version> dependency> dependency> groupId>org.springframeworkgroupId> artifactId>spring-contextartifactId> version>${spring.version}version> dependency> dependency> groupId>junitgroupId> artifactId>junitartifactId> version>${junit.version}version> scope>testscope> dependency> dependencies> dependencyManagement> build> pluginManagement> plugins> plugin> groupId>org.apache.maven.pluginsgroupId> artifactId>maven-source-pluginartifactId> version>3.0.0version> executions> execution> id>attach-sourcesid> phase>verifyphase> goals> goal>jar-no-forkgoal> goals> execution> executions> plugin> plugins> pluginManagement> build>project>
- dependencyManagement: 能让子POM继承父POM的配置的同时, 又能够保证子模块的灵活性: 在父POMdependencyManagement元素配置的依赖声明不会实际引入子模块中, 但能够约束子模块dependencies下的依赖的使用(子模块只需配置groupId与artifactId, 见下).
- pluginManagement: 与dependencyManagement类似, 配置的插件不会造成实际插件的调用行为, 只有当子POM中配置了相关plugin元素, 才会影响实际的插件行为.
project xmlns='http://maven./POM/4.0.0' xmlns:xsi='http://www./2001/XMLSchema-instance' xsi:schemaLocation='http://maven./POM/4.0.0 http://maven./xsd/maven-4.0.0.xsd'> parent> groupId>com.vdian.feedcentergroupId> artifactId>feedcenter-pushartifactId> version>1.0.0.SNAPSHOTversion> parent> modelVersion>4.0.0modelVersion> artifactId>feedcenter-push-clientartifactId> dependencies> dependency> groupId>org.springframeworkgroupId> artifactId>spring-coreartifactId> dependency> dependency> groupId>org.springframeworkgroupId> artifactId>spring-contextartifactId> dependency> dependency> groupId>org.springframeworkgroupId> artifactId>spring-beansartifactId> dependency> dependency> groupId>junitgroupId> artifactId>junitartifactId> dependency> dependencies> build> plugins> plugin> groupId>org.apache.maven.pluginsgroupId> artifactId>maven-source-pluginartifactId> plugin> plugin> groupId>org.apache.maven.pluginsgroupId> artifactId>maven-compiler-pluginartifactId> 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所提倡的约定.
project> modelVersion>4.0.0modelVersion> repositories> repository> id>centralid> name>Central Repositoryname> url>https://repo.maven./maven2url> layout>defaultlayout> snapshots> enabled>falseenabled> snapshots> repository> repositories> pluginRepositories> pluginRepository> id>centralid> name>Central Repositoryname> url>https://repo.maven./maven2url> layout>defaultlayout> snapshots> enabled>falseenabled> snapshots> releases> updatePolicy>neverupdatePolicy> releases> pluginRepository> pluginRepositories> build> directory>${project.basedir}/targetdirectory> outputDirectory>${project.build.directory}/classesoutputDirectory> finalName>${project.artifactId}-${project.version}finalName> testOutputDirectory>${project.build.directory}/test-classestestOutputDirectory> sourceDirectory>${project.basedir}/src/main/javasourceDirectory> scriptSourceDirectory>${project.basedir}/src/main/scriptsscriptSourceDirectory> testSourceDirectory>${project.basedir}/src/test/javatestSourceDirectory> resources> resource> directory>${project.basedir}/src/main/resourcesdirectory> resource> resources> testResources> testResource> directory>${project.basedir}/src/test/resourcesdirectory> testResource> testResources> pluginManagement> plugins> plugin> artifactId>maven-antrun-pluginartifactId> version>1.3version> plugin> plugin> artifactId>maven-assembly-pluginartifactId> version>2.2-beta-5version> plugin> plugin> artifactId>maven-dependency-pluginartifactId> version>2.8version> plugin> plugin> artifactId>maven-release-pluginartifactId> version>2.3.2version> plugin> plugins> pluginManagement> build> reporting> outputDirectory>${project.build.directory}/siteoutputDirectory> reporting> profiles> profile> id>release-profileid> activation> property> name>performReleasename> value>truevalue> property> activation> build> plugins> plugin> inherited>trueinherited> artifactId>maven-source-pluginartifactId> executions> execution> id>attach-sourcesid> goals> goal>jargoal> goals> execution> executions> plugin> plugin> inherited>trueinherited> artifactId>maven-javadoc-pluginartifactId> executions> execution> id>attach-javadocsid> goals> goal>jargoal> goals> execution> executions> plugin> plugin> inherited>trueinherited> artifactId>maven-deploy-pluginartifactId> configuration> updateReleaseInfo>trueupdateReleaseInfo> configuration> plugin> plugins> build> profile> profiles>project>
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./POM/4.0.0' xmlns:xsi='http://www./2001/XMLSchema-instance' xsi:schemaLocation='http://maven./POM/4.0.0 http://maven./maven-v4_0_0.xsd'> modelVersion>4.0.0modelVersion> groupId>com.fq.pluginsgroupId> artifactId>lc-maven-pluginsartifactId> packaging>maven-pluginpackaging> version>0.0.1-SNAPSHOTversion> dependencies> dependency> groupId>com.google.guavagroupId> artifactId>guavaartifactId> version>19.0version> dependency> dependency> groupId>org.apache.mavengroupId> artifactId>maven-plugin-apiartifactId> version>3.3.3version> dependency> dependency> groupId>org.apache.maven.plugin-toolsgroupId> artifactId>maven-plugin-annotationsartifactId> version>3.3version> dependency> dependencies>project>
maven-plugin 打包方式能控制Maven为其在生命周期阶段绑定插件处理的相关目标.
- 编写目标Mojo
@Mojo(name = 'lc', defaultPhase = LifecyclePhase.VERIFY)public class LCMavenMojo extends AbstractMojo { private static final List 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 resources; @Parameter(defaultValue = '${project.build.testResources}', readonly = true) private List testResources; @Parameter(property = 'lc.file.includes') private Set 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 lp = new LineProcessor() { 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 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 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.pluginsgroupId> artifactId>lc-maven-pluginsartifactId> version>0.0.1-SNAPSHOTversion> executions> execution> id>lcid> phase>verifyphase> goals> goal>lcgoal> goals> configuration> includes> include>javainclude> include>luainclude> include>jsoninclude> include>xmlinclude> include>propertiesinclude> 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.pluginspluginGroup> pluginGroups>
在达到以上两点之后,我们便可以通过以下命令来调用自己的插件了:
mvn lc:lc
要在别的项目中应用插件也是简单的,我们只需要在该项目的pom.xml文件中使用上面标签声明该插件即可。