持续集成之Maven实战

来源:互联网 发布:淘宝网踩踏式垃圾桶 编辑:程序博客网 时间:2024/05/29 10:46

1. 概述

Maven是一个Java开发者比较熟悉的项目构建工具,旨在简化项目构建和自动化测试过程。


2. Maven的框架模型

Maven本质上是一个插件框架,其运行命令一般为:

mvn plugin-name : goal  -Dparameter=abc

例如在创建一个新Maven项目时,我们使用:mvn archetype:generate,或是部署一个项目文件:mvn deloy:deploy-file

这里的archetype和deploy都是maven的插件,generate和deploy-file意思是使用插件的某个方法。参数传递同Java的入口函数(Main)相同。

Maven对项目的结构描述放在pom.xml中,其中字段的含义在这就不一一赘述了。


Maven在构建项目时有三种执行流:default, clean, site

1. default 

Validate     //验证项目目录结构等是否完整

Compile    //编译

Test           //单元测试

Package  //打包

Integration-test  //集成测试

Verify      //测试包是否有效

Install     //将包安装到本地repository中,默认为.m2文件夹

Deploy  //将包部署到私有仓库中(nexus或artifactory)

当用户执行某一命令时,其之前的命令都会被执行一遍。例如用户输入mvn package,则Maven会依次运行Validate, Compile, Test, Package

2. clean: 清理所有构建生成的文件(目标class,jar包war包,资源文件等等)。用法mvn : clean。它的执行流程是Pre-clean, Clean, Post-clean

3. site: 生成详细的文档。用法mvn site 


3. 模块化管理

Maven的模块化管理非常简单,首先定义父项目为Sample,其中有三个子项目A,B,C,其中C是Web项目,也是最终的输出项目

3.1 父模块

Sample的pom文件如下

<pre name="code" class="html"><project ....><modelVersion>4.0.0</modelVersion><!-- 0.0.4 --><groupId>com.example.maven</groupId><artifactId>Sample</artifactId><version>0.0.1-SNAPSHOT</version><packaging>pom</packaging><properties><spring.version>3.6</spring.version><base.url>http://x.x.x.x:port</base.url></properties><distributionManagement><repository><id>maven.release</id><name>maven-releases</name><url>${base.url}/artifactory/libs-release-local</url></repository><snapshotRepository><id>maven.snapshots</id><name>maven-snapshots</name><url>${base.url}/artifactory/libs-snapshot-local</url></snapshotRepository></distributionManagement><modules><module>A</module><module>B</module><module>C</module></modules><dependencyManagement><dependencies><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.version}</version></dependencies></dependencyManagement><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.5.1</version></plugin></plugins></pluginManagement></build></project>


父项目中的packaging字段需为pom类型,即其只负责管理项目。

modules字段中定义了子项目

distributionManagement管理了打包上传的服务器仓库地址(这里我使用的是artifactory私有仓库)

注意:这里使用了<dependencyManagement> 和 <pluginManagement> ,而不是直接使用<dependencies>和<plugins>。这是应为如果直接使用dependencies,则所有的子项目都会继承依赖父项目的包,即使子项目并没有用到这个包。而把dependencies放入dependencyManagement中后,子项目继承父项目中对依赖的定义,而不会直接继承依赖包。即父项目只是在管理包的版本,而不管谁去用它。例如在A项目中,它必须显示声明

<dependencies><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></dependencies>
才会去使用spring-beans-3.6这个包。pluginManagement同理。

许晓斌在InfoQ的文章里还介绍了一种更加简练的包依赖管理。试想我们有7,8个子项目,依赖50-100个包,我们无法清晰的对这些包进行划分,即哪个子项目用了哪些包。可用下面的方法,新建一个空项目A-Dependency,项目的pom如下

<project>  ..  <artifactId>A-Dependency</artifactId>  <packaging>pom</packaging>  <version>1.0-SNAPSHOT</version>  <dependencyManagement>    <dependencies>        ...    </dependencies>  </dependencyManagement></project>

该项目专门用来管理A所需要的,或是一类包,然后在Sample的pom中作如下引用

<dependencyManagement>    <dependencies>        <dependency>          <artifactid>A-Dependency</artifactId>          <version>1.0-SNAPSHOT</version>          <type>pom</type>          <scope>import</scope>        </dependency>    </dependencies></dependencyManagement>
这里的scope使用了import


Maven Dependency有6个scope

Compile      //默认,编译和打包都会引用到该包

Provided    //编译时需要使用,但运行时环境会提供无需打包,例如tomcat, jboss等容器的包(servlet-api等等)

Runtime    //编译时无需使用但运行时需要,所以会一起打包,例如oracle jdbc driver(通过反射调用)

Test           // 只在单元测试时使用,无需打包

System     // 引用本地的Jar包

Import


3.2 子模块

因为C模块的输出为War包,我们直接看C的pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.example.maven</groupId><artifactId>Sample</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath></parent><artifactId>C</artifactId><packaging>war</packaging>...<build><finalName>C-WEB</finalName><resources><resource><directory>src/main/resources</directory><filtering>false</filtering><excludes><exclude>**/*.*</exclude></excludes></resource></resources><plugins><!-- war plugin --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><configuration><archive><addMavenDescriptor>false</addMavenDescriptor></archive><!-- Default: ${project.build.directory} --><outputDirectory>${package.outputDirectory}</outputDirectory><webResources><resource><directory>src/main/resources/${package.environment}</directory><targetPath>WEB-INF/classes</targetPath><filtering>true</filtering></resource><resource><directory>src/main/resources/config</directory><targetPath>WEB-INF/classes</targetPath><filtering>true</filtering></resource></webResources></configuration></plugin></plugins></build></project>
其中parent中定义了继承自哪一个父模块


4. maven-war Plugin 使用不通配置文件

首先,C项目的src/main/resource下有三个文件夹

config //存放所有不会改变的资源文件,例如log4j.properties

local //存放本地环境的资源文件

test  //存放测试环境的资源文件

再看build节点

resources节点实际上是使用了maven-resource插件,由于是maven的必装插件,所以无需使用plugin声明,在该节点中,我们使用了exclude方法过滤了所有的src/main/resouces文件夹下的资源文件。即resource下的资源文件不会输出到WEB-INF/classes下。

而在War-Plugin中,我们将需要的资源文件拷贝到相应的文件夹中进行打包。其中${package.environment}代表一个参数变量,用户可以有两种方法传入,一是使用properties节点,二是在敲命令行时传入。例如mvn package -Dpackage.environment=test,此时就会把src/main/resource/test文件夹下的资源文件放入WEB-INF/classes中

outputDirectory定义了war包的输出地址

注意:maven-resouce和maven-war-plugin执行的节点不一样,resource是在maven编译时执行,war-plugin是在package时执行,我们所做的操作实际上是拷贝资源文件。

5. jetty-maven-plugin Web应用集成测试

在集成测试Web应用时,通常有这么几步:启动Web容器,部署待测试Web应用,以Web客户端的角色运行测试用例,停止Web容器

首先需要启动Web容器,部署应用和关闭容器,在build节点中添加如下

<plugin>        <groupId>org.mortbay.jetty</groupId>        <artifactId>jetty-maven-plugin</artifactId>        <version>6</version>        <configuration>          <stopPort>9090</stopPort>          <stopKey>stop-jetty-inthatport</stopKey>        </configuration>        <executions>          <execution>            <id>start-jetty</id>            <phase>pre-integration-test</phase>            <goals>              <goal>run</goal>            </goals>            <configuration>              <daemon>true</daemon>            </configuration>          </execution>          <execution>            <id>stop-jetty</id>            <phase>post-integration-test</phase>            <goals>              <goal>stop</goal>            </goals>          </execution>        </executions>      </plugin>
其中execution中的goal表明要使用哪个方法(目标),phase表示在哪个阶段启动这个方法。其他具体配置详见http://www.eclipse.org/jetty/documentation/current/jetty-maven-plugin.html

为了测试Web应用是否正确部署,我们需要用Junit些一些测试用例,并使用maven-surefire-plugin(test阶段默认使用的plugin)验证测试

<plugin>        <groupId>org.apache.maven.plugins</groupId>        <artifactId>maven-surefire-plugin</artifactId>        <version>2.7.2</version>        <executions>          <execution>            <id>run-integration-test</id>            <phase>integration-test</phase>            <goals>              <goal>test</goal>            </goals>            <configuration>              <includes>                <include>**/*IntegrateT.java</include>              </includes>            </configuration>          </execution>        </executions>      </plugin>
默认的情况下surefire会取*Test.java做单元测试,所以给我们的集成测试取名*IntegrateT.java
在IntegrateTestJava中可以使用类似下面的方法去验证Web应用是否正确启动

HttpClient httpclient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet( "http://localhost:8080/C-Web/index.html" );
HttpResponse response = httpclient.execute( httpGet );

Assert.assertEquals(200, response.getStatusLine().getStatusCode() );


6. 正确的集成命令

再此,直接引用许http://www.infoq.com/cn/articles/xxb-maven-4-ci的原文:


在持续集成服务器上使用怎样的 mvn 命令集成项目,这个问题乍一看答案很显然,不就是 mvn clean install 么?事实上比较好的集成命令会稍微复杂些,下面是一些总结:
不要忘了clean: clean能够保证上一次构建的输出不会影响到本次构建。
使用deploy而不是install: 构建的SNAPSHOT输出应当被自动部署到私有Maven仓库供他人使用,这一点在前面已经详细论述。
使用-U参数: 该参数能强制让Maven检查所有SNAPSHOT依赖更新,确保集成基于最新的状态,如果没有该参数,Maven默认以天为单位检查更新,而持续集成的频率应该比这高很多。
使用-e参数:如果构建出现异常,该参数能让Maven打印完整的stack trace,以方便分析错误原因。
使用-Dmaven.repo.local参数:如果持续集成服务器有很多任务,每个任务都会使用本地仓库,下载依赖至本地仓库,为了避免这种多线程使用本地仓库可能会引起的冲突,可以使用-Dmaven.repo.local=/home/juven/ci/foo-repo/这样的参数为每个任务分配本地仓库。
使用-B参数:该参数表示让Maven使用批处理模式构建项目,能够避免一些需要人工参与交互而造成的挂起状态。
综上,持续集成服务器上的集成命令应该为 mvn clean deploy -B -e -U -Dmaven.repo.local=xxx 。此外,定期清理持续集成服务器的本地Maven仓库也是个很好的习惯,这样可以避免浪费磁盘资源,几乎所有的持续集成服务器软件都支持本地的脚本任务,你可以写一行简单的shell或bat脚本,然后配置以天为单位自动清理仓库。需要注意的是,这么做的前提是你有私有Maven仓库,否则每次都从Internet下载所有依赖会是一场噩梦。


7. 持续集成

本地(一般是Windows平台)的构建,单元测试,集成测试成功了。但是目标平台(例如linux)可能会发生未知的错误。所以建议使用自动化持续集成工具。例如Hudson,

在目标服务器上安装Hudson, 让其去取VCS(Version Control System)的最新代码,调用Maven进行构建。Hudson和git, svn, cvs都有很好的集成,再次不赘述了。



总之,Maven很强大,是Java开发,敏捷团队的必备工具







0 0
原创粉丝点击