好久没有写豆知识系列了。
记录下使用中的一些问题。
eclipse中maven项目的bug
两个项目同时在开发,在同一个workspace,项目A依赖项目B。项目A会直接使用项目B中的代码(B的最新代码还没有install或deploy),而不是maven repository中的jar包。
换言之,我在项目B中加了一些新的类,eclipse中的项目A也可以使用,正常编译。
但如果在命令行中mvn clean package
,就肯定会失败,ClassNotFound。
对于正常的项目而言,改下版本号就可避免。项目A依赖的项目B版本和项目B正在开发的版本不同,就会直接依赖repo中的jar包。
但我碰到的情况是项目A依赖了一个SNAPSHOT版本。而项目B也正在这个SNAPSHOT版本上开发。有点蛋疼。
只能记着多deploy了。
不确定这是bug,也许有配置可以改,但我没找到。
skipTests与maven.test.skip
首先要知道maven生命周期。
skipTests只是maven-surefire-plugin插件的一个参数,这个插件是maven自带的,默认绑定到test阶段。设置这个参数后,test阶段不会执行测试。
而maven.test.skip会影响更多插件,包括maven-resources-plugin(testResource阶段),maven-compiler-plugin(testCompile阶段),maven-surefire-plugin(test阶段)。换言之,和测试有关的所有阶段都会起作用。
所以maven.test.skip是更彻底的跳过测试的方法。
直观的感受,就是如果src/test/java下的代码编译有问题,加上maven.test.skip就能防止整个项目构建失败。而只用skipTests则会在testCompile阶段失败。
但maven.test.skip也可能造成一些奇怪的问题,见这里。
report插件
maven本质上来说就是个插件的集合。
而插件分为两种,build插件和report插件。build插件会在default生命周期生效,配置在POM中的<build/>
元素里;report插件会在site生命周期生效,配置在POM中的<reporting/>
元素里。
根据配置的插件不同,mvn site
命令会在target目录下生成不同的html报告,帮你了解项目的一些概况。
虽然大多数项目用不到,但挺好玩的。所以研究了下。
常见的report插件(有一些插件既是build插件也是report插件):
插件名 | 说明 |
---|---|
maven-project-info-reports-plugin | 这个其实是自带的,有很多内置的报告 |
maven-pmd-plugin | 代码静态检查 |
maven-jxr-plugin | 将代码以html方式呈现 |
findbugs-maven-plugin | 另一个代码静态检查,但findbug是分析class文件,而不是java文件 |
jdepend-maven-plugin | 一些统计信息 |
taglist-maven-plugin | 汇总代码中的TODO,也可自定义其他的tag |
maven-surefire-report-plugin | 生成单元测试的报告 |
cobertura-maven-plugin | 检查单元测试覆盖率,配置可以很复杂 |
maven-javadoc-plugin | 生成javadoc |
maven-checkstyle-plugin | 强迫症福音,检查代码风格,比如方法名/变量名/空格/每个方法不超过多少行 |
每个插件都有自己的配置,可以去看相应的官方文档。
另外对于多模块的项目而言,mvn site
只会在各个模块的target目录下生成报告,而不会汇总起来。可以用mvn site:stash
命令汇总到父项目的target目录,方便查看。
但这个命令要求POM中做些修改:
|
|
jetty plugin的配置
jetty plugin用来调试很方便,但网上很多文章给的配置都是6.x老版本的。
jetty现在已经由eclipse基金会维护了。新版的配置项和6.x很不一样,见官方文档。
例子:
|
|
依赖版本冲突
如果在项目中引入同一个依赖的不同版本,会发生什么情况?
maven会自动帮你选择一个版本,这里又分两种情况:
如果是直接声明的依赖,后声明的生效。例如:
|
|
如果是间接引入的依赖,规则会更复杂,参考这个。
简言之,深度较小的优先;如果深度相同,先声明的优先。例如:
|
|
如果上面两种情况混合起来,就更蛋疼了。。。例子:
|
|
最终生效的依赖是slf4j-jdk14-1.6.1、slf4j-api-1.6.1、slf4j-nop-1.6.0。
enforcer plugin
上面说过依赖版本冲突时maven会自动帮你选择一个版本,但这样可能有隐患。例如某个模块依赖于guava 18.0的一些新功能,而maven却自动选择了guava 15.0。运行的时候很可能会报错,比如ClassNotFound或NoSuchMethod。如果编译正常,但运行时出现这两种异常,很可能是jar包版本冲突。
版本冲突还可能导致其他很多奇怪的问题。
所以最佳实践是不要有版本冲突。检测版本冲突就要用到enforcer plugin,maven的爱之铁拳。。。
简言之,可以配置一些强制性的规则,如果规则不满足,build会失败。不光可以用来检测版本冲突,还可以做很多其他事情,还可以自己编写规则,又一个强迫症福音。。。
例子:
|
|
enforcer plugin默认会绑定到validate阶段,详细文档见这里。
插件的configuration元素
折腾enforcer插件的时候,发现一个奇怪的问题。mvn clean package
时,插件可以正常生效;mvn enforcer:enforce
却会报错,说找不到相关配置。
google了一下,找到这个。
简单的说,配置一个插件的<configuration>
元素时,可以直接配置,比如:
|
|
这样的配置对这个插件在所有情况下都生效。
也可以配置在<execution>
元素中,比如:
|
|
这样的配置只会在maven执行到特定生命周期时才会生效。
对enforcer插件而言,默认绑定在validate阶段,所以configuration元素只有在validate阶段才生效。
所以直接执行mvn enforcer:enforce
才会报错。
默认插件
maven默认会将一些插件绑定到特定生命周期,比如clean插件、compile插件、resource插件等。这个是在哪里配置的?
答案是$M2_HOME/lib/maven-core-x.y.z.jar
文件中,解压这个文件可以找到一个xml文件,配置了所有默认的插件和绑定的阶段。参考官方文档:1、2。
本来以为是在超级pom中配置的,实际不是。
插件绑定
一个插件可以有多个goal,每个goal可以绑定到特定的生命周期上。
有些goal会有默认的绑定,比如enforcer:enforce
默认会绑定到validate
阶段。这种插件在配置execution元素时可以不用配置phase。
有些goal没有默认绑定,比如jetty:run
,如果要在build过程中自动执行,配置execution元素时就必须明确指定phase。当然也可以不在build过程中执行,直接mvn jetty:run
去执行特定插件的特定goal。
这个是由编写插件时的@Mojo
注解决定的,见官方文档。
另外pluginManagement和dependencyManagement的语义有些不同。pluginManagement中的插件即使不配置在plugins中,也可以直接使用,比如mvn jetty:run
。而dependencyManagement中的元素,必须在dependency中再配置一遍才能生效。