Ant的出现,填补了Java领域 compile kit的空白。而Maven的出现,则算是更近了一步(除了它之外,还有比较著名的同类编译套件IVY等)。构建在之上的CI(Sonar,Hudson,Jenkins等)构件为我们的项目管理带来了极大的方便。这篇文章,源自于工作中Maven的一些高级特性应用,开发后的不断思考,总结。希望能给大家带来一些帮助。
学习一门技术,先要了解它的历史,之后,没准你会和我一样,深深地迷上它。谈及Maven的发展历程,我们这里可以用此处省略。。。来概括。唯独不能省略的是,我们必须了解现行存在的三个并行版本:2.0.11,2.2.1,3.0.4。在工作中,我用后面两个版本对项目进行交叉验证,以便掌握之间的不同。
我们都知道Maven本质上是一个插件框架,它的核心并不执行任何具体的构建任务,所有这些任务都交给插件来完成,例如编译源代码是由maven-compiler-plugin完成的。进一步说,每个任务对应了一个插件目标(goal),每个插件会有一个或者多个目标,例如maven-compiler-plugin的compile目标用来编译位于src/main/java/目录下的主源码,testCompile目标用来编译位于src/test/java/目录下的测试源码。用户可以通过两种方式调用Maven插件目标。第一种方式是将插件目标与生命周期阶段(lifecycle phase)绑定,这样用户在命令行只是输入生命周期阶段而已,例如Maven默认将maven-compiler-plugin的compile目标与compile生命周期阶段绑定,因此命令mvn compile实际上是先定位到compile这一生命周期阶段,然后再根据绑定关系调用maven-compiler-plugin的compile目标。第二种方式是直接在命令行指定要执行的插件目标,例如mvn archetype:generate 就表示调用maven-archetype-plugin的generate目标,这种带冒号的调用方式与生命周期无关。认识上述Maven插件的基本概念能帮助你理解Maven的工作机制,不过要想更高效率地使用Maven,了解一些常用的插件还是很有必要的,这可以帮助你避免一不小心重新发明轮子。
Maven将插件体系分为两大块,Build plugins & Reporting plugins,可以参看官网http://maven.apache.org/plugins/index.html的说明。从另一个维度分类的话,可以分为
Bundled plugins & Third party plugins。下面列举出自己项目中定制化的Bundled plugins属性:
<!-- maven plugin version -->
<compiler.plugin.version>2.3.2</compiler.plugin.version>
<resource.plugin.version>2.5</resource.plugin.version>
<surefire.plugin.version>2.11</surefire.plugin.version>
<findbugs.plugin.version>2.3.2</findbugs.plugin.version>
<cobertura.plugin.version>2.5.1</cobertura.plugin.version>
<war.plugin.version>2.2</war.plugin.version>
<failsafe.plugin.version>2.11</failsafe.plugin.version>
<enforcer.plugin.version>1.0.1</enforcer.plugin.version>
为了项目编译方便,自己写了一套编译脚本,这里也和大家分享一下,先从直观上感受一下Maven的强大吧。
@echo off
:input_project_home
SET PROJECT_HOME=
SET /p PROJECT_HOME=【输入应用根目录,默认为%cd%】
if /i "%PROJECT_HOME%"=="" SET PROJECT_HOME=%cd%
if not exist "%PROJECT_HOME%" ECHO 找不到路径%PROJECT_HOME%
if not exist "%PROJECT_HOME%" goto input_project_home
cd /d %PROJECT_HOME%
ECHO 应用根目录为%PROJECT_HOME%
:mvn_command
ECHO ************************
ECHO 请选择需要执行的常用命令(注意:不执行测试但还是会编译测试文件和测试资源文件)
ECHO ************************
ECHO 1-生成eclipse工程文件(注意:如果无法正常通过,请先执行选项3或者4)
ECHO 2-编译打包,不执行测试
ECHO 3-编译打包(解压war包),不执行测试
ECHO 4-编译打包,并执行测试
ECHO 5-编译打包(解压war包),并执行测试
ECHO 6-执行整个工程的测试
ECHO 7-执行单个项目的测试
ECHO 8-启动jetty(关闭调试器)
ECHO 9-启动jetty(启动调试器)
ECHO 0-退出菜单
set isopt=
set /p isopt=【选择命令】
if /i "%isopt%"=="1" goto mvn_eclipse
if /i "%isopt%"=="2" goto mvn_install_not_test
if /i "%isopt%"=="3" goto mvn_install_not_test_unzip_war
if /i "%isopt%"=="4" goto mvn_install_with_test
if /i "%isopt%"=="5" goto mvn_install_with_test_unzip_war
if /i "%isopt%"=="6" goto mvn_all_test
if /i "%isopt%"=="7" goto mvn_project_test
if /i "%isopt%"=="8" goto mvn_jetty
if /i "%isopt%"=="9" goto mvn_jetty_debug
if /i "%isopt%"=="0" goto exit
echo "无效选项,请选择(0-9)"
goto mvn_command
:mvn_eclipse
cd %PROJECT_HOME%\all
echo 开始生成eclipse工程文件
call mvn eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true
goto mvn_end
:mvn_install_not_test
cd %PROJECT_HOME%\all
ECHO 当前路径为%cd%
echo 开始编译打包,但不执行测试
call mvn clean install -DskipTests=true
goto mvn_end
:mvn_install_not_test_unzip_war
cd %PROJECT_HOME%\all
ECHO 当前路径为%cd%
echo 开始编译打包(解压war包),但不执行测试
call mvn clean install -DskipTests=true -Dunzip.war=true
goto mvn_end
:mvn_install_with_test
cd %PROJECT_HOME%\all
ECHO 当前路径为%cd%
echo 开始编译打包,并且执行测试
call mvn clean install
goto mvn_end
:mvn_install_with_test_unzip_war
cd %PROJECT_HOME%\all
ECHO 当前路径为%cd%
echo 开始编译打包(解压war包),并且执行测试
call mvn clean install -Dunzip.war=true
goto mvn_end
:mvn_all_test
cd %PROJECT_HOME%\all
echo 开始执行整个工程的测试
call mvn clean test
goto mvn_end
:mvn_project_test
cd %PROJECT_HOME%
echo 当前路径为%cd%
echo 应用根目录为%PROJECT_HOME%
set /p subprj=【输入项目路径,如biz\service】
if /i "%subprj%"=="" goto mvn_project_test
if not exist "%PROJECT_HOME%\%subprj%" ECHO 找不到路径%PROJECT_HOME%\%subprj%
if not exist "%PROJECT_HOME%\%subprj%" goto mvn_project_test
cd %PROJECT_HOME%\%subprj%
echo 项目完整路径为%PROJECT_HOME%\%subprj%
echo 开始执行单个项目的测试
call mvn clean test
goto mvn_end
:mvn_jetty
cd %PROJECT_HOME%\web
echo 当前路径为%cd%
echo 启动jetty容器
call mvn jetty:run
:mvn_jetty_debug
cd %PROJECT_HOME%\web
echo 当前路径为%cd%
echo debug模式启动jetty容器
set MAVEN_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=52000,server=y,suspend=n
call mvn jetty:run
:mvn_end
cd %PROJECT_HOME%
pause
goto mvn_command
:exit
cd %PROJECT_HOME%
下面,我们一起看下,架构设计多模块Maven工程时,优化点有哪些?
首先,做依赖优化&插件优化时,我喜欢share配置,这完全仰仗于Maven的dependencyManagement,dependencyManagement两个标签,你不妨也试试?做依赖优化时,注意“未使用的,但声明的依赖”是比较难优化的(ASM 2静态分析),除非你有非常完备的单元测试,否则我不建议去优化这块的~
nearest definition)会帮助你选择最合适的版本。而对于传递依赖,则要使用依赖图来检测,正如Sonar里面的依赖矩阵一样。如:
如果大家喜欢手动解决冲突,可以使用maven的dependency,-X 或者是 M2eclipse 的dependency hierarchy来诊断
最后,对于profile和assemble的一些说明。在此之前,我不得不提一下著名理论COC。好的软件,一般都有比较合理的默认值。在做JVM调优时,我就偏爱使用jinfo和-XX:+PrintFlagsFinal(举个例子,如:java -server -XX:+UnlockExprimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+PrintFlagsFinal -XX:+AggressiveOpts version),很可惜Maven并没有类似的小功能,至少我现在还没发现(Maven只是提供了help插件,帮助我们查看其它插件的帮助信息,如你可以在命令行中输入:mvn help:describe -DgroupId=org.apache.maven.plugins -DartifactId=maven-compiler-plugin -Dfull=true 备注:可以使用这个命令 mvn help:expressions)。Maven的属性命名有这么几种约定,如下:
(1) env.X: Prefixing a variable with "env." will return the shell's environment variable. For example,
${env.PATH} contains the $path environment variable (%PATH% in Windows).
(2) project.x: A dot (.) notated path in the POM will contain the corresponding element's value.
For example: <project><version>1.0</version></project> is accessible via ${project.version}.
(3) settings.x: A dot (.) notated path in the settings.xml will contain the corresponding element's value.
For example: <settings><offline>false</offline></settings> is accessible via ${settings.offline}.
(4) Java System Properties: All properties accessible via java.lang.System.getProperties() are available
as POM properties, such as ${java.home}.
(5) x: Set within a <properties /> element or an external files, the value may be used as ${someVar}.
默认的属性可以通过查看这里http://docs.codehaus.org/display/MAVENUSER/MavenPropertiesGuide获得。同时,repository的配置也很重要,除了自己架设私服外,可以利用业界现有的开放仓库,我的配置一般如下:
<profile>
<id>mvnrepository</id>
<repositories>
<repository>
<id>mvnrepository</id>
<name>mvnrepository</name>
<url>http://mvnrepository.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
<profile>
<id>jboss</id>
<repositories>
<repository>
<id>jboss</id>
<name>mvnrepository</name>
<url>http://repository.jboss.org</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
<profile>
<id>jboss2</id>
<repositories>
<repository>
<id>JBoss repository</id>
<name>mvnrepository</name>
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
<profile>
<id>sonatype</id>
<repositories>
<repository>
<id>sonatype</id>
<name>mvnrepository</name>
<url>http://repository.sonatype.org/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
<profile>
<id>javaNet</id>
<repositories>
<repository>
<id>java.net-Public</id>
<name>Maven Java Net Snapshots and Releases</name>
<url>https://maven.java.net/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
<profile>
<id>maven2</id>
<repositories>
<repository>
<id>maven2</id>
<name>maven2</name>
<url>http://search.maven.org</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>sonatype</activeProfile>
<activeProfile>jboss</activeProfile>
<activeProfile>jboss2</activeProfile>
<activeProfile>mvnrepository</activeProfile>
<activeProfile>maven2</activeProfile>
<activeProfile>javaNet</activeProfile>
</activeProfiles>
有时,在做servlet迁移时,需要做deploy的兼容性测试。我通常会这么做,先在web module里面做如下声明:
<profiles>
<profile>
<id>jboss4x</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<containerId>jboss4x</containerId>
<url>
http://ovh.dl.sourceforge.net/sourceforge/jboss/JBoss-4.2.2.GA.zip
</url>
</properties>
</profile>
<profile>
<id>tomcat7x</id>
<properties>
<containerId>tomcat7x</containerId>
<url>http://www.apache.org/dist/jakarta/tomcat-7/v7.0.25/bin/
apache-tomcat-7.0.25.zip</url>
</properties>
</profile>
</profiles>
然后,复写cargo插件配置,如下:
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<container>
<containerId>${containerId}</containerId>
<zipUrlInstaller>
<url>${url}</url>
<installDir>${installDir}</installDir>
</zipUrlInstaller>
</container>
<configuration>
<properties>
<cargo.servlet.port>8280</cargo.servlet.port>
</properties>
</configuration>
</configuration>
</plugin>
后面,你就可以通过命令进行兼容性测试了,如:mvn install cargo:start 或者mvn cargo:start -Ptomcat7x。
至此,整篇文章就差不多了,关于Maven的插件编写,后面有机会会和原理篇放一起,一起分享给大家吧,如果有什么不明白的,欢迎留言。
参考资料:
2. http://www.sonatype.com/books/mvnref-book/reference/resource-filtering-sect-properties.html
3. http://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html
4. http://apollo.ucalgary.ca/tlcprojectswiki/index.php/Public/Project_Versioning_-_Best_Practices
5. https://community.jboss.org/wiki/MavenRepository
6. http://www.juvenxu.com/2010/12/31/infoq-maven-pom-refactoring-add-or-delete/