`
lushuaiyin
  • 浏览: 673616 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

maven入门

 
阅读更多

1. Maven入门--概念与实例

最近由于工作原因在研究、应用Maven,有了一些体会就 写成了此文。本文虽然是Maven2的入门文章,但并不涉及Maven的历史、下载与安装,这些内容可以到Maven的官方网站上了解。本文主要是关注 Maven中的重要概念,并以一个实例来阐述使用Maven的基本方法。文末有例子代码下载的链接。(2007.01.02最后更新)
注:转载时请注明原作者(jiangshachina)及出处(
http://www.blogjava.net/jiangshachina)!

1 关键名词
Project
:任何您想build的事物,Maven都可以认为它们是工程。这些工程被定义为工程对象模型(POM,Poject Object Model)。一个工程可以依赖其它的工程;一个工程也可以由多个子工程构成。
POM:POM(pom.xml)是Maven的核心文件,它是指示Maven如何工作的元数据文件,类似于Ant中的build.xml文件。POM文件位于每个工程的根目录中。
GroupId:groupId是一个工程的在全局中唯一的标识符,一般地,它就是工程名。groupId有利于使用一个完全的包名,将一个工程从其它有类似名称的工程里区别出来。
Artifact:artifact 是工程将要产生或需要使用的文件,它可以是jar文件,源文件,二进制文件,war文件,甚至是pom文件。每个artifact都由groupId和 artifactId组合的标识符唯一识别。需要被使用(依赖)的artifact都要放在仓库(见Repository)中,否则Maven无法找到 (识别)它们。
Dependency:为了能够build或运行,一个典型的Java工程会依赖其它的包。在Maven中,这些被依赖的包就被称为dependency。dependency一般是其它工程的artifact。
Plug-in:Maven是由插件组织的,它的每一个功能都是由插件提供的。插件提供goal(类似于Ant中的target),并根据在POM中找到的元数据去完成工作。主要的Maven插件要是由Java写成的,但它也支持用Beanshell或Ant脚本写成的插件。
Repository:仓库用于存放artifact,它可以是本地仓库,也可以是远程仓库。Maven有一个默认的远程仓库--central,可以从http://www.ibiblio.org/maven2/下载其中的artifact。在Windows平台上,本地仓库的默认地址是User_Home\.m2\repository。
Snapshot:工程中可以(也应该)有一个特殊版本,它的版本号包括SNAPSHOT字样。该版本可以告诉Maven,该工程正处于开发阶段,会经常更新(但还未发布)。当其它工程使用此类型版本的artifact时,Maven会在仓库中寻找该artifact的最新版本,并自动下载、使用该最新版。
2 Maven Build Life Cycle
软件项目一般都有相似的开发过程:准备,编译,测试,打包和部署,Maven将上述过程称为Build Life Cycle。在Maven中,这些生命周期由一系列的短语组成,每个短语对应着一个(或多个)操作;或对应着一个(或多个)goal(类似于Ant中的 target)。
如编译源文件的命令mvn compile中的compile是一个生命周期短语。同时该命令也可以等价于mvn compiler:compile,其中的compiler是一个插件,它提供了compile(此compile与mvn compile中的compile意义不同)goal;compiler还可提供另一个goal--testCompile,该goal用于编译junit测试类。
在执行某一个生命周期时,Maven会首先执行该生命周期之前的其它周期。如要执行compile,那么将首先执行validate,generate- source,process-source和generate-resources,最后再执行compile本身。关于Maven中默认的生命周期短语,请见参考资源[6]中的附录B.3
3 标准目录布局
Maven为工程中的源文件,资源文件,配置文件,生成的输出和文档都制定了一个标准的目录结构。Maven鼓励使用标准目录布局,这样就不需要进行额外 的配置,而且有助于各个不同工程之间的联接。当然,Maven也允许定制个性的目录布局,这就需要进行更多的配置。关于Maven的标准目录布局,请见参考资源[6]中的附录B.1
4 Maven的优点
[1]build逻辑可以被重用。在Ant中可能需要多次重复地写相同的语句,但由于POM的继承性,可以复用其它的POM文件中的语句。这样既可以写出清晰的build语句,又可以构造出层次关系良好的build工程。
[2]不必关注build工作的实现细节。我们只需要使用一些build生命周期短语就可以达到我们的目标,而不必管Maven是如何做到这些的。如,只需要告诉Maven要安装(install),那么它自然就会验证,编译,打包,及安装。
[3]Maven会自动加载工程依赖的artifact所依赖的其它artifact(Transitive Dependency),而不用显示的将这些artifact全部写到dependency中。
[4]如果完全使用Maven的标准目录布局,那么可以极大地减少配置细节。
5 实例
5.1 构想

由于只是阐述Maven的基本使用方法,所以本文将要设计的实例,只是一个简单的Maven demo。该实例包含两个工程:普通应用程序工程(app)和Web应用工程(webapp)。app工程提供一个简单的Java类;webapp工程只 包含一个Servlet,并将使用app中的Java类。
该Demo的目标是能够正确地将webapp制成war包,以供部署时使用。要能够正确制作war,自然首先就必须要能够正确的编译源代码,且要将App模块制成jar包。本文创建的工程所在的目录是D:\maven\demo。
5.2 App工程
可以使用Maven的archetype插件来创建新工程,命令如下:
D:\maven\demo>mvn archetype:create -DgroupId=ce.demo.mvn -DartifactId=app
该工程的groupId是ce.demo.mvn,那么该工程的源文件将放在Java包ce.demo.mvn中。artifactId是app,那么该工程根目录的名称将为app。
当第一次执行该命令时,Maven会从central仓库中下载一些文件。这些文件包含插件archetype,以及它所依赖的其它包。该命令执行完毕后,在目录D:\maven\demo下会出现如下目录布局:

app
|--pom.xml
`--src
|--main
|`--java
|`--ce
|`--demo
|`--mvn
|`--App.java
`--test
`--java
`--ce
`--demo
`--mvn
`--AppTest.java

因本文暂时不涉及JUnit测试,故请将目录app\src\test目录删除(不删除也没关系 ^_^)。然后再修改App.java文件,其完全内容如下:

packagece.demo.mvn;
publicclassApp {
publicStringgetStr(Stringstr) {
returnstr;
}
}

其实,如果我们能够清楚地知道Maven的标准目录布局,就可以不使用archetype插件来创建工程原型;如果我们要定制个性的目录布局,那么就更没有必要使用archetype插件了。
5.3 WebApp工程
我们仍然如创建app工程一样使用archetype插件来创建webapp工程,命令如下:
D:\maven\demo>mvn archetype:create -DgroupId=ce.demo.mvn -DartifactId=webapp -DarchetypeArtifactId=maven-archetype-webapp
第一次运行此命令时,也会从central仓库中下载一些与Web应用相关的artifact(如javax.servlet)。此命令与创建app的命 令的不同之处是,多设置了一个属性archetypeArtifacttId,该属性的值为maven-archetype-webapp。即告诉 Maven,将要创建的工程是一个Web应用工程。创建app工程时没有使用该属性值,是由于archetype默认创建的是应用程序工程。同样的,执行 完该命令之后,会出现如下标准目录布局:

webapp
|--pom.xml
`--src
`--main
`--webapp
|--index.jsp
|--WEB-INF
`--web.xml

根据5.1节的构想,webapp工程将只包含一个Servlet,所以我们不需要index.jsp文件,请将其删除。此时大家可以发现,目前的目录布局中并没有放Servlet,即Java源文件的地方。根据参考资源[6]中的附录B.1,以及app工程中Java源文件的布局,可以知道 Servlet(它仍然是一个Java类文件)仍然是放在webapp\src\main\java目录中,请新建该目录。此处的Servlet是一个简单HelloServlet,其完整代码如下:

packagehello;

importjava.io.IOException;
importjava.io.PrintWriter;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;

importce.demo.mvn.App;//引用app工程中的App类

publicclassHelloServletextendsHttpServlet {
privatestaticfinallongserialVersionUID=-3696470690560528247L;
publicvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)
throwsServletException,IOException {
Appapp=newApp();
Stringstr=app.getStr("CEMavenDemo");
PrintWriterout=response.getWriter();
out.print("<html><body>");
out.print("<h1>"+str);
out.print("</body></html>");
}
}

5.4 POM文件
大家可以发现,在前面新建工程时,我们并没有提到各个工程中的pom.xml文件。现在将要讨论这个问题。我们先看看app工程中的POM文件,其完整内容如下:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>ce.demo.mvn</groupId>
<artifactId>app</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>CEMavenDemo--App</name>
</project>

大家可以发现此我帖出来的内容与实际由archetype插件生成的POM文件的内容有些不同,但基本上是一致的。只是为了使文件中的语句更清晰,此处删除了一些冗余的内容,并修改了该工程的version和name的值,以与此例子的背景来符合。在目前情况下modelVersion值将被固定为 4.0.0,这也是Maven2唯一能够识别的model版本。groupId,artifactId的值与创建工程时使用的命令中的相关属性值是一致的。packaging的值由工程的类型决定,如应用程序工程的packaging值为jar,Web应用工程的packaging值为war。上述情况 也可以从webapp的POM文件中看出,下面将看看这个pom的完整内容。

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>ce.demo.mvn</groupId>
<artifactId>webapp</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>CEMavenDemo--WebApp</name>

<dependencies>
<dependency>
<groupId>ce.demo.mvn</groupId>
<artifactId>app</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

比较app与webapp中的POM,除前面已经提过的packaging的差别外,我们还可以发现webapp中的POM多了dependencies 项。由于webapp需要用到app工程中的类(见HelloServlet源代码),它还需要javax.servlet包(因为该包并不默认存在于 jsdk中)。故,我们必须要将它们声明到依赖关系中。
5.5 执行
上述两个工程创建完毕后,就需要执行一些命令来看看会有什么结果出现。我们首先进入app目录,并执行命令mvn compile,然后会在该目录下发现新生成的目录target\classes,即编译后的class文件(包括它的包目录)就放在了这里。再执行命令mvn package,在目录target中就会生成app-1.0.jar文件。该文件的全名由如下形式确定:artifactId-version.packaging。根据第2章的叙述可以知道,执行命令mvn package时,将首先将产生执行命令mvn compile之后的结果,故如果要打包,那么只需要执行mvn package即可。
在app工程中执行完之后,就需要进入webapp工程了。进入webapp目录,此次将只执行mvn package命令(隐示地执行了compile过程)。此次命令的执行并不成功,会出现如下问题:

D:\maven\demo\webapp>mvnpackage
……
Downloading:http://repo1.maven.org/maven2/ce/demo/mvn/app/1.0/app-1.0.pom
[
INFO]------------------------------------------------------------------------
[ERROR]BUILDERROR
[INFO]------------------------------------------------------------------------
[INFO]ErrorbuildingPOM(maynotbethisproject'sPOM).
ProjectID:ce.demo.mvn:app
Reason:ErrorgettingPOMfor'ce.demo.mvn:app'fromtherepository:Errortransferringfile
ce.demo.mvn:app:pom:1.0
fromthespecifiedremoterepositories:
central(http://repo1.maven.org/maven2)
……

由粗体内容可知,Maven正试图从central仓库下载app工程的artifact,但central仓库肯定不会有这个artifact,其结果 只能是执行失败!由第1章artifact名词的解释可知,被依赖的artifact必须存在于仓库(远程或本地)中,但目前webapp所依赖的app 必不存在于仓库中,所以执行只能失败。
解决这个问题有两种方法:[1]将app-1.0.jar安装到仓库中,使它成为一个artifact;[2]构建一个更高层次的工程,使app和webapp成为这个工程的子工程,然后从这个更高层次工程中执行命令。
第一种方法比较简单(见http://www.blogjava.net/jiangshachina/archive/2006/09/11/68944.html中的第一个主题,(或 其他 部分)),此处将详细讨论第2种方法(见5.6节)。
5.6 更高层次工程
我们可以将app和webapp的上一级目录demo作为这两个工程的 一个 更高层次工程,即使用app和webapp成为这个工程的子工程。为了使demo目录成为一个demo工程,只需要在这个目录下添加一个pom.xml文件,该文件内容如下:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>ce.demo</groupId>
<artifactId>mvn-demo</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<name>CEMavenDemo</name>

<modules>
<module>app</module>
<module>webapp</module>
</modules>
</project>

与app和webapp中的POM相比,demo的POM使用了modules项,modules用于声明本工程的子工程,module中的值对应于子工程的artifact名。而且该POM的packaging类型必须为pom。
有了demo工程后,我们只需要在demo目录下执行相关命令就可以了。通过如下命令即可验证:
[1]mvn clean – 消除工程(包括所有子工程)中产生的所有输出。这本文的实例中,实际上是删除target目录。由于之前的操作只有app工程产生了target目录,而webapp并没有,所以将只会删除app工程中的target目录。
[2]mvn package – 将工程制作成相应的包,app工程是作成jar包(app-1.0.jar),webapp工程是作成war包(webapp-1.0.war)。打开webapp-1.0.war包,可以发现app-1.0.jar被放到了WEB-INF的lib目录中。
6 小结
通过以上的叙述与实例,应该可以对Maven有一个粗略的认识了。使用Maven关键是要弄清楚如何写pom.xml文件,就如同使用Ant要会写 build.xml文件一样。在POM中可以直接写入Ant的task脚本,也可以调用Ant的build.xml文件(推荐),所以Maven也可以完 成Ant的绝大多数工作(但不必安装Ant)。注意:使用Maven就不要再过多的使用Ant脚本
利用好Maven的继承特性及子工程的关系,可以很好地简化POM文件,并能够构建层次结构良好的工程,有利于工程的维护。
7 参考资源
[1]Maven官方网站. http://maven.apache.org
[2]Maven POM文件参考结构. http://maven.apache.org/ref/current/maven-model/maven.html
[3]Super POM. http://maven.apache.org/guides/introduction/introduction-to-the-pom.html
[4]Maven主要插件的列表. http://maven.apache.org/plugins
[5]Maven基本使用指南. http://maven.apache.org/guides/index.html
[6]Better Build with Maven. http://www.mergere.com/m2book_download.jsp -- 强烈推荐
[7]介绍Maven2. http://www.javaworld.com/javaworld/jw-12-2005 /jw-1205-maven_p.html
[8]揭秘Maven2 POM. http://www.javaworld.com/javaworld/jw-05-2006/jw-0529-maven.html
[9]Maven让事情变得简单. http://www-128.ibm.com/developerworks/cn/java/j-maven
[10]Maven文档集. http://docs.codehaus.org/display/MAVENUSER/Home
[11]有效利用Maven2的站点生成功能. http://www.matrix.org.cn/resource/article/44/44491_Maven2.html
文中例子程序下载:http://www.blogjava.net/files/jiangshachina/maven.rar

2. Maven入门--较复杂的实例

1 实例的构想
文章开头的摘要已经讲述了,本文仍然将以一个实例描述如何使用Maven,该实例将使用非Maven标准的目录结构,并将呈现一些关键的Maven插件的配置与应用。该实例是一个基于db4o的数据库Web应用。该应用本身十分简单,即从db4o数据库中查询出若干记录并将它们显现在Web页面中。
该实例仍然由一个普通应用工程(demo-app)与一个Web应用工程(demo-web),以及这两个工程的父工程(demo)构成,最终的目标是将 Web应用工程制作成war文件,并部署到JBoss服务器中。启动服务器后,能够在页面中看到正确的查询结果。
该实例使用Eclipse3.2 + JDK1.5.0_10 + Windows2000开发。当然这仅仅只是我个人的开发平台,但该实例并不受限于此平台;由于我选择使用db4o针对JDK1.5的产品包,所以该实例只能运行在JDK1.5.0或更高版本的JDK/JRE中; 该工程中的所有文件都使用UTF-8编码方式。

2 demo工程
demo工程是其它两个工程的父工程,它的主要职责是预定义子工程所需要依赖的jar文件(artifact),以及针对子工程所使用的插件进行通用配置。该工程完整的POM文件如下所示:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mvn.demo</groupId>
<artifactId>demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<description>MavenDemoProject</description>

<modules>
<module>demo-app</module>
<module>demo-web</module>
</modules>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>mvn.demo</groupId>
<artifactId>demo-app</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>mvn.demo</groupId>
<artifactId>demo-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.db4o</groupId>
<artifactId>db4o-java5</artifactId>
<version>5.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.2</version>
<exclusions>
<exclusion>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
</exclusion>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
<exclusion>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<charset>UTF16</charset>
</configuration>
</plugin>
</plugins>
</build>
</project>

预定义工程的依赖关系,就是把会被子工程依赖的artifact的详细信息(groupId,artifactId,version,...)先声明到 <dependencyManagement>中。然后子工程只需要声明使用某个artifact就可以了,即那时只需要设置groupId 和artifactId(甚至更少)就可以了。 <dependencyManagement>中声明的artifact并不一定真的会被使用到
2.1 声明依赖关系
根据实际情况, 该实例 需 要使用db4o针对java5的产品包(jar文件)。由于该jar文件并不存在于Maven的中央仓库中,所以我们不能直接通过Maven获得该jar 文件。我们只能另外下载db4o-5.5(Java版)的压缩包,然后从压缩包内获得db4o-java5.jar。得到该jar后,必须先将它安装到 Maven的本地仓库中(安装方法参见资源[1],主题"向本地仓库安装文件时要生成POM文件"),以备后面的使用。此处将该artifact的 groupId定义为com.db4o,artifactId定义为db4o-java5,version自然就是5.5了(请见上述POM脚本)。
由于该实例最终是一个Web应用,所以它至少需要依赖Servlet的包(servlet-api-2.4.jar),还需要commons- configuration-1.2.jar。这两个artifact都已经存在于Maven中央仓库中,所以我查找到它们后,按照Maven中央仓库的 命名将它们声明到了<dependencyManagement>中(请见上述POM脚本)。junit是进行单元测试时使用的 artifact,(假设)它肯定会被每个工程使用,所以没有将它设置到 <dependencyManagement>中,而直接设置到了 <dependency>中。
细心的朋友肯定已经发现了,针对 commons-configuration的依赖声明处多了一些语句。从表面上看,应该是排除了4个artifact(dom4j, xml-apis , xalan 和 xerces )。 不错,就是排除了这4个jar文件(artifact)。如果有兴趣的话,可以将整个<exclusions>元素删除,然后再尝试一下制作 war文件。你会发现在WEB-INF/lib目录下存在着这4个artifact对应的jar文件。那我为什么要将它们“排除”呢?因为,它们是多余 的!即,它们对于我的这个Web应用来说,根本就是无用的!
Maven2加入了一个很好的特性:自动加载“依赖的依赖(Transitive Dependency)”。 以commons-configuration为例。为了能够让它运行正常,我们实际上还需要其它一些jar(artifact),如commons- collections,commons-lang,...。但这些artifact我都没有“显示”地声明需要依赖它们,但Maven会自动加载,因为 commons-configuration的POM文件将它们声明为了dependency 。
既然那个4个artifact是commons-configuration的依赖,为什么会认为它们是无用的呢?实际上,它们就不应该被声明到commons-configuration的依赖关系中。这是commons -configuration开发者的失误,他们没有将依赖关系整理清晰,而将一些确实既不是runtime,更不是compile-time需要的 artifact放入到了依赖关系中。在Maven中央仓库中存在着很多这种情况,所以我们有时需要弄清楚“哪些文件是我们真正需要的,哪些是可以被清除的”。但有时候,很难做到一个不漏。正是由于这一原因,自动加载Transitive Dependency这一极好的特性,有时让人十分无奈 ^_^
2.2 对插件进行基本配置
我们可以把对插件的全局性(如针对整个项目的)设置放到较高层次的POM文件中,因为它们被设置后,子工程们就会自然遵守它们,而且可以使每个子工程的情况都是一样的。
在第1节中,已经表明该工程使用JDK1.5平台,并且所有文件都使用UTF-8的编码方式。而Maven默认使用JDK1.3级别的javac编译器;默认使用本地编码方式(简体中文Windows操作系统默认使用GBK编码方式)处理文件。这样就必须对Maven进行适当设置,以满足工程的实际需要。
针对资源文件的处理,Maven 使用maven-resources-plugin插件,需要将它的编码方式设置为UTF-8。编译Java源文件,是使用maven-compiler -plugin插件,需要将它的source(Java源文件)与target(class文件)的级别都设置为1.5,另外还要将它的encoding 方式设置为UTF-8。(详细设置请见POM脚本)

3 demo-app工程
demo-app工程是一个普通应用程序工程,它用于处理和数据库相关的操作,如针对数据库的增、删、改、查等基本功能。该工程POM文件的主要内容如下所示:

<project>
......
<build>
<finalName>app</finalName>
<directory>target</directory>

<sourceDirectory>src/java</sourceDirectory>
<outputDirectory>target/classes</outputDirectory>
<resources>
<resource>
<directory>src/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>

<testSourceDirectory>src/test/java</testSourceDirectory>
<testOutputDirectory>target/test-classes</testOutputDirectory>
<testResources>
<testResource>
<directory>src/test/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</testResource>
</testResources>
</build>
</project>

文章的开头已经提到,本实例将会使用定制的目录结构,但在前面却一字不提此事,现在将描述如何定制目录结构。Maven的标准目录结构其实是在Super POM中设置的,由于任何POM都会继承该POM,所以所有的工作都会默认使用标准目录结构。要定制目录,其实就是需要重新设置相关参数的值,即用新值覆盖Super POM中的值。
[1]<finalName>,该元素指定了工程输出的artifact的名称,默认值为${artifactId}-${version},此处修改为app。
[2]<directory>,该元素指定了工程输出的目标目录。默认值为target,此处未修改变。
[3]<sourceDirectory>,该元素指定了Java源文件所在的目录。默认值为src/main/java,此处修改为src/java。
[4]<outputDirectory>,该元素指定了编译后的class文件的放置目录。默认值为target/classes,此处未作改变。
[5]<resources> < resource>,该元素指定了Java源文件使用的资源文件的存放目录。默认值为src/main/resources,此处修改为 src/java。由于在编码Java源文件时,Maven会将资源路径中的文件全部拷贝到classes目录。而此时将Java资源文件目录与Java 源文件目录,设置为同一目录,所以需要将.java文件排除在资源文件的范畴之外( <exclude>**/*.java</exclude> )。
[6] <testSourceDirectory>,该元素指定了单元测试Java源文件的放置目录。默认值为src/test/java,此处未作修改。
[7] <testOutputDirectory>,该元素指定了单元测试Java源文件编译后的class文件放置目录。默认值为 target/test-classes,此处未作改变。
[8] <testResources> <testResource>,该元素指定了单元测试Java源文件所使用的资源文件的放置目录。默认值为src/test/resources,此处修改为 src/test/java。并且也做了与 设置<resources> <resource>时相同的处理(排除Java源文件)。
通过上述设置后,就可以拥有一个定制的Maven工程目录结构了。

4 demo-web工程
demo-web工程是整个应用最终的目标输出,因为此处的目的就是制作一个war文件,然后将它部署到JBoss服务器中。与demo-app工程相比,demo-web工程的POM文件主要有如下不同内容:

<project >
......
<build>
......
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version> 2.0.1 </version>
<configuration>
<webappDirectory>target/${artifactId}</webappDirectory>
<warSourceDirectory>src/webapp</warSourceDirectory>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jboss-maven-plugin</artifactId>
<version> 1.3.1 </version>
<configuration>
<jbossHome>E:/jboss- 4.0.2 </jbossHome>
<serverName>default</serverName>
<fileName>
${project.build.directory}/${project.build.finalName}.${project.packaging}
</fileName>
</configuration>
</plugin>
</plugins>
</build>
</project>

可以看出不同之处就在于对maven-war-plguin及jboss-maven-plugin插件的配置与使用。
Maven使用maven-war-plugin插件对Web工程制作war文件。由于本文使用了定制目录结构,这样则会使maven-war- plugin无法找到Web工程的Web Root目录(默认是src/main/webapp),所以需要对该插件进行适当地配置。<warSourceDirectory>就是 Web工程的Web Root目录,此处设置为;<webappDirectory>是制作war文件之前,相当于是一个被打开(exploded)的war文件 的根目录(默认是target/artifactId-version)。
该工程的脚本中,还使用了一个JBoss插件。该插件可以将制作好的war文件部署(实质上是拷贝)到指定的JBoss部署目录中。< jbossHome>是JBoss的安装根目录,<serverName>指JBoss Server的名称,<fileName>是被部署war文件的名称。

参考资源
[1]Maven入门--概念与实例. http://www.blogjava.net/jiangshachina/archive/2006/09/01/67080.html
[2]Maven + Continuum Weed. http://www.blogjava.net/jiangshachina/archive/2006/09/11/68944.aspx
[3]Maven POM Reference. http://maven.apache.org/pom.html
[3]db4o. http://www.db4objects.com
本文实例下载地址--
http://www.blogjava.net/files/jiangshachina/mvn-demo.rar

3. Continuum入门--实例

1 使用持续集成的好处
1.1 较早提交,经常提交

当开发者有规律性地提交文件时,持续集成将是最高效的。这就是意味着,不能提交不完全的代码,而是保持每次的改动不大,并且有很好的测试。这就可以更好地发现代码中的错误。
1.2 尽可能频繁的构建
频繁的Build可能要受到需要进行Build的工作量,以及进行Build工作的机器的性能的影响。但频繁的Build确实可以在开发人员转移注意力之 前,尽可能早的发现错误。任何时候,任何一个提交的发生,Continuum都可以触发一个Build工作。这就意味着,这些Build工作应该更快,对 于大量的Build工作或性能测试
1.3 创建一个稳定的环境
如果该Build不是其它开发,测试或产品环境中的一员,那么就可以避免定制JDK或其它的本地设置。如果在持续集成环境中,一个Build工作失败了,它会被从引起它的原因的修改中隔离出来,可以独立于运行环境来被使用。
1.4 运行干净的构建
快速、重复地构建是十分有用的,同样重要的是,不会由于旧的Build状态而发生失败的Build工作。有规律的考虑问题,就能得到干净的构建。 Continuum默认就是进行干净的Build工作,未来它还可以允许开发者根据所选的时间表去请求一个初始的checkout。
2 实例
2.1 构想

本实例中需要持续集成的Maven工程是《Maven入门--概念与实例》中的Demo。使用的SCM是Subversion,即假定已经安装好了Subversion服务器和命令行客户端。我们的目标是使用Continuum对Maven工程demo(包括它的子工程)进行定时的持续集成。在本文的环境中有如下路径:
Continuum_Home=D:\continuum
SVN_Repoitory=E:\svn-repo\repository
Maven_Demo=D:\maven\demo
Continuum_Maven=D:\maven\continuum // 该目录存放从Subversion下载的Maven Demo工程
2.2 初始化Subversion仓库
生成仓库:svnadmin create SVN_Repository
将Maven_Demo中的文件导入到仓库:
svn import Maven_Demo file:///SVN_Repoitory/demo -m "initial import demo"
重新下载仓库中的文件(在目录Continuum_Maven中运行如下命令):
svn checkout file:///SVN_Repoitory/demo demo
2.3 启动Continuum
假设此时已经安装了Continuum,但还没有运行它。要运行Continuum,可以双击Continuum_Home\bin\win32\run.bat文件。对于第一次运行,会报一些错误:

……
WARNRDBMS-Errorinitialisingderbyschema:Schema'SA'doesnotexist
ERROR42Y07:Schema'SA'doesnotexist
……

这种情况是正常的,因为Continuum后台使用的是Apache Derby数据库,在第一次运行之前,Continuum所需要的一些表和数据并不存在。但Continuum会初始化这些数据,当第2次启动Continuum时,这些错误就不会出现了。
Continuum服务器默认使用8080端口,如果之前该端口已被占用了,那么启动将失败。为了解决这个问题,我们需要修改jar文件Continuum_Home\apps\continuum-plexus-application-1.0.3.jar中的\conf\application.xml文件。在该文件中查找8080(该值是唯一的),将改它为你所指定的端口值,本文是使用8081。经过上述过程后,Continuum服务器应该就可以正常启动了。下面将讨论Continuum的初始化配置。
2.4 初始化配置
在浏览器中使用地址
http://localhost:8081/continuum登录Continuum控制台界面,如下图所示。

第一次登录Continuum后,需要进行一些配置。主要包括用户名/密码,工作区目录,Build输出目录以及公司信息。主要参数值如下所示:
Working Directory=D:\continuum\workspace
Build Output Directory=D:\continuum\workspace\output
Base URL=http://localhost:8081/continuum/servlet/continuum
// 这里的Company信息就借用此Blog站点的信息吧 *_*
Company Name=BlogJava
Company Logo=http://www.blogjava.net/images/logo.gif
Company URL=http://www.blogjava.net/
提交之后会出现如下画面(能够看到BlogJava的logo吗 *_*)

使用初始配置时设置的用户名及密码,就可以登录Continuum控制台了。
2.5 修改POM文件
在目录Continuum_Maven中执行如下命令,将Maven工程demo重新下载一次
svn checkout file:///SVN_Repository/demo demo
为了能被Continuum使用,且能与Subversion进行配合,需要对原来Maven工程中的POM文件进行修改。在每个POM中加入如下形式的语句:

<project>
……
<ciManagement>
<system>Continuum</system>
<url>http://localhost:8081/continuum/servlet/continuum</url>
<notifiers>
<notifier>
<type>mail</type>
<address>jiangshachina@163.com</address>
</notifier>
</notifiers>
</ciManagement>

<scm>
<connection>scm:svn:file://localhost/SVN_Repository/ProjectName</connection>
</scm>
……
</project>

其中ProjectName要与POM当前所在工程名一致,如对于demo目录中的POM,ProjectName就是demo。这三个文件修改完毕之后,将它们都提交到Subversion服务器中,在各个POM的当前目录下执行命令:
svn commit pom.xml -m "my settings"
为了每个工程都可以被单独地运行,需要将整个demo及其子工程都安装到Maven仓库中,仅需要在Maven_Demo 目录下执行命令:
mvn install
2.6 添加Maven工程
登录到Continuum控制台后,点击左边“Add Project”组中的“Maven 2.0+ Project”。添加Maven2工程有两种情况:输入POM文件的URL;直接上传POM文件。前者适用于任何Maven工程;后者只能适用于没有 module,即无子工程的Maven工程。由于本文使用的Maven工程demo中有两个子工程,所以只能使用前一种方法。

只需要加载顶层工程的POM文件,Continuum会自动加载其它moudel的POM文件。如上图所示,在M2 POM Url中输入:
file:///D:/maven/continuum/Demo/pom.xml
提交之后会看到如下的画面,即表示Maven工程已经被正确地加载到Continuum中了。

加载工程时,Continuum会将这些工程分别下载到Working Directory(见2.4节)中,并在 Build Output Directory(见2.4节)中为每个工程 生 成一个输出目录(开始只有一些log文件)。细心的朋友可能会发现,当上述工作完成之后,子工程“CE Maven Demo – App”和“CE Maven Demo – WebApp”的Build状态仍然是“Queued Build”。其实这是Continuum的一个Bug!Continnum在许多情况下不会自动刷新页面,需要手动刷新,即点击“Show projects”按钮。但请不要使用浏览器中的refresh按钮,这样可能会重新提交你的请求。这个Bug将在Continuum1.1中被修复。当页面刷新之后,App和WebApp的Build状态将与Demo工程一致。
2.7 第一次Build
要Build任何一个Maven工程都十分简单,只需要点击某个工程右边“Build Now”按钮就可以Build该工程了。如我们点击demo工程对应的“Build Now”按钮,那么Continuum将按照demo中的POM文件执行Build工作。当Build完成后,可以显示本次Build的结果状态:成功, 失败或错误(但请随时手动刷新页面^_^)。而Build次数也将从0改变为1。

点击上图表格中的列“Build”中的数字,将可以看到本次Build的详细过程,如下图所示。

2.8 设置时间表
前面已经谈过,持续集成就需要进行有规律的Build。即需要建立一个时间表,让持续集成服务器按照这个表进行Build。Continuum默认的时间 表为:每天的每一个整点进行一次Build,即每天每隔一小时Build一次。我们也可以建立自己的时间表。点击Administration组中的 “Schedules”按钮就可以发现当前默认使用的时间表,再点击“Add”按钮就可以添加新的时间表了,如下图所示。

这些参数项中最关键的是Cron Expression,它定义了Continuum执行Build的时间规则,它的语法规则请见参加资源[4]。上图中规定的时间表是:在每天的14: 00-14:59之间,每分钟都要进行一次Build。勾选上Enable复选框之后,再提交,该Schedule就可以被使用了。
为了使用新建的Schedule,我们需要进入工程视图页面。点击“Show Projects”-->工程名(如CE Maven Demo),就可以看到该工程的详细信息,如下图所示。

在工程视图的Build Definitions项的Goals中可以发现,使用的Schedule就是“DEFAULT_SCHEDULE”时间表。再点击“Add”按钮,并在 Schedule下拉列表框中,请选择刚刚新建的“My First Schedule”时间表(如下图所示),最后再提交后,新的时间表将被采用。

4 小结
根据前面的描述,应该可以对Continuum的使用有些感性上的认识了。由于Continuum是Maven的子项目,它内置支持Maven2,所以能 够与Maven进行良好的集成使用。现时,Continuum也能够支持Ant与Shell脚本工程。使用Continuum+Maven,能够对工程进 行干净与频繁的集成,可以提高Build效率并能促进工程的健康发展。
5 参数资源
[1]Continuum官方网站.
http://maven.apache.org/continuum/
[2]Continuum的参考文档. http://maven.apache.org/continuum/guides/index.html
[3]Better Build with Maven. http://www.mergere.com/m2book_download.jsp
[4]Cron Expression语法规则. http://www.opensymphony.com/quartz/api/org/quartz/CronTrigger.html

4. Maven 其他

学习、使用Maven的过程中,亲身遇到或看到的一些问题的解决方法。Maven有不少Bug,大家使用时一定要小心。(2007.02.10最后更新)

向本地仓库安装文件
mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=<packaging> -DgeneratePom=true -DcreateChecksum=true(需要使用maven-install-plugin 2.2-SNAPSHOT)

打包时,不在META-INF中生成maven目录
使用Maven打包(mvn package)时,默认地会在META-INF中生成一个目录maven,里面是一个pom和一个属性文件。如果不想生成这个目录,需要在POM中进行如下配置:
以对于一般应用程序打包,即制作jar包为例

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
</configuration>
</plugin>
</plugins>
</build>

如果你在打jar包(mvn package)时,报如下错误:

[INFO]Failedtoconfigurepluginparametersfor:org.apache.maven.plugins:maven-jar-plugin:2.0
Cause:Cannotfindsetternorfieldinorg.apache.maven.archiver.MavenArchiveConfiguration for'addMavenDescriptor'

这是由于maven-jar-plugin的版本不够高(很可能是2.0版本),需要升级到最新的2.1版本。运行命令mvn -U package,会先下载最新版本,再执行打包操作。
类似的,对于制作war,ear包,只需要将artifactId换成对应的plugin( maven-war-plugin , maven-ear-plugin )就可以了。

Javadoc中文乱码
中文操作系统中,JDK1.5.0的Javadoc自动默认支持中文,而且页面中的条目名也都默认为中文显示(在之前的JDK中,这些条目名都默认为英文)。 如是在这种情况下使用命令mvn javadoc:javadoc生成Javadoc,则这些条目名将成为乱码。
解决方法:让javadoc插件使用UTF16或Unicode字符集。具体配置的形式如下:

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<charset>UTF16</charset>
</configuration>
</plugin>
</plugins>
</build>


Scope
Scope表示了某个依赖关系的适用范围(作用域),共有5个scope。
compile: 默认的适用范围,表示该依赖关系要应用于所有的classpath。
provided: 该适用范围非常像compile适用范围。但它表示该依赖关系已经由JDK或某个容量提供,如javax.servlet。
runtime:表示该依赖关系不用于编译阶段,而只使用于运行时阶段,如apache jakarta commons。
test:表示该依赖关系并不用于实际的应用程序本身,而是该应用的测试程序的编译与运行,如junit。
system:该依赖关系类似于provided,但必须显示地表示是哪一个容器提供了这个artifact。无法在仓库中找到该artifact。
注意:
[1]具有compile或runtime适用范围的依赖关系中的jar文件,制作war文件时将会被放入WEB-INF/lib目录中。
[2]不推荐使用system适用范围。

将mvn.bat配置为Eclipse外部工具
为了能够在Eclipse环境中运行mvn.bat,需要将它配置为一个外部工具(external tool)。在我的
Eclipse Weed(配置外部工具)一文中已经提到了如何配置Eclipse External Tools(可先参见该文)。本主题将具体讲述如何将mvn.bat的package Build周期短语配置为外部工具。
[1]主菜单Run-->External Tools-->External Tools...
[2]先选中Program项,再点击左上角的New launch configuration按钮
[3]Name文本框中输入该外部工具的名称“MvnPackage”
[4]通过Browser File System...按钮,向Location文本框中输入mvn.bat文件的绝对路径
[5]通过Variables...按钮,选择project_loc,将向Working Directory文本框中输入${project_loc}
[6]在Augments文本域中输入package
这样当你选中一个pom.xml文件后,再运行该外部工具,就相当于对该POM文件文件执行mvn package命令。

制作war文件时,过滤文件
使用maven-war-plugin制作war文件时,它会先将所有可能用于制作war的内容放入target/artifactId-version目录(标准目录结构)下,然后再将这些文件进行打包。这样就有两种方法进行文件过滤:[1]使期望被过滤的文件一开始就不被放入 target/artifactId-version目录,即使它成为不可能的文件;[2]在制作war文件时,不将期望被过滤的文件加入包中。
[1]实现第一种方法,要对dependency进行配置。将不希望加入包的artifact放入exclusion参数中,如下脚本所示:

<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<exclusions>
<exclusion>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
</exclusion>
</exclusions>
</dependency>

[2]实现第二种方法,要对maven-war-plugin进行配置,将 不希望加入包的资源文件(不再称之为artifact)放入warSourceExcludes参数中,如下脚本所示:

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.0.1</version>
<configuration>
<warSourceExcludes>WEB-INF/lib/dom4j-1.4.jar</warSourceExcludes>
</configuration>
</plugin>
</plugins>
</build>

注意:上述方法都不仅仅是过滤掉dom4j的jar文件,它还会过滤掉dom4j所依赖的其它文件(artifact)。

当加载插件时抛NullPointerException
如果仓库中有某个插件的jar文件有问题,则会抛出这样的异常。遇到这样的问题时,必须将这个有问题的插件清除。
可以在执行参数中加上-X(如mvn -X compile)来获得加载插件的过程,从中可能会发现问题插件。如果还不行,则使用一个新的仓库,再次执行工程。

Install或Deploy源代码

<project>
<build>
<plugins>
<plugin>
<inherited>true</inherited>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

使用如上的脚本即可,但该脚本只能针对jar文件,而不能针对war或ear...文件。maven-source-plugin并没有提供install或deploy Web工程源代码的goal。

在命令行中设置本地仓库
在使用Maven2命令(mvn)时,可以设置本地仓库的路径,该路径将会替代settings.xml中设置的本地仓库路径。
如命令,mvn -Dmaven.repo.local=Another_Local_Repo package
注:在Maven1中还可以通过-Dmaven.repo.remote设置远程仓库,但目前在Maven2中还不行。

在Maven发行包中找到Super POM文件
所有的POM都默认继承Super POM,该POM定义了Maven标准目录结构。但在Maven的发行包中,这个Super POM是存放在了哪里呢?
这个POM就存放在maven-project的jar文件中。在笔者的机器中,该Super POM的位置是:
Maven_Home/lib/maven-project-2.0.4.jar/org/apache/maven/project/pom-4.0.0.xml

构建Maven远程仓库
构建Maven远程仓库的方法很多,也很简单。常用的Web服务器(Apache,JBoss,Tomcat,...)都可以用于构建Maven远程仓库;而发布artifact时,也可以使用多种协议(FTP,SFTP,SSH,...)。
此处使用RedHat AS 4.0 + Apache2.0.59,并应用SSH协议向远程仓库发布artifact。
[1]在Apache的DocumnetRoot中新建目录maven2/repo,此处该目录的绝对路径为
/usr/local/apache2/htdocs/maven2/repo
[2]在本地Maven的settings.xml文件中设置Server,语句的形式如下:
<servers>
<server>
<id>myrepo</id>
<username>myuser</username>
<password>mypasswd</password>
</server>
</servers>
myuser/mypasswd是登录远程Linux系统时使用的用户名/密码。
[3]在本地工程的pom.xml中进行如下形式的设置:
<distributionManagement>
<repository>
<id>myrepo</id>
<url>scp://Host/usr/local/apache2/htdocs/maven2/repo</url>
</repository>
</distributionManagement>
此处id必须与前面设置的server中的id一致;scp是使用SSH协议的文件传输命令;Host是远程Linux服务器的IP地址或域名;/usr/local/apache2/htdocs/maven2/repo就是Maven仓库在远程服务器中的绝对路径。
[4]在本地中使用命令mvn deploy发布artifact到远程仓库中。
[5] 启动Apache服务器, 通过地址http://Host/maven2/repo,就可以看到刚刚发布artifact了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics