Ant用法——摘自《轻量级Java EE企业应用实战》
1.6 Ant的安装和使用
Ant是一种基于Java的生成工具。从作用上来看,它有些类似于C编程(Unix平台上使用较多)中的Make工具,C/C++项目经常使用Make工具来管理整个项目的编译、生成。
Make使用Shell命令来定义生成任务,并定义任务之间的依赖关系,以便它们总是以必需的顺序来执行。
Make工具主要有如下两个缺陷:
— Make工具的本质还是依赖Unix平台的Shell语言,所以Make工具无法跨平台。
— Make工具的生成文件的格式比较严格,容易导致错误。
Ant工具是基于Java语言的生成工具,所以具有跨平台的能力;而且Ant工具使用XML文件来编写生成文件,因而具有更好的适应性。
由此可见:Ant是Java世界的Make工具,而且这个工具是跨平台的,并具有简单、易用的特性。
提示:由于Ant具有跨平台的特性,所以编写Ant生成文件时可能会失去一些灵活性。为了弥补这个不足,Ant提供了一个“exec”核心task,这个task允许执行特定操作系统上的命令。
1.6.1 Ant的下载和安装
下载和安装Ant请按如下步骤进行:
(1)登录http://ant.apache.org/bindownload.cgi站点下载Ant最新版,笔者成书之时,Ant的最新稳定版是1.7.0,建议下载该版本。
虽然Ant是基于Java的生成工具,具有平台无关的特性,但考虑到解压缩的方便性,通常建议Windows平台下载ZIP压缩宝,而Linux平台则下载GZ压缩包。
(2)将下载到的压缩文件解压缩到任意路径,例如笔者解压缩到D:/根路径下,并将Ant文件夹重命名为ant170。解压缩后看到如下文件结构:
—bin:启动和运行Ant的可执行性命令。
—docs:Ant工具的相关文档,这些文档对学习使用Ant有很大的作用。
—etc:包含一些样式单文件,通常无需理会该目录下的文件。
—lib:包含Ant的核心类库,以及编译和运行Ant所依赖的第三方类库。
—LICENSE等说明性文档。
提示:重命名Ant文件夹仅仅是为了方便、简捷,并不是必需的。读者既可以像笔者一样重命名该文件夹,也可以不重命名该文件夹。
(3)Ant的运行需要如下两个环境变量:
—JAVA_HOME:该环境变量应指向JDK的安装路径。如果已经成功安装了Tomcat,则该环境变量应该已经是正确的。
—ANT_HOME:该环境变量应指向Ant的安装路径。
按前面介绍的方式配置ANT_HOME环境变量。
提示:Ant的安装路径就是前面释放Ant压缩文件的路径。Ant安装路径下应该包含bin、docs、etc和lib四个文件夹。
(4)Ant工具的关键命令就是%ANT_HOME%/bin路径下的ant.bat命令,如果读者希望操作系统可以识别该命令,还应该将%ANT_HOME%/bin路径添加到操作系统的PATH环境变量之中。
提示:当我们在命令行窗口、Shell窗口输入一条命令后,操作系统会到PATH环境变量所指定的系列路径中去搜索,如果找到了该命令所对应的可执行性程序,即运行该命令,否则将提示找不到命令。如果读者不嫌麻烦,愿意每次都输入%ANT_HOME%/bin/ant.bat的全路径来运行Ant工具,则可以无需将%ANT_HOME%/bin路径添加到PATH环境变量之中。
经过上面四个步骤,Ant安装成功,读者可以启动命令行窗口,输入如下ant.bat命令(如果读者未%ANT_HOME%/bin路径添加到PATH环境变量之中,则应该输入%ANT_HOME%/bin/ant.bat,则应该看到如下提示:
Buildfile: build.xml does not exist!
Build failed
如果看到上面提示信息,则表明Ant安装成功
1.6.2 使用Ant工具
使用Ant非常简单,当正确地安装Ant后,只要输入ant或ant.bat即可。
如果运行ant命令时没有指定任何参数时,Ant会在当前目录下搜索build.xml文件。如果找到了就以该文件作为生成文件,并执行默认的target。
提示:关于生成文件和target的概念请参看1.6.3节内容,关于生成文件中默认target的介绍也请参看1.6.3节内容
如果运行时使用-find或者-s选项(这两个选项的作用完全相同),Ant就会到上级目录中搜索生成文件,直至到达文件系统的根路径。
要想让Ant使用其他生成文件,可以用-buildfile <生成文件>选项,其中-buildfile可以使用-file或-f来代替,这三个选项的作用完全一样。例如如下命令:
ant -f a.xml //显式指定使用a.xml作为生成文件。
ant -file b.xml //显式指定使用b.xml作为生成文件。
如果希望Ant运行时只输出少量的必要信息,则可使用-quiet或-q选项;如果希望Ant运行时输出更多的提示信息,则可使用-verbose或-v选项。
如果希望Ant运行时将提示信息输出到指定文件,而不是直接输出到控制台,则可使用-logfile <file>或-l <file>选项。例如如下命令:
ant -verbose -f a.log //运行Ant时生成更多的提示信息,并将提示信息输出到a.log文件中。
除此之外,Ant还允许运行时指定一些属性来覆盖生成文件中指定的属性值(使用Propertytask来指定),例如使用-D<property>=<value>,则此处指定的value将会覆盖生成文件中property的属性值。例如如下命令:
ant -Dbook=Spring2 //该命令将会覆盖生成文件中的book属性值。
提示:通过该方法可以将操作系统的环境变量值传入生成文件,例如我们在运行Ant工具时使用如下命令:
ant -Denv1=%ANT_HOME%
上面命令中粗体字代码用于向生成文件中传入一个env1属性,而该属性的值并没有直接给出,而是用%ANT_HOME%的形式给出——这是Windows下访问环境变量的形式。通过这种方式,就可以将Windows环境变量值传入生成文件了,如果希望在生成文件中访问到该环境变量的值,使用$env1即可。
上面命令在Linux平台上则改为:ant -Denv1=$ANT_HOME,Linux下以$符来访问环境变量。
默认情况下,Ant将运行生成文件里指定的默认target,如果希望运行Ant时显式指定希望运行的target,则可采用如下命令格式:
ant [target [target2 [target3] ...]]
实际上,如果读者需要获取ant命令更多详细情况,直接使用ant -help选项即可。运行ant -help将看到如图1.26所示提示信息:
图1.26 Ant命令用法
1.6.3 定义生成文件
实际上,使用Ant的关键就是编写生成文件,生成文件定义了该项目的各个生成任务(以target来表示,每个target表示一个生成任务),并定义生成任务之间的依赖关系。
Ant生成文件的默认名为build.xml,也可以取其他的名字。但如果为该生成文件起其他名字,将意味着要将这个文件名作为参数传给Ant工具。生成文件可以放在项目的任何位置,但通常做法是放在项目的顶层目录中,这样有利于保持项目的简洁和清晰。
下面是一个典型的项目层次结构:
<project>:该文件夹存放了整个项目的全部资源。
|-src:存放源文件、各种配置文件的文件夹。
|-class:存放编译后的class文件的文件夹。
|-lib:存放第三方JAR包的文件夹
|-dist:存放项目打包、项目发布文件的文件夹。
|-build.xml:Ant生成文件。
Ant生成文件的根元素是project,每个项目下可以定义多个生成目标,每个生成目标以一个target元素来定义,它是project元素的子元素。
project元素可以有多个属性,project元素常见属性的含义如下:
—default:指定默认target,这个属性是必须的。如果运行ant.bat命令时没有显式指定想执行的target,Ant将执行该target。
—basedir:指定项目的基准路径,生成文件中的其他相对路径都是基于该路径的。
—name:指定项目名,该属性仅指定一个名字,对编译、生成项目没有太大的实际作用。
—description:指定项目的描述信息,对编译、生成项目没有太大的实际作用。
如下代码片段所示:
复制内容到剪贴板代码:
<?xml version="1.0" encoding="GBK"?>
<!-- 下面配置信息指定基准路径是当前路径,默认target为空 -->
<project name="struts
2" description="demo" basedir="." default="" >
...
</project>
每个生成目标对应一个target元素,—name:指定该target的名称,该属性是必须的。该属性非常重要,当希望Ant运行指定的生成目标时,就是根据该name来确定生成目标的。所以我们可以得出一个结论:同一个生成文件里不能有2个同名的target元素。
—depends:该属性可指定一个或多个target名,表示运行该target之前应先运行该depends属性所指定的一个或多个target。
—if:该属性指定一个属性名,用属性表示仅当设置了该属性时才执行此target。
—unless:该属性指定一个属性名,用属性表示仅当没有设置该属性时才执行此target。
—description:指定该target的的描述信息。
例如如下配置片段:复制内容到剪贴板
代码:
<!-- 下面代表表示执行run target之前,必须先执行compile target -->
<target name="run" depends="compile"/>
<!-- 只有当设置了prop1属性之后才会执行exA target -->
<target name="exA" if="prop1"/>
<!-- 只要没有设置了prop1属性,就可以执行exB target -->
<target name="exB" unless="prop2"/>
每个生成目标又可能由一个或者多个任务序列组成,当执行某个生成目标时,实际上就是依次完成该目标所包含的全部任务。每个任务由一段可执行的代码组成。定义任务的代码格式如下:复制内容到剪贴板
代码:
<name attribute1="value1" attribute2="value2" ... />
上面代码中name是任务的名称,attributeN和valueN用于指定执行该任务所需的属性名和属性值。提示:简而言之,Ant生成文件的基本结构是project元素里包含多个target元素,而每个target元素里包含多个任务。
Ant的任务可以为3类。
—核心任务:核心任务是Ant自带的任务。
—可选任务:可选任务实来自第三方的任务,因此需要一个附加的JAR文件。
—用户自定义的任务:用户自定义的任务是用户自己开发的任务。
根据上面介绍,不难发现Ant生成文件具有如下结构:
图1.27 Ant生成文件结构
除此之外,project元素还可拥有如下两个重要的子元素:
—property:用于定义一个或多个属性。
—path:用于定义一个或多个文件和路径。
proprty元素
property元素用于定义一个或者多个属性,Ant生成文件中的属性类似于编程语言中的宏变量,它们都具有名称和值。与编程语言不同的是:Ant生成文件中的属性值不可改变。
定义一个属性最简单形式如下:
<!-- 下面代码定义了一个名为builddir的属性,其值为dd -->
<property name="builddir" value="dd"/>
如果需要获取属性值则使用${propName}的形式,例如如下代码即可获取builddir属性值:
//输出builddir属性值
${ builddir }
由此可见:$符在Ant生成文件中具有特殊意义,如果我们希望Ant将生成文件中的$当成普通字符,则应该使用$$。例如如下配置片段:
<echo>$${builddir}=${builddir}</echo>
上面代码中的$${builddir}不会获取builddir属性值,而${builddir}才会获取builddir属性值,执行上面任务将会输出:
[echo] ${builddir}=dd
提示:echo是Ant的核心任务之一,该任务直接输出某个字符串,通常用于输出某些提示信息。
实际上,property元素可以接受如下几个常用属性:
—name:指定需要设置的属性名。
—value:指定需要设置的属性值。
—resource:指定属性文件的资源名称,Ant将负责从属性文件中读取属性名和属性值。
—file:指定属性文件的文件名,Ant将负责从属性文件中读取属性名和属性值。
—url:指定属性文件的URL地址,Ant将负责从属性文件中读取属性名和属性值。
—environment:用于指定系统环境变量的前缀。通过这种方式允许Ant访问系统环境变量。
—classpath:指定搜索属性文件的的文件和路径集。
—classpathref:指定搜索属性文件的的文件和路径集引用,该属性并不是直接给出系列文件或路径,而是给定文件和路径集引用。
提示:关于文件和路径集、以及文件和路径集引用的知识请参考path元素和classpath元素。
下面给出几个使用property元素的例子:
<!-- 指定读取foo.properties属性文件中的属性名和属性值 -->
<property file="foo.properties"/>
下面从网络中读取属性名和属性值:
<!-- 指定从指定URL处读取属性名和属性值 -->
<property url="http://www.mysite.com/props/foo.properties"/>
property元素所读取的属性文件就是普通的属性文件,该文件的内容由系列的name=value组成,如下配置片段所示:
author=Yeeku.H.Lee
book=Light Weight Java EE
price=56
除此之外,通过property元素可以让Ant生成文件访问到操作系统的环境变量值,例如如下代码:
<!-- 定义访问操作系统环境变量的前缀是env -->
<property environment="env"/>
定义了上面的property元素之后,下面就可以在Ant生成文件中通过如下形式来访问操作系统环境变量:
<!-- 输出JAVA_HOME环境变量 -->
<echo>${env.JAVA_HOME}</echo>
在笔者的机器上运行上面任务,即可看到输出:[echo] D:/Java/jdk1.6.0_06,这就是笔者机器上JAVA_HOME环境变量的值。
path元素和classpath元素
通常我们需要使用Ant编译、运行Java文件,编译、运行Java文件时常常需要引用第三方JAR包,这就是需要使用classpath元素了。path元素和classpath元素都用于定义文件和路径集,区别是classpath元素通常作为其他任务的子元素,用于指定第三方类库、资源文件的搜索路径;而path元素则作为project元素的子元素,用于定义一个独立的、有名称的文件和目录集,用于被引用。
提示:因为path和classpath都是用于文件和目录集,所以也把path和classpath元素定义的内容称为Path-like Structures(似目录结构)。
path和classpath元素都用于收集系列的文件和目录集,这两个元素都可接受如下子元素:
—pathelement:用于指定一个或多个目录。
—dirset:采用模式字符串的方式指定系列目录。
—fileset:采用模式字符串的方式指定系列文件。
—filelist:直接列出系列文件名的方式指定系列文件。
pathelement元素用于指定一个或多个目录,pathelement元素可以指定如下两个属性的其中之一:
—path:指定一个或者多个目录(或者JAR文件),多个目录或JAR文件之间以英文冒号(:)或英文分号(;)分开。
—location:指定一个目录和JAR文件。
提示:因为JAR文件还可以包含更多层次的文件结构,所以JAR文件实际上可以看成是一个文件路径。
如下配置片段所示:复制内容到剪贴板
代码:
<!-- 定义/path/to/file2.jar、/path/to/class2和/path/to/class3所组成的目录集 -->
<pathelement path="/path/to/file2.jar:/path/to/class2;/path/to/class3"/>
<!-- 定义由lib/helper.jar单个文件对应的目录 -->
<pathelement location="lib/helper.jar"/>
如果需要指定多个目录集,则应该使用dirset元素,该元素需要一个dir属性,dir属性指定该目录集的根路径。除此之外,dirset还可以使用include和exclude两个子元素来指定包含和不包含哪些目录,如下配置片段所示:复制内容到剪贴板代码:
<!-- 指定该目录集的根路径是build目录 -->
<dirset dir="build">
<!-- 指定包含apps目录下的所有classes目录 -->
<include name="appsclasses"/>
<!-- 指定排除目录名中有Test的目录 -->
<exclude name="apps*Test*"/>
</dirset>
上面配置文件代表build/apps目录下,所有名为classes、且文件名不包含Test子串的目录。如果希望配置多个文件,则可用fileset或者filelist元素,通常fileset使用模式字符串来匹配文件集,而filelist则通过列出文件名的方式来指定文件集。
—filelist元素需要指定如下两个属性:
—dir:指定文件集里多个文件所在的基准路径,这是一个必需的属性。
—files:多个文件名列表,多个文件名之间以英文逗号(,)或空白隔开。
如下示例配置片段:
<!-- 配置src/foo.xml和src/bar.xml文件组成的文件集 -->
<filelist id="docfiles" dir="src" files="foo.xml,bar.xml"/>
提示:几乎所有的Ant元素都可以指定两个属性:id和refid,其中id用于为该元素指定一个唯一标识,而refid用于指定引用另一个元素。例如下面filelist配置:
<filelist refid="docfiles"/>,该filelist元素所包含的文件集和前面docfiles文件集里包含的文件完全一样。
实际上,filelist还允许使用多个file子元素来指定文件列表,如下配置片段:复制内容到剪贴板
代码:
<filelist id="docfiles" dir="${doc.src}">
<!-- 通过两个file子元素指定的文件列表和通过files属性指定的效果完全一样 -->
<file name="foo.xml"/>
<file name="bar.xml"/>
</filelist>
fileset元素可指定如下两个属性:—dir:指定文件集里多个文件所在的基准路径,这是一个必需的属性。
—casesensitive:指定是否区分大小写,默认区分大小写。
除此之外,fileset元素还可以使用include和exclude两个子元素来指定包含和不包含哪些文件,如下配置片段所示:复制内容到剪贴板
代码:
<!-- 定义src路径下的文件集 -->
<fileset dir="src" casesensitive="yes">
<!-- 包含所有*.java
文件 -->
<include name="***Test*"/>
</fileset>
掌握了pathelement、dirset、filelist和fileset四个元素的用法之后,我们就可以使用path或者classpath将它们组合在一起使用了,如下配置片段所示:复制内容到剪贴板代码:
<classpath>
<!-- 定义classpath属性值所代表的路径 -->
<pathelement path="${classpath}"/>
<!-- 定义lib路径下的所有*.jar文件 -->
<fileset dir="lib">
<include name="**classes"/>
<exclude name="apps*Test*"/>
</dirset>
<!-- 定义res路径下的a.properties和b.xml文件 -->
<filelist dir="res" files="a.properties,b.xml"/>
</classpath>
1.6.4 Ant的任务(task)到目前为止,我们已经掌握了Ant生成文件的基本结构、以及project、target、property等元素的配置方式。而target元素的核心就是task,即每个target由一个或多个task组成。
Ant提供了大量的核心task和可选task,除此之外,Ant还允许用户定义自己的task,这大大扩展了Ant的功能。
本书由于篇幅关系,所以不可能详细介绍Ant所有的核心task和可选task,本书将会简要介绍一些常用的核心task。
—javac:用于编译一个或者多个Java源文件,通常需要srcdir和destdir两个属性,用于指定Java源文件的位置和编译后class文件的保存位置。
—java:用于运行某个Java类,通常需要classname属性,用于指定需要运行哪个类。
—jar:用于生成JAR包,通常需要指定destfile属性,用于指定所创建JAR包的文件名。除此之外,通常还应指定一个文件集,表明需要将哪些文件打包到JAR包里。
—sql:用于执行一条或多条SQL语句,通常需要driver、url、userid和password等属性,用于指定连接数据库的驱动类、数据库URL、用户名和密码等,还可以通过src来指定需要指定的SQL脚本文件,或者直接使用文本内容的方式指定SQL脚本字符串。
—echo:输出某个字符串。
—exec:执行操作系统的特定命令,通常需要executable属性,用于指定想执行的命令。
—copy:用于复制文件或路径。
—delete:用于删除文件或路径。
—mkdir:用于创建文件夹。
—move:用户移动文件和路径。
提示:%ANT_HOME%/docs/manual/CoreTasks路径下包含了Ant所有核心task的详细介绍,而%ANT_HOME%/docs/manual/OptionalTasks路径下包含了Ant所有可循task的详细介绍。读者可以参考这些文档来了解各task所支持的属性和选项。
下面定义了一份简单的生成文件,这份生成文件里包含了编译Java文件、运行Java程序、生成JAR包等常用的target,通过这份文件就可非常方便地管理该项目。
程序清单:codes/01/antQs/build.xml复制内容到剪贴板
代码:
<?xml version="1.0" encoding="GBK"?>
[code]<!-- 定义生成文件的project根元素,默认的target为空 -->
<project name="antQs" basedir="." default="">
<!-- 定义三个简单属性 -->
<property name="src" value="src"/>
<property name="classes" value="classes"/>
<property name="dest" value="dest"/>
<!-- 定义一组文件和目录集 -->
<path id="classpath">
<pathelement path="${classes}"/>
</path>
<!-- 定义help target,用于输出该生成文件的帮助信息 -->
<target name="help" description="打印帮助信息">
<echo>help - 打印帮助信息</echo>
<echo>compile - 编译Java源文件</echo>
<echo>run - 运行程序</echo>
<echo>build - 打包JAR包</echo>
<echo>clean - 清除所有编译生成的文件</echo>
</target>
<!-- 定义compile target,用于编译Java源文件 -->
<target name="compile" description="编译Java源文件">
<!-- 先删除classes属性所代表的文件夹 -->
<delete dir="${classes}"/>
<!-- 创建classes属性所代表的文件夹 -->
<mkdir dir="${classes}"/>
<!-- 编译Java文件,编译后的class文件放到classes属性所代表的文件夹内 -->
<javac destdir="${classes}" debug="true"
deprecation="false" optimize="false" failonerror="true">
<!-- 指定需要编译的Java文件所在的位置 -->
<src path="${src}"/>
<!-- 指定编译Java文件所需要第三方类库所在的位置 -->
<classpath refid="classpath"/>
</javac>
</target>
<!-- 定义run target,用于运行Java源文件,
运行该target之前会先运行compile target -->
<target name="run" description="运行程序" depends="compile">
<!-- 运行lee.HelloTest类,其中fork指定启动另一个JVM来执行java命令 -->
<java classname="lee.HelloTest" fork="yes" failonerror="true">
<classpath refid="classpath"/>
<!-- 运行Java程序时传入2个参数 -->
<arg line="测试参数1 测试参数2"/>
</java>
</target>
<!-- 定义build target,用于打包JAR文件,
运行该target之前会先运行compile target -->
<target name="build" description="打包JAR文件" depends="compile">
<!-- 先删除dest属性所代表的文件夹 -->
<delete dir="${dest}"/>
<!-- 创建dest属性所代表的文件夹 -->
<mkdir dir="${dest}"/>
<!-- 指定将classes属性所代表的文件夹下的所有
*.classes文件都打包到app.jar文件中 -->
<jar destfile="${dest}/app.jar" basedir="${classes}"
includes="**/*.class">
<!-- 为JAR包的清单文件添加属性 -->
<manifest>
<attribute name="Main-Class" value="lee.HelloTest"/>
</manifest>
</jar>
</target>
<!-- 定义clean target,用于删除所有编译生成的文件 -->
<target name="clean" description="清除所有编译生成的文件">
<!-- 删除两个目录,目录下的文件也一并删除 -->
<delete dir="${classes}"/>
<delete dir="${dest}"/>
</target>
</project>
注意:上面生成文件中定义javatask时粗体字代码指定了fork=true,这表明启动另一个JVM进程来运行lee.HelloTest类,这个属性通常是个陷阱!如果不指定该属性,该属性值默认是false,这表明使用运行Ant的同一个JVM来运行Java程序,这将导致随着Ant工具执行完成,被运行的Java程序也不得不退出——这当然不是开发者希望看到的。上面配置定义的生成文件里包含了5个target,这些target分别完成打印帮助信息、编译Java文件、运行Java程序、打包JAR包和清除编译中生成的文件。执行这些target可使用如下命令:
ant help:输出该生成文件的帮助信息。
ant compile:编译Java文件。
ant run:运行lee.HelloTest类。
ant build:将classes路径下所有class文件打包成app.jar,并放在dest目录下。
ant clean:删除classes和dest两个目录。