第三章 用Maven创建应用程序
3.1 介绍
现在你将钻研的更深一些,在这部分你将学习通过一个管理FAQ应用程序的真实例子学习Maven的最佳实践和高级应用。这个应用程序名字叫Proficio,该名字来自于拉丁语帮助的意思。
3.2 建立应用程序目录结构
Proficio的模块组成
Proficio API
Proficio的应用程序接口
Proficio CLI
提供命令行接口
Proficio Core
接口实现
Proficio Model
Proficio的数据模型
Proficio Stores
存放存储模块,Proficio有一个简单的基于内存(memory-based)的仓库和一个基于流(XStream-based)的仓库
查看Proficio的顶层POM时,你会在modules元素下看到所有的子模块构成了Proficio应用程序。一个子模块是另一个Maven项目的引用。如下
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>Maven Proficio</name>
<url>http://maven.apache.org</url>
[...]
<modules>
<module>proficio-model</module>
<module>proficio-api</module>
<module>proficio-core</module>
<module>proficio-stores</module>
<module>proficio-cli</module>
</modules>
[...]
</project>
上述pom中的一个特征就是版本属性为1.0-SNAPSHOT。对于一个应用程序来说,通常会一起发布许多子模块,这对于所有模块都有一个公共版本来说是很有意义的。建议你的应用程序的所有子模块都用顶层POM的版本号。
你应该注意packaging元素,在这个例子中它的值为pom。对于包括子模块的POMs来说,packaging类型必须被设置成pom,这告诉Maven你打算构建一个多模块的结构。目录结构如下
proficio
----proficio-api
----proficio-cli
----proficio-core
----proficio-model
----proficio-stores
--------proficio-store-memory
--------proficio-store-xstream
--------pom.xml
----pom.xml
各个模块的打包类型
Module Packaging
proficio-api jar
proficio-cli jar
proficio-core jar
proficio-model jar
proficio-stores pom
在大多数时候打包类型为默认的jar,但是proficio-stores这个模块类型为pom,下面是这个模块的POM
<project>
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>proficio-stores</artifactId>
<name>Maven Proficio Stores</name>
<packaging>pom</packaging>
<modules>
<module>proficio-store-memory</module>
<module>proficio-store-xstream</module>
</modules>
</project>
当你看到packaging类型为pom时,意味着该项目/模块有多个子模块构成
3.3 使用项目继承
项目继承是Maven的一项重要特征之一。使用项目继承允许你在同一个位置声明你的组织信息,部署信息或者你的通用依赖性。你可能会注意到每个子POMs的头部会像下面这样
[...]
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
[...]
这是一个pom片段,让你利用顶层POM的资源,定义了从哪个项目继承。让我们来看一个例子。
如果你观察一下Proficio的顶层POM,你会发现在依赖部分有一个JUnit3.8.1的声明。在这个例子中假设Junit将被用来测试所有子项目。所以,通过继承顶层POM的依赖,在你的任何子POMs中你将不需要再次声明这个依赖。这个依赖定义如下:
<project>
[...]
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
[...]
</project>
让我们来看看每个子POM发生了什么,每个子POM都继承了顶层POM的依赖部分。所以如果你看一下proficio-core模块的POM你会发现如下(注意:没有JUnit的依赖声明)
<project>
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>proficio-core</artifactId>
<packaging>jar</packaging>
<name>Maven Proficio Core</name>
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-api</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
</dependency>
</dependencies>
</project>
为了让你看见继承的过程,你需要使用快捷工具mvn help:effective-pom命令。这个命令将让你 看到最终目标POM的结果。进入proficio-core模块目录运行上述命令,你将会看到JUnit的依赖。
<project>
[...]
<dependencies>
[...]
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>
[...]
</project>
你会发现用mvn help:effective-pom命令输出的pom比预想的大很多,因为这个命令输出了合并后的整个层次结构的pom,当你遇到问题时这个命令是非常有帮助的。
3.4 管理依赖
当你构建应用程序时,有许多依赖需要管理而且随着时间的增长依赖也不断的增长,使依赖管理变得复杂。Maven管理依赖的策略是通过把项目的继承机制和在POM中定义的依赖管理元素相结合来处理这个问题。
当你写的应用程序由许多独立的项目组成,很可能其中一些项目共享相同的依赖。当这个发生时,要保证所有的相同版本的依赖被你的所有项目使用,这样才能保证你的应用程序正常工作。为了在跨多个项目管理你的依赖,你使用顶层POM中的依赖管理部分来管理依赖。让我们来看一下如下部分
<project>
[...]
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-store-memory</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-store-xstream</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>1.0-alpha-9</version>
</dependency>
</dependencies>
</dependencyManagement>
[...]
</project>
注意:${project.version}是指顶层POM的版本元素,也就是应用程序版本
就像上面的依赖管理部分看到的,我们有几个Proficio依赖和一个Plexus依赖注入容器的依赖。在包括依赖元素的dependencyManagement元素和在POM顶层dependencies依赖元素有一个重要的区别:dependencyManagement只是用来定义引用的版本号,他自己不会作用于依赖层次图,而顶层dependencies元素会影响层次图。如果你观察一下proficio-api模块里的POM,你会发现依赖声明部分如下:
<project>
[...]
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-model</artifactId>
</dependency>
</dependencies>
</project>
这个依赖的版本来源于顶层POM的dependencyManagement元素。只有在子项目的依赖没有声明版本时定义在祖先项目中的dependencyManagement依赖才会生效。
3.5 使用快照(Snapshots)
当你开发拥有多个模块的应用程序时,在某些情况下每个模块更新都很频繁,需要不断的获取最新子模块(原文:it is usually the case that each of the modules are in flux)。你的APIs会改变,你的实现会改变和更新,你可能还会重构。你的构建系统需要以简单的方式来实时更新(获取最新子模块以构建项目),这就是Maven快照出现的原因。Maven中快照是用来获得最新源代码生成的产品。如果你看一下顶层Proficio的POM,你会发现快照版本定义如下:
<project>
[...]
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>1.0-alpha-9</version>
</dependency>
</dependencies>
</dependencyManagement>
[...]
</project>
定义一个快照版本意味着Maven将寻找最新的依赖。快照假设依赖是被改变的,所以Maven将企图获取他们。默认情况下Maven会按日生成一次快照,但是你可以使用-U命令强迫更新。控制快照如何工作将在第七章介绍。当你定义了一个非快照的依赖,Maven将会下载依赖一次而不会再去获取他。
3.6 解决依赖冲突和使用版本范围
当使用Maven2.0时,你只要简单的定义依赖,Maven会计算所有的依赖图。然而,当生成依赖图时,有2个或多个产品依赖不同版本号的特定依赖是不可避免的。在这种情况下依赖必须选择提供的版本。
在Maven中版本选择会以离版本树最近的优先选择,然而这个是有限制的:
1) 这个版本可能没有其他依赖所需要的特征
2) 如果选择了多个版本,可能会出现不确定的结果
要手动解决这些冲突,你可以移除版本树中不正确的版本或者你可以用正确的版本覆盖。移除不正确的版本要求识别出不正确的版本源代码,通过运行-X标记(详情见6.9),例如:如果你在proficio-core模块中使用mvn -X test,输出将包括如下:
proficio-core:1.0-SNAPSHOT
junit:3.8.1 (selected for test)
plexus-container-default:1.0-alpha-9 (selected for compile)
plexus-utils:1.0.4 (selected for compile)
classworlds:1.1-alpha-2 (selected for compile)
junit:3.8.1 (not setting scope to compile; local scope test wins)
proficio-api:1.0-SNAPSHOT (selected for compile)
proficio-model:1.0-SNAPSHOT (selected for compile)
plexus-utils:1.1 (selected for compile)
在执行mvn -X test之前必须保证构建已经进行,所以最好先执行mvn install保证项目已经被安装到本地库中。
一旦版本路径已经识别出来,你可以通过在dependency中增加exclusion来排除对象图中的依赖。在上面的例子中plexus-utils出现了2次,Proficio需要1.1版本的。要保证这个,只需修改在proficio-core/pom.xml中的plexus-container-default依赖如下:
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>1.0-alpha-9</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</exclusion>
</exclusions>
</dependency>
这样就保证了Maven会忽略依赖图中1.0.4版本的plexus-utils,而1.1版本的会被使用。
另一种保证特定版本依赖的方式是直接在POM中指定如下:
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>1.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
然而这个方式是不建议使用的,除非你制造的产品不需要被其他程序依赖(例如:WAR包)。原因是这样做曲解了依赖图,使得难以被依赖。
你会注意到这里使用了runtime范围。这是因为这个例子中依赖被用在打包而不是编译。
上述2个方法都不是理想的,但是它能保证改进你的依赖的质量,降低构建的风险问题。如果你要发布框架的库,发布给许多人是非常重要的,这就要使用版本范围而不是指定一个特定的。
当版本声明为1.1就像上一个例子中的plexus-utils,这表明首选的版本是1.1,但是其他版本可能也是可接受的。Maven不知道哪些版本能运行,所以为了防止与另一个版本冲突,Maven假设所有的版本都是有效的,使用先前描述的最近依赖技术来决定选择哪个版本。用版本范围的定义方式如下:
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>[1.1,)</version>
</dependency>
上述的配置意味着,当使用最近依赖技术防止冲突时,其版本范围必须匹配。如果最近版本不匹配,则用第二近的,以此类推。如果都不匹配,或者没有冲突,则会使用大于1.1的版本。这时库中大于1.1的最新版本将会被使用。
版本范围的例子:
Range Meaning
(,1.0] Less than or equal to 1.0
[1.2,1.3] Between 1.2 and 1.3 (inclusive)
[1.0,2.0) Greater than or equal to 1.0,
[1.5,) Greater than or equal to 1.5
(,1.1),(1.1,) Any version, except 1.1
使用更精确的定义可以使依赖机制更可靠和降低异常的数量。然而你不应该定义的太过精确。因为,如果2个版本范围没有交集,构建过程会失败。
要理解版本范围如何工作,理解版本比较是必须的。如下所示说明了版本如何分割:
主版本号(Major) 次版本号(Minor) 修订号(Bug fix) 限定符(Qualifier) 构建号(Build Number)
1. 0. 1- 20060211.131141- 1
连起来就是1.0.1-20060211.131141-1
正如你看到的一个版本被拆分成5部分:主版本号,次版本号,修订号以及限定符和最后的构建号。对于当前的版本模式,一个快照(如前文提到的)允许有限定符和构建号。对于正规的版本,你可以只提供限定符或者只提供构建号。限定符的意图是表明一个产品在发布之前的版本(例如:alpha-1,beta-1,rc1)。对于snapshot的限定符,必须为文本"snapshot"或者时间戳。上例中的快照版本生成于2006-11-02 13:11:41。构建号是一个在发布后表明构建补丁的数字。
关于排序,这些元素被用来顺序决定哪个比较新:首先是主版本号,然后如果主版本号相同则比较次版本号,第三是修订号,第四是限定符(使用字符串比较),最后是构建号。一个带限定符的版本比不带限定符的版本旧。例如1.2-beta比1.2旧。一个带构建号的版本比不带构建号的版本新。例如:1.2-beta-1比1.2-beta新。在某些情况下版本不会匹配这个语法。在那些情况下,2个版本完全用字符串比较。
对于版本范围比较也一样。如果你使用的版本范围是[1.1,),版本1.1和1.2-beta-1存在于库中,则1.2-beta-1会被选择。一般这是不希望的,为了避免这种情况,你必须组织你的发布,避免用命名规则导致上述的行为或者使用不同的库来存放你希望的版本产品。
无论你发布最终版本还是发布里程碑式的测试版本,你应该把它们部署到库中。这能保证测试版本只有在项目明确声明为快照时才使用范围。
最后一个需要注意的地方是当使用范围时如何决定版本更新。这个机制和快照一样。默认情况下,每天从库中更新一次。然而,可以配置更多的时间间隔才更新或者使用-U选项强制更新。配置间隔的例子如下:
<repository>
[...]
<releases>
<updatePolicy>interval:60</updatePolicy>
</releases>
</repository>
3.7 利用构建生命周期
在第二章里,Maven被描述成为通过良好定义的方式和过程(也就是Maven的默认构建生命周期)与可执行插件交互的框架。Maven的默认构建生命周期满足于大多数项目。但是,有些项目将有不同的需求,需要在默认生命周期基础上再增加一些阶段来满足需求。
例如,Proficio有一个从模型生成源代码的需求。Maven允许通过插件声明来把它绑定到默认生命周期的标准阶段——generate-sources阶段。
Maven中的插件根据特定的任务创建,意味着插件被绑定到默认构建生命周期。在Proficio中,Modello插件用来为数据模型生成源代码。如果你观察一下proficio-model的POM你会发现其中有一个plunins元素:
<project>
<parent>
<groupId>com.devzuz.mvnbook.proficio</groupId>
<artifactId>proficio</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>proficio-model</artifactId>
<packaging>jar</packaging>
<name>Proficio Model</name>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.modello</groupId>
<artifactId>modello-maven-plugin</artifactId>
<version>1.0-alpha-5</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<version>1.0.0</version>
<packageWithVersion>false</packageWithVersion>
<model>src/main/mdo/proficio.mdo</model>
</configuration>
</plugin>
</plugins>
</build>
</project>
这个和你在第二章中看到的声明maven-compiler-plunin非常相似,但是你会看到多出了executions元素。Maven中的插件可以有多个目标,所以你需要通过指定在executions元素中的goal来定义你要运行插件中的哪个目标。
3.8 使用Profiles
Profiles让你在默认构建生命周期时创建环境变量来完成一些事情,如在不同平台构建,不同虚拟机构建,不同数据库测试或者引用本地文件系统。典型的,你试图尽可能封装POM来保证可移植的构建,但是有时你不得不考虑跨系统的环境变量,这就是Maven提供profiles的原因。
Profiles在构建时修改POM,意味着可以为目标环境提供意义相同而定义不同的参数(例如,应用程序服务器的开发,测试,产品环境的根路径)。
你可以在下面三个地方定义Profiles:
1 Maven配置文件(默认:<user_home>/.m2/settings.xml)
2 和POM在同一文件夹下的profiles.xml文件
3 在POM中
要确定哪个profile优先,规则是就近原则。所以POM中的最高,然后是profiles.xml,最后是setting.xml。
setting.xml可以影响到所有构建,所以他是全局的profiles。profiles.xml允许你使用一个单独的项目构建而不需要修改POM。基于POM的profiles是首选的,因为他们是可移植的(他们可以被发布到库中和可以被子构建继承使用)。
由于可移植性的原因,任何不能被发布到库中的文件都不能改变基本构建。因此,在profile.xml和setting.xml中定义的profiles只允许定义:
repositories
pluginRespositories
properties
其他的都必须定义在POM的profile中,或者POM自己或者不定义。例如,如果你有一个setting.xml需要注入一个依赖,你的项目运行时确实需要那个依赖,一旦你的工程被部署到库中,这个依赖就不能被解决,因为他的依赖设置在你的本地settings.xml而库中没有。
注意:repositories,pluginRespositories,properties也可以被定义在POM中。所以能在POM外面定义的profiles只是POM中可定义的profiles的一个子集。
你可以在POM profile中定义如下元素:
repositories
pluginRepositories
dependencies
plugins
properties (not actually available in the main POM, but used behind the scenes)
modules
reporting
dependencyManagement
distributionManagement
构建元素自己可以包括如下元素:
defaultGoal
resources
testResources
finalName
激活profiles有好几个方式:
1 -P CLI选项,后面跟以,分割的profiles-ids,例如:
mvn -Pprofile1,profile2 install
2 通过Maven设置中的activeProfiles部分。例如:
<settings>
[...]
<profiles>
<profile>
<id>profile1</id>
[...]
</profile>
</profiles>
<activeProfiles>
<activeProfile>profile1</activeProfile>
</activeProfiles>
[...]
</settings>
3 Profiles可以根据构建环境的检测状态自动触发。通过activation部分设定。目前这个检测被限制于前缀匹配JDK版本或者系统属性,例如:
<profile>
<id>profile1</id>
[...]
<activation>
<jdk>1.4</jdk>
</activation>
</profile>
这个活动在JDK版本为以1.4开头时触发(例如"1.4.0_08","1.4.2_07","1.4")
<profile>
<id>profile1</id>
[...]
<activation>
<property>
<name>debug</name>
</property>
</activation>
</profile>
这个将在定义了debug属性时触发
<profile>
<id>profile1</id>
[...]
<activation>
<property>
<name>environment</name>
<value>test</value>
</property>
</activation>
</profile>
这个将会在定义了environment属性为test时触发
既然你熟悉了profiles,你打算使用他们创建定制的装配件:Proficio的装配件使用基于内存的仓库和使用基于XStream的仓库。这些装配件将在proficio-cli模块中创建,profiles用来控制这些装配件的创建。
下面是proficio-cli模块的profile定义:
<project>
[...]
<!-- Profiles for the two assemblies to create for deployment -->
<profiles>
<!-- Profile which creates an assembly using the memory based store -->
<profile>
<id>memory</id>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly-store-memory.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</build>
<activation>
<property>
<name>memory</name>
</property>
</activation>
</profile>
<!-- Profile which creates an assembly using the xstream based store -->
<profile>
<id>xstream</id>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly-store-xstream.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</build>
<activation>
<property>
<name>xstream</name>
</property>
</activation>
</profile>
</profiles>
</project>
你可以看到2个profile:一个是基于memory的另一个是xstream。在每一个profiles中你都配置了将会创建装配件的插件描述。你将注意到使用系统属性激活profiles。你应该注意下面的例子依赖的部分应该首先被执行构建,所以应该先使用顶层项目的mvn install以保证所需的组件已经被安装到本地库。
如果你想创建基于内存的库,你应该执行:
mvn -Dmemory clean assembly:assembly
如果你想创建基于XStream库,你应该执行:
mvn -Dxstream clean assembly:assembly
上述两个装配件都被创建到目标目录,如果你在结果装配件上使用java tvf命令,你将看到基于内存的装配件只包括proficio-store-memory-1.0-SNAPSHOT.jar。这是一个非常简单的例子,但是这个例子展示了自定义生命周期profiles来适应你的需求。
3.9 部署你的应用程序
既然你有了一个应用程序装配件,你想把它尽可能多的与人分享,所以你决定部署你的应用程序装配件。
现在Maven支持好几种部署方式:包括简单的file-based部署,SSH2部署,SFTP部署,FTP部署和扩展SSH部署。为了部署,你需要配置POM中的distributionManagement元素,它通常在你的顶层POM中,所以所有子POMs可以继承这些信息。下面是通过各种部署机制来配置你的POM的例子。
3.9.1 以文件方式部署
<project>
[...]
<distributionManagement>
<repository>
<id>proficio-repository</id>
<name>Proficio Repository</name>
<url>file://${basedir}/target/deploy</url>
</repository>
</distributionManagement>
[...]
</project>
3.9.2 以SSH2方式部署
<project>
[...]
<distributionManagement>
<repository>
<id>proficio-repository</id>
<name>Proficio Repository</name>
<url>scp://sshserver.yourcompany.com/deploy</url>
</repository>
</distributionManagement>
[...]
</project>
3.9.3 以SFTP方式部署
<project>
[...]
<distributionManagement>
<repository>
<id>proficio-repository</id>
<name>Proficio Repository</name>
<url>sftp://ftpserver.yourcompany.com/deploy</url>
</repository>
</distributionManagement>
[...]
</project>
3.9.4 以扩展SSH方式部署
目前为止上述3中方式已经被Maven包含,所以只要distributionManagement就可以了,但是使用扩展SSH命令部署的话你不仅需要配置distributionManagement还需要一个build extension,如下
<project>
[...]
<distributionManagement>
<repository>
<id>proficio-repository</id>
<name>Proficio Repository</name>
<url>scpexe://sshserver.yourcompany.com/deploy</url>
</repository>
</distributionManagement>
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh-external</artifactId>
<version>1.0-alpha-6</version>
</extension>
</extensions>
</build>
[...]
</project>
The build extension specifies the use of the Wagon external SSH provider, which does the work of moving your files to the remote server. Wagon is the general purpose transport mechanism used throughout Maven.
3.9.5 以FTP方式部署
<project>
[...]
<distributionManagement>
<repository>
<id>proficio-repository</id>
<name>Proficio Repository</name>
<url>ftp://ftpserver.yourcompany.com/deploy</url>
</repository>
</distributionManagement>
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ftp</artifactId>
<version>1.0-alpha-6</version>
</extension>
</extensions>
</build>
[...]
</project>
一旦你配置好了相应的POM你可以执行下列命令来开始部署:mvn deploy
一章也显示不下,本章最后一节见下篇:http://hrtc.iteye.com/admin/blogs/181422