架构那点事系列四 - Maven优化篇

       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静态分析),除非你有非常完备的单元测试,否则我不建议去优化这块的~

        其次,当检测到依赖版本冲突时,Maven内建的版本冲突算法(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 container迁移时,需要做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的插件编写,后面有机会会和原理篇放一起,一起分享给大家吧,如果有什么不明白的,欢迎留言。


参考资料:

1. http://www.infoq.com/cn/maven-practice

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/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值