maven插件和生命周期

 
maven有三套完全独立的生命周期,clean,default和site。
每套生命周期都可以独立运行,每个生命周期的运行都会包含多个phase,每个phase又是由各种插件的goal来完成的,一个插件的goal可以认为是一个功能。
这就是maven的生命周期 -> phase(可以理解为阶段) -> 插件的关系,也是maven构建执行的核心原理
你每次执行一个生命周期,都会依次执行这个生命周期内部的多个phase,每个phase执行时都会执行某个插件的goal完成具体的功能
notion image

maven的生命周期以及phase

clean生命周期包含的phase如下:
  1. pre-clean
  1. clean
  1. post-clean
default生命周期包含的phase如下:
validate:校验这个项目的一些配置信息是否正确 initialize:初始化构建状态,比如设置一些属性,或者创建一些目录 generate-sources:自动生成一些源代码,然后包含在项目代码中一起编译 process-sources:处理源代码,比如做一些占位符的替换 generate-resources:生成资源文件,才是干的时我说的那些事情,主要是去处理各种xml、properties那种配置文件,去做一些配置文件里面占位符的替换 process-resources:将资源文件拷贝到目标目录中,方便后面打包 compile:编译项目的源代码 process-classes:处理编译后的代码文件,比如对java class进行字节码增强 generate-test-sources:自动化生成测试代码 process-test-sources:处理测试代码,比如过滤一些占位符 generate-test-resources:生成测试用的资源文件 process-test-resources:拷贝测试用的资源文件到目标目录中 test-compile:编译测试代码 process-test-classes:对编译后的测试代码进行处理,比如进行字节码增强 test:使用单元测试框架运行测试 prepare-package:在打包之前进行准备工作,比如处理package的版本号 package:将代码进行打包,比如jar包 pre-integration-test:在集成测试之前进行准备工作,比如建立好需要的环境 integration-test:将package部署到一个环境中以运行集成测试 post-integration-test:在集成测试之后执行一些操作,比如清理测试环境 verify:对package进行一些检查来确保质量过关 install:将package安装到本地仓库中,这样开发人员自己在本地就可以使用了 deploy:将package上传到远程仓库中,这样公司内其他开发人员也可以使用了
site生命周期的phase:
  1. pre-site
  1. site
  1. post-site
  1. site-deploy

默认的phase和plugin绑定

但是问题来了,那么我们直接运行mvn clean package的时候,每个phase都是由插件的goal来完成的,phase和plugin绑定关系是?
实际上,默认maven就绑定了一些plugin goal到phase上去,比如:
类似于resources:resources这种格式,说的就是resources这个plugin的resources goal(resources功能,负责处理资源文件)
process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package jar:jar或者war:war install install:install deploy deploy:deploy
site生命周期的默认绑定是:
site site:site site-deploy site:deploy
clean生命周期的默认
clean clean:clean
到此为止,基本我们就很清楚maven的声明周期、phase和plugin的关系,同时也清楚我们运行类似mvn clean package之类的命令时,到底是在干啥了。。。。。。也知道为啥这个命令一执行,就可以实现清理、打包之类的功能更了。。。

maven的命令行和生命周期

比如mvn clean package
clean是指的clean生命周期中的clean phase package是指的default生命周期中的package phase
此时就会执行clean生命周期中,在clean phase之前的所有phase和clean phase,pre clean,clean 同时会执行default生命周期中,在package phase之前的所有phase和package phase
就是这个意思
mvn clean package
clean生命周期的pre clean,clean两个phase 但是,pre clean phase默认是没有绑定任何一个plugin goal的,所以默认什么也不会干;clean phase默认是绑定了clean:clean,clean plugin的clean goal,所以就会去执行clean插件的clean goal,就会实现一个功能,就是清理target目录下的文件
process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package jar:jar或者war:war install install:install deploy deploy:deploy
mvn clean install mvn clean deploy
mvn dependency:tree mvn deploy:deploy-file
就是不执行任何一个生命周期的任何一个phase
直接执行指定的插件的一个goal
比如mvn dependency:tree,就是直接执行dependency这个插件的tree这个goal,这个意思就是会自动分析pom.xml里面的依赖声明,递归解析所有的依赖,然后打印出一颗依赖树
mvn deploy:depoy-file,就是直接执行deploy这个插件的deploy-file这个goal,这个意思就是说将指定目录的jar包,以指定的坐标,部署到指标的maven私服仓库里去,同时使用指定仓库id对应的server的账号和密码。

插件

生命周期 -> phase -> plugin goal
mvn clean package,背后的一套原理
现在,引出来一个问题,就是默认情况下,你什么都不搞的话,其实就少数一些phase绑定了一些maven内置的插件的goal,相当于有大量的phase其实是空置的
另外一个问题,可能在我们使用maven的过程中,有很多各种各样其他的一些需求,我给大家举个两个最常见的例子:

第三方插件使用场景

(1)比如说,我们打出来的一个jar包,放到生产环境去跑的时候,实际上是需要将所有依赖的jar包都打在这个jar包里面的,这样的话,在实际程序运行的时候,才能找到所有需要的依赖。
默认maven的功能是不支持的,所以此时就需要用到maven其他的插件,就有很多人或者公司,为了增强maven的功能,基于maven实现各种各样酷炫或者实用的功能,于是基于maven的这一套生命周期+插件的机制,开发了各种各样的插件,然后呢,我们如果要使用别人的插件在我们的项目里实现某些特殊的功能,此时就可以在pom.xml里面配置那个插件,包括最重要的,配置那个插件绑定到哪个phase上去。
然后呢,在maven的命令执行的时候,不是默认会执行生命周期中的各种phase吗,如果你绑定了某个第三方的插件到phase,此时插件就会执行,然后实现我们想要的功能。
比如说,我们就可以用assembly的插件,配置在里面,打包的时候,将所有依赖的jar包都放入我们自己工程的jar包里。
 
(2)另外一个,就是我们可能需要的是,除了能运行单元测试代码以外,我们还需要在单元测试执行完之后,运行一个其他的插件,计算出单元测试对我们所有代码的一个覆盖率。就是说单元测试,测试了百分之多少的代码。此时也需要使用一个第三方的cobertura插件。
(3)比如我们后面会讲解,开发了一个web工程,需要在本地,在eclipse里面随时很轻易的就启动一个web容器,然后部署这个web工程,而不是用比较重型的eclipse+tomcat的那种方式。运行mvn命令,直接web容器也启动了,web工程也部署好了,接着打开浏览器,访问接口,就可以冒烟测试。这种情况下,也需要使用一个专门的jetty插件。绑定到一个phase,phase结束之后,就直接插件启动一个jetty容器,然后部署工程打好的war包到jetty容器中,对外提供访问。
 
就是百度搜索一下,找到你需要的那个功能对应的插件,然后搜一下那个插件配置的范例,接着在pom.xml里面配置那个插件,将插件绑定到某个phase。此时每次你执行mvn命令的时候,插件就可以被执行,然后提供更多的功能。
来学习如何配置插件,插件配置的语法是什么
学完了以后,我们以后应该彻底了解两个事情:
(1)你配置了插件以后,结合生命周期执行原理,了解插件执行的原理是什么 (2)你完全可以看懂眼花缭乱,乱七八糟的那些所谓的插件的配置 (3)我们这一讲不会去练习配置插件,因为后面插件有的你配置,我们上面讲解的那些功能,后面都是需要借助插件去实现的

插件和goal

plugin
每个插件都有多个goal,每个goal都是一个具体的功能
举个例子,dependency插件有十几个goal,可以进行分析项目依赖,列出依赖树,等等
插件的goal,写法就是plugin:goal,比如dependency:tree
用mvn dependency:tree,就可以手动执行某个插件的goal,执行某种功能
resources:resources,process-resources phase。

将插件的goal绑定到phase上

maven内置就会绑定一些插件的goal到phase上,
这里讲解一下,我们如果要使用某个maven插件,如何手动将插件的goal绑定到phase上
比如将source插件的jar-no-fork goal绑定到verify phase,在完成集成测试之后,就生成源码的jar包,这里可以看到绑定plugin的语法:
简单说一句,有不少开源的项目,不仅仅会给你提供下载一个可以立即使用的jar包,还会给你提供一个用src结尾的一个jar包,那个jar包里放的就是那个开源项目的源码。
 
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.1.1</version> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
 
运行mvn verify,就可以看到生成了一个包含源码的jar包
即使不配置绑定的phase也可以,因为大多数插件都默认内置了要绑定的phase,比如这个插件就内置绑定在package phase。

配置插件

如果在命令行执行插件,可以用-Dkey=value来进行插件的设置
比如mvn install -Dmaven.test.skip=true,就是surefire插件在测试的时候提供的参数,设置为true就会跳过测试
此外也可以在pom.xml中用来配置
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.1</version> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build>

找到你需要的插件

http://maven.apache.org/plugins/index.html中可以找到所有的插件,里面有文档
很简单,很接地气的办法,就是一般你可以百度搜索,用maven启动jetty插件,用maven部署tomcat插件

插件解析

先从本地仓库找插件,没有则从远程仓库找插件
插件的远程仓库也需要配置,maven默认配置了远程的插件仓库
<pluginRepositories> <pluginRepository> <id>central</id> <name>Maven Plugin Repository</name> <url>http://repo1.maven.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories>

常用插件

单元测试插件surefire

通常我们在开发完成之后,都会编写一些单元测试,但是随着系统日益增长,单元测试会很多很多很多,可能多大数百个,数千个,甚至是数万个。而我们每次如果要将开发好的代码跟别人开发的代码进行持续集成,或者是发布到测试环境联调测试,或者是直接上线,每次都是需要将所有的单元测试都运行一次的,因为这是保障系统质量非常重要的行为。如果所有单元测试都能测试通过,那么这个系统的质量也可以得到保障。
微服务这一块的架构,集成测试
假如说有某个哥儿们,写了一段代码,偷懒,嫌弃麻烦,就没运行所有的单元测试,就告诉你说,o了,哥儿们,我要上线了,你觉得你能相信他吗?
不能完全依靠对每个工程师的信任,说他每次修改代码肯定会去运行单元测试
持续集成,自动化集成测试
在大公司里面,都有持续集成的服务器,jenkins,hudson
那么实际上,持续集成,说的就是每个工程师当天写完代码之后,都必须将代码合并到一个公共的持续集成的代码分支上面去,一提交,要触发jenkins持续集成服务器,要立即自动化执行一次构建,自动化既有surefire这种插件,去运行所有的单元测试,确保你的代码没有把系统给改坏。
势必说要用到maven的自动化运行单元测试的插件,surefire
而我们如果手动进行测试的话,那么是无法想象的,此时就需要用maven来自动化运行单元测试。
maven中默认内置了surefire插件来运行单元测试,与最新流行的junit单元测试框架整合非常好。一般是在default生命周期的test阶段,会运行surefire插件的test goal,然后执行src/test/java下面的所有单元测试的。
而surefire插件会根据一定的规则在sre/test/java下面找单元测试类,具体规则如下:
  • /Test.java **/*Test.java **/*TestCase.java
通常比较流行的是用*Test.java格式类命名单元测试的类
2、跳过单元测试
如果某次你的确不想要执行单元测试就打包,那么可以跳过单元测试,但是在实际开发过程中,这是绝对不被允许的。
用mvn package -DskipTests即可
3、指定想要运行的测试类
mvn test -Dtest=**Test,直接指定你要运行的类即可
通常见于开发人员自己刚写了某个单元测试类,然后就想自己运行一下看看效果。可以用逗号隔开,也可以用*匹配多个类。
4、自定义包含与排除测试类
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <includes> <include>/*Tests.java</include> </includes> <excludes> <exclude>/*TempTest.java</exclude> </excludes> </configuration> </plugin>
5、测试报告
单元测试覆盖率是什么意思
比如说,你总共写了100行代码
然后你写了3个单元测试方法,结果这3个单元测试的方法会执行你的100行代码中的80行,另外有20行代码是无论运行哪个单元测试都不会执行的
此时单元测试覆盖率就是80 / 100,80%
一般单元测试覆盖率,要求的是,两种,方法覆盖率,行覆盖率
比如你一共写了10个方法,100行代码
方法覆盖率要达到90%,那么要求就是至少对10个方法中的9个方法都要运行 代码覆盖率要求70%,那么要求就是至少对70行代码都要执行到
分支覆盖率,如果你的代码中有if else,或者是try catch,相当于是你的一段代码根据不同的情况,可能会有2个分支,分支里面还可以再嵌套if else,再分出来2个分支
if if else else if else
2 * 2 = 4
你的单元测试可以覆盖几个分支,1/4,25%;2 / 4,50%分支覆盖率
surefire插件默认就会在target/surefire-reports目录下生成错误报告,就是如果有单元测试运行失败的话,就会有。
同时可以引入cobertura插件,可以看到测试覆盖率的报告
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.5.1</version> </plugin>
为什么不配置executions去指定这个插件的goal绑定到哪儿? 为什么不配置configuration特殊的参数
第一个问题,其实每个插件你只要定义它的坐标,默认情况下,它自己内部就带了要把哪些goal绑定到哪个phase上去
第二个问题,默认如果没有特殊的要求,用默认的参数就够了172941

web插件jetty

一般来说,我们在公司里面开发,都是一个常规的路径,写代码,写单元测试,运行单元测试,看一看单测覆盖率,覆盖率不达标,得继续写单元测试,覆盖大部分的代码
但是对于这个web工程来说,除了在本地单元测试以外,你还需要在本地启动web服务,然后调用一下web接口,简单的测试一下,做一个简单的冒烟测试,看看基本的功能是否正常
冒烟测试,微软那边传出来,开发工程师,不能完全依赖于测试工程师去帮你检测代码的质量,至少说你在把代码交付给测试工程师去检测之前,你需要用抽一根烟的时间,简单对你的代码核心和基础的功能,跑一下,看看,基本可以跑通。一般来说,测试工程师,吐槽最多的就是,开发工程师自己看都没看,写完代码就直接给我提测了。
加入jetty插件的配置
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>7.1.6.v20100715</version> <configuration> <scanIntervalSeconds>10</scanIntervalSeconds> <webAppConfig> <contextPath>/test</contextPath> </webAppConfig> </configuration> </plugin>
scanIntervalSeconds,指的是自动扫描项目代码变更的时间
contextPath,就是部署之后的contextPath
jetty插件的groupId不是默认的org.apache.maven.plugins,因此如果要执行该插件,需要在settings.xml中进行配置:
<pluginGroups> <pluginGroup>org.mortbay.jetty</pluginGroup> </pluginGroups>
接着mvn jetty:run,执行jetty插件的run goal,会用jetty容器启动web应用,默认绑定8080端口
用mvn jetty:run -Djetty.port=8081可以修改端口号
settings.xml文件里,有一个东西叫做pluginGroups
就是我们一般如果配置了插件以后,然后如果我们就直接在命令行运行jetty:run,cobertura:cobertura命令,其实用的是插件的缩写,jetty插件,cobertura插件。但是如果要将插件的缩写和插件自己的groupId对应起来的话,有的时候还是需要你去手动做一下配置的,不然在某些环境下可能直接运行类似jetty:run的命令可能会有问题。
但是有些插件,比如说dependency:tree,他的groupId,是org.maven.plugins,这个是默认的maven内核中的插件,所以不需要额外配置,但是对于我们上面说的两个插件,最好是配置一下。

部署war包插件

接下来一般情况下,就是要部署到集成测试环境,跟其他的同学部署的工程进行集成测试,或者如果没有这个环节,就是部署到QA测试环境,让测试工程师来进行测试。
这里就涉及到了web工程的部署
一般情况下,在很久很久以前,10年以前,很low的年代,怎么部署,弄一个测试服务器,上面装一个tomcat,然后呢先停止tomcat,然后把原来的那个项目的war包删除了,然后用eclipse的一个插件,export war包,导出来包含了所有依赖的这么一个war包,然后放到测试服务器的tomcat中,接着重新启动tomcat。
手工的操作,而且还要登录测试服务器,停止和启动tomcat,很麻烦,很容易出错
maven,自动化的部署
cargo插件,就是专门用来进行将本地的web工程打包成一个war包,然后呢扔到远程的服务器上的Tomcat中去,然后就是给你自动化的部署
在搞定了整个系统的单元测试,web测试之后,就需要将整个系统完成构建,打出一个完整的war包,然后部署到web容器里去了,此时就要用cargo插件来进行自动化的web部署
<plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <version>1.0</version> <configuration> <container> <containerId>tomcat6x</containerId> <type>remote</type> </container> <configuration> <type>runtime</type> <properties> <cargo.remote.username>admin</cargo.remote.username> <cargo.remote.password>admin</cargo.remote.password> <cargo.tomcat.manager.url>http://localhost:8080/manager</cargo.tomcat.manager.url> <cargo.servlet.port>8080</cargo.servlet.port> </properties> </configuration> </configuration> </plugin>
同样需要在settings.xml中加入pluginGroup:org.codehaus.cargo
然后执行mvn cargo:deploy即可部署
(1)先在本地安装一个tomcat 6 (2)给tomcat 6设置一个管理员,在conf/tomcat-users.xml, <user username="admin" password="admin" roles="manager"/> (3)修改tomcat的conf/server.xml,里面在8080那个地方,加入两个配置,URIEncoding="UTF-8" useBodyEncodingForURI="true" (4)启动tomcat
(5)在项目的pom.xml里加入cargo插件的配置,直接从我给的代码里面去拷贝 (6)在settings.xml里加入pluginGroup,org.codehaus.cargo (7)解决中文编码的问题,先在web.xml里加入一个字符集编码的过滤器,直接从代码里面拷贝