项目工具
我们平常在写 Java 程序的时候,基本的步骤都是 打开一个集成开发环境(Eclipse 或者 Intellij IDEA)-> 开开心心的敲代码 ->点击 Run,就可以很有成就的看到自己辛劳的成果了。但是在实际的项目开发中,我们的项目会很大,而且在部署到服务器的时候我们需要用到一些自动化的工具(比如 jenkins),这个时候我们熟悉的集成开发环境就不存在了,因此我们需要用另外一种编译、打包、运行程序的方法。熟悉 C 语言的同学会会知道在编译 C 语言程序的时候,有种叫做 makefile 的东西,makefile 功能的强大足够支持它编译整个 Linux 系统,那对于 Java 来说有没有好用的工具呢?好在世界那么大,办法总是有的,著名的 Apache 基金会有一个叫做 Ant 的项目,是构建 Java 程序的一把好手,而且 Ant 的源码也是用 Java 实现了,用“自己”来编译“自己”,感觉也是件奇妙的事情呢!
说了这么多,可能有些同学还不知道 Ant 到底是什么,我们给出官方的定义:
Apache Ant,是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于 Java 环境中的软件开发。由Apache软件基金会所提供.Ant 也可以有效地用于构建非Java应用程序,例如 C 或 C++ 的应用程序。更一般地,Ant 可以用来驾驶任何类型的能在目标和任务来描述过程。
学习好 Ant ,你将会更了解 Java 程序的构建过程。
首先进入 Ant 的下载界面 http://ant.apache.org/bindownload.cgi,Windows 平台的同学选在下载 .zip 格式的压缩包,目前(2016-10-27)最新的版本是 1.9.7 ,所以我们这里就已这个版本在 Windows 下的安装为教程(在安装 Ant 之前,我们要确保 JDK 已经正确的安装在我们的计算机上面了,因为 Ant 的运行需要依赖 JDK)。
;%ANT_HOME%\bin;
-> 点击确定到目前为止我们的 Ant 安装已经结束,不过我们需要验证 Ant 是否正确的安装。快捷键 win+R 快速打开运行窗口,然后输入 cmd 点击确定,我们就进入了 Windows 的命令行界面,然后输入 ant -version
显示如下的信息就代表我们安装成功了(大家在安装的时候可能会因为版本和笔者的不一样,显示的信息不完全一致,这是正常的,怎么可能你安装了 1.9.8 的版本还显示 1.9.7 的信息呢?显示对应的版本即可)
Apache Ant(TM) version 1.9.7 compiled on April 9 2016
如果失败的话,发生的原因会有很多,我建议主要检查一下几项:
使用Mac OS 的用户首先也需要进入官网下载 .zip 格式的文件,然后使用命令行进入你保存 .zip 文件的位置
1 进入保存下载文件的位置(这里以桌面为例)
cd ~/Desktop
2 切换到超级用户权限(需要提供密码验证)
sudo sh
3 把 .zip 文件放到合适的位置
mv apache-ant-1.9.7-bin.zip /usr/local/
4 进入 .zip 文件所在目录
cd /usr/local/
5 解压 .zip 文件
unzip apache-ant-1.9.7-bin.zip
6 改变 Ant 文件的所有权,这样以后再修改 Ant 目录什么的时候就不再次需要 root 权限
chown (your account name):(your account name) apache-ant-1.9.7
7 给你的 Ant 目录创建一个链接,创建完链接后,你可以指定你的 Ant 目录是 /usr/local/ant,而不是/usr/local/apache-ant-1.9.7
ln -s apache-ant-1.9.7 ant
8 打开设置环境变量的文件
vi /etc/bashrc
9 修改环境变量(按 i 按键进入编辑模式,在问价的末件加入如下两行)
export ANT_HOME=/usr/local/apache-ant-1.9.7
export PATH=${PATH}:${ANT_HOME}/bin
按下 Esc 键退出编辑模式,然后键入 :wq 即可退出
这个时候我们就需要验证 Ant 是否正确安装,方法同 Windows 下一样,这里就不再叙述了。
1 下载 Ant 文件,我们需要下载 .tar.gz 格式的文件,进入保存文件的目录(这里笔者以自己账户的目录为例)
cd /home/weegee/
2 切换到 root 权限(需要自己的密码验证)
su
3 移动文件到适合的位置
mv apache-ant-1.9.7-bin.tar.gz /usr/local/
4 进入 /usr/local/ 目录
cd /usr/local/
5 解压文件
tar -vxzf apahce-ant-1.9.7-bin.tar.gz
6 更改文件所有者,这样以后再修改 Ant 目录什么的时候就不再次需要 root 权限
chown weegee apache-ant-1.9.7
7 给你的 Ant 目录创建一个链接,创建完链接后,你可以指定你的 Ant 目录是 /usr/local/ant,而不是/usr/local/apache-ant-1.9.7
ln -s apache-ant-1.9.7 ant
8 打开设置环境变量的文件
vi /etc/profile
9 修改环境变量(按 i 按键进入编辑模式,在问件的末尾加入如下两行)
export ANT_HOME=/usr/local/apache-ant-1.9.7
export PATH=$PATH:$ANT_HOME/bin
按下 Esc 键退出编辑模式,然后键入 :wq 即可退出
10 让环境变量立即生效
source /etc/proifle
这个时候我们就需要验证 Ant 是否正确安装,方法同 Windows 下一样,这里就不再叙述了。
打开我们解压后的 Ant 的文件,可以看到它的文件内容如下
apache-ant-1.9.7
|-bin
|-etc
|-lib
|-manual
| ...这是一些文件,我们不做重点介绍
这里我主要说明下前四个文件夹的作用
- bin 是 ant 的程序运行入口,如果没有配置 ANT_HOME 的情况下,可以通过 bin 目录中的 bat 程序进行运行 build 任务。如:在 cmd 中运行 ant.bat 就可以执行程序,当然你选择要当前目录中存在一个 build.xml (build.xml是默认的ant执行文件,当然你可以指定其他文件)
- etc 目录中存放的都是一些xsl的输出模板,创建一个加强的导出各种任务的 XML 输出,使你的 build 文件摆脱过时的警告
- lib 目录中存放的是 ant 程序需要依赖的 jar 包
- manual 目录是 ant 程序的帮助文档
Ant 是以 build.xml 为默认的文件,如果不存在 build.xml,则 Ant 会报错,不过我们可以指定特定名称的 xml 文件作为 Ant 编译的文件。
首先我们创建一个 xml 文件并命名为 build.xml,其内容是:
<project name="ant-project" default="example">
<target name="example">
<echo message="Hello World!" />
target>
project>
上面代码的意思是,创建一个名称为 ant-project 的 Ant 工程,然后创建一个名称为 example 的执行目标,这个目标要做的事情就是打印字符串 Hello World!,很简单是不是。
然后我们通过命令行进入 build.xml 文件所在的目录,执行 ant 命令,可以看到输出
Buildfile: F:\BIUBIU\Blog\ant\demo1\build.xml
example:
[echo] Hello World!
BUILD SUCCESSFUL
Total time: 1 second
如果我们的 xml 文件的名字不是 build.xml,这个时候会发生什么呢?我们修改文件名为 ant.xml 然后同样运行 ant 命令,可以看到如下信息
Buildfile: build.xml does not exist!
Build failed
很显然命令没有执行成功并提示 build.xml 不存在,那我们怎么执行 ant.xml 文件呢?可以使用如下命令
ant -buildfile ant.xml
输出的结果和刚才的结果一样,屏幕上打印出了 Hello World!。
经过上一小节的学习,我们已经初步了解了 Ant 的执行方式,不过在实际运行 ant 命令的时候,会有很多的可选命令,我们输入 ant -help
可以看到这些命令。
语法元素说明如下:
attribute | description |
---|---|
-help | 显示描述ant 命令及其选项的帮助信息 |
-projecthelp | 显示包含在构建文件中的、所有用户编写的帮助文档。即为各个中description 属性的文本,以及包含在元素中的任何文本。将有description 属性的目标列为主目标(Main target),没有此属性的目标则列为子目标(Subtarget)。 |
-version | 要求ant 显示其版本信息,然后退出。 |
-quiet | 抑制并非由构建文件中的echo 任务所产生的大多数消息。 |
-verbose | 显示构建过程中每个操作的详细消息。此选项与-debug 选项只能选其一。 |
-debug | 显示Ant 和任务开发人员已经标志为调试消息的消息。此选项与-verbose 只能选其一。 |
-emacs | 对日志消息进行格式化,使它们能够很容易地由Emacs 的shell 模式(shellmode)所解析;也就是说,打印任务事件,但并不缩排,在其之前也没有[taskname]。 |
-logfile filename | 将日志输出重定向到指定文件。 |
-logger classname | 指定一个类来处理Ant 的日志记录。所指定的类必须实现了org.apache.tools.ant.BuildLogger 接口。 |
-listener classname | 为Ant 声明一个监听类,并增加到其监听者列表中。在Ant与IDE或其他Java程序集成时,此选项非常有用。可以阅读第六章以了解有关监听者的更多信息。必须将所指定的监听类编写为可以处理Ant 的构建消息接发。 |
-buildfile filename | 指定Ant 需要处理的构建文件。默认的构建文件为build.xml。 |
-Dproperty=value | 在命令行上定义一个特性名-值对。 |
-find filename | 指定Ant 应当处理的构建文件。与-buildfile 选项不同,如果所指定文件在当前目录中未找到,-find 就要求Ant 在其父目录中再进行搜索。这种搜索会继续在其祖先目录中进行,直至达到文件系统的根为止,在此如果文件还未找到,则构建失败。 |
-atuoproxy | jdk1.5以上的可以使用代理设置 |
-nouserlib | 运行ant时不使用用户lib中的jar包 |
-nice | 设计主线程优先级 |
-logfile | 使用指定的log日志 |
-noinput | 不允许交互输入 |
-keep-going, -k | 执行不依赖于所有目标 |
-propertyfile | 加载所有属性配置文件 -d 属性文件优先 |
这么多的命令我们在设计使用的时候用到的也不是很多,这里仅供大家了解,扩充知识面。
我们已经知道 Ant 是使用 Java 语言编写的,而在写 Java 程序的时候我们常常需要依赖很多外部的 jar 文件。在运行 ant 命令的时候,也需要依赖一些库文件,当需要的 jar 文件没有在 Ant 目录下的 lib 目录中提供的时候,我们可以把该文件直接放到 ant_home\lib 目录中作为全局的库使用。
如果你不想“污染”原始的ant程序,那么你可以把 jar 包放在当前 Windows 用户的目录下,具体位置应该是 ${user.home}/.ant/lib 下。没有以上目录可以手动建立。
如果你只是临时要添加 lib ,又不想把 lib 添加到当前 ant 工程中,你可以使用如下命令在运行ant程序的时候指定依赖的 lib 参数。
ant -lib xxx.jar -f build.xml
或者,你还可以在当前 build 工程的 classpath 目录中添加你的工程依赖 jar 包,这个就是后话了,我们会在后面的章节中介绍这种方法。
在前面的 一个简单的例子 章节,我们已经看到了一个最基本的 build.xml 文件的组成
<project name="ant-project" default="example">
<target name="example">
<echo message="Hello World!" />
target>
project>
project
元素是一个构建xml文件必不可少的元素,且只能有一个此元素,作为最外层的元素,所有其他的元素标签都必须在其包含范围内。 target
元素是构建文件执行的基本单位,一个xml文件中可以有多个 target 元素,每一个 target 元素代表一个独立的任务,但是要想执行完所有的 target,我们必须制定 target 的执行顺序,不然及时我们在xml文件中写入了相应的任务,在构建的时候也不会执行。在只有一个 target 元素的xml文件中,我们可以按照例子中的方法,在 project 中的 default 属性的值制定默认的 target default="example"
,要是有多个 target 怎么办,default 属性的值又不能指定多个,这个时候我们就需要借助另外一个任务元素 depends。 depends
下面我们通过一个简单的例子简要介绍 depends 的用法。
<project name="ant-project" default="next">
<target name="example">
<echo message="Hello World!" />
target>
<target name="next" depends="example">
<echo message="Hello World Again!" />
target>
project>
我们这里写了两个 target,然后让默认的 target 是 next,看一下执行的结果是什么样
example:
[echo] Hello World!
next:
[echo] Hello World Again!
BUILD SUCCESSFUL
Total time: 0 seconds
可以看到执行的顺序是先执行 example,然后才执行 next,这是因为 next 依赖于 example,所以在构建xml文件的时候,按照 default 的值找到了 next,但是 next 说他依赖于 example,你要想构建我,必须先构建 example,不然我就自杀:-),编译器拗不过他,只好先去构建 example,然后 next 才愉快的接受构建。
但是要是遇到更多的 target 我们应该怎么办呢?
比如有三个 target init、compile 和 run,我们知道在编译运行程序的时候首先需要初始化一个目录、文件什么的,然后再执行编译过程,编译成功之后再运行,就可以得到程序的结果,在xml文件中我们可以这样指定这三个 target 的顺序。
方法一(这里仅做演示使用,就不写出全部的xml文件内容了)
target init
target compile depends init
target run depends compile
这是依次依赖的写法。
方法二
target init
target compile
target run depends init,compile
我们这里在最后一个 target 中把全部的依赖加进来,依赖按照指定的顺序依次运行。
我们这里介绍了一个完整xml文件的最基本的元素,在 Ant 的构建物文件中,还有很多很多这样的元素,按照元素功能的不同我们又把这些元素分为两大类,Tasks 和 Types,刚才讲到的 target 和 depends 都是属于 Tasks,在实际的使用中我们也是使用 Tasks 的元素比较多,Types 的元素主要是一个文件操作的功能。
在实际的开发过程中,我们基本都是面对集成开发环境写代码,这个时候如果需要执行 Ant 命令还在命令行下输入就会降低效率,因此下面的内容中我就以 Eclipse 和 IDEA Intellij 为例介绍如何在集成开发环境(IDE)中集成 Ant,相信 90% 以上的读者在日常开发中都是使用这两款 IDE 吧。
Eclipse 在很早的版本就增加了对 Ant 的支持,我么可以在 Window->Perferences->Ant 页面看到 Ant的详细信息,这里可以设置 Ant 的目录以及编写 build.xml 文件时候的编译器语法显示等等。
其实在使用 Ant 的时候也是非常简单,我们只需要按照平常的使用编写代码就好了,最后在该项目下新建一个 build.xml 文件,然后在该文件里编写构建项目的 Ant 过程就可以了,最后右键点击该 build.xml 文件,Run As->Ant Build,就可以在控制台看到输出了。
有的人会觉得这样太麻烦了,这个时候我们可以采用一些偷懒的方法,在把功能代码写好了之后,然后 File->Export->General->Ant Buildfiles 就可以在该项目下导出一个由系统帮你编写好的 build.xml 文件,如果有其他的需要就可以在这个文件中修改为自己想要的功能。
有的时候我们不想使用 Eclipse 自带的 Ant 版本,想更换为自己的 Ant,我们就可以在 Window->Perferences->Ant->Runtime->Classpath->Ant Home Entries ,然后点击右侧的 Ant Home 修改 Ant。
IDEA Intellij 中也集成了 Ant 的功能,我们也只需要在项目下新建 build.xml 文件就可,然后在 Ant Build 窗口(没有的话就在 View->Tool Windows->Ant Build)中打开,然后在 Ant Build 窗口点击加号把需要构建的 build.xml 文件加入进来(可以加入多个 build.xml 文件进行构建),然后 Run Build 就可以在 Messages 窗口看到输出(没有的话就在 View->Tool Windows->Messages)中打开。
随着构建工具的发展,现在项目开发中我们在IDE中使用 Ant 的频率已经不那么多了,更多的是使用 Maven、Gradle,因此编写 Ant 文件比较繁琐,但是在某些场合 Ant 依然是很优秀的,而且学习 Ant 可以对项目运行过程有更加详细的了解,所以不要放弃!
要想写好 build.xml 文件,对 Ant 的基本元素要有非常熟悉的了解,前面的章节我们介绍了 target、depends 元素,在下面的内容中我们会更加详细的介绍 Ant 的元素
project 元素是 Ant 构件文件的根元素, Ant 构件文件应该包含一个 project 元素,否则会发生错误。在每个 project 元素下,可包含多个 target 元素。接下来向读者展示一下 project 元素的各属性。
name
属性:用于指定 project 元素的名称。 default
属性:用于指定 project
默认执行时所执行的 target
的名称。 basedir
属性:用于指定基路径的位置。该属性没有指定时,使用 Ant
的构件文件的附目录作为基准目录。
<project name="ant-project" default="print-dir" basedir=".">
<target name="print-dir">
<echo message="The base dir is: ${basedir}" />
target>
project>
从上例可以看出,在这里定义了 default 属性的值为 print-dir,即当运行 ant 命令时,如果没有指明执行的 target ,则将执行默认的 target(print-dir)。此外,还定义了 basedir 属性的值为 “.” ,. 表示当前目录,进入当前目录后运行 ant 命令,得一下结果:
print-dir:
[echo] The base dir is: F:\BIUBIU\Blog\ant\demo1
BUILD SUCCESSFUL
Total time: 0 seconds
target 为 ant 的基本执行单元或是任务,它可以包含一个或多个具体的单元/任务。多个 target 可以存在相互依赖关系。它有如下属性:
name
属性:指定 target 元素的名称,这个属性在一个 project 元素中是唯一的。我们可以通过指定 target 元素的名称来指定某个 target。 depends
属性:用于描述 target 之间的依赖关系,若与多个 target 存在依赖关系时,需要以 “.” 间隔。 Ant 会依照 depends 属性中 target 出现的顺序依次执行每个 target ,被依赖的 target 会先执行。 if
属性:用于验证指定的属性是存在,若不存在,所在 target 将不会被执行。 unless
属性:该属性的功能与 if 属性的功能正好相反,它也用于验证指定的属性是否存在,若不存在,所在 target 将会被执行。 description
属性:该属性是关于 target 功能的简短描述和说明。 示例:
<project name="ant-target" default="print">
<target name="version" if="ant.java.version">
<echo message="Java Version: ${ant.java.version}" />
target>
<target name="print" depends="version" unless="docs">
<description>
a depend example!
description>
<echo message="The base dir is: ${basedir}" />
target>
project>
从以下结果后可以看到,我们运行的是名为 print 的 target ,由于它依赖于 version 这个 target 任务,所以 version 将首先被执行,同时因为系统配置了 JDK,所以 ant.java.version 属性存在,执行了 version,输出信息: [echo] Java Version: 1.8
version 执行完毕后,接着执行 print,因为 docs 不存在,而 unless 属性是在不存在时进入所在 target 的,由此可知 print 得以执行,输出信息: [echo] The base dir is:D:\Workspace\AntExample\build
。
property 元素可看作参量或者参数的定义,project 的属性可以通过 property 元素来设定,也可在 Ant 之外设定。若要在外部引入某文件,例如 build.properties
文件,可以通过如下内容将其引:
<property file="build.properties"/>
然后就可以使用 build.properties 中变量的名字。
property 元素可用作 task 的属性值。在 task 中是通过将属性名放在 ${属性名}
之间,并放在 task 属性值的位置来实现的。
Ant 提供了一些内置的属性,它能得到的系统属性的列表与 Java 文档中 System.getProperties() 方法得到的属性一致,这些系统属性可参考 sun 网站的说明。同时, Ant 还提供了一些它自己的内置属性,如下:
basedir
: project 基目录的绝对路径; ant.file
: buildfile的绝对路径,上例中 ant.file 值为D:\Workspace\AntExample\build
; ant.version
: Ant 的版本信息,本文为 1.9.7 ; ant.project.name
: 当前指定的 project 的名字,即前文说到的 project 的 name 属性值; ant.java.version
: Ant 检测到的JDK版本,本文为 1.8
。举例说明如下:
<project name="ant-project" default="example">
<property name="name" value="jojo" />
<property name="age" value="25" />
<target name="example">
<echo message="name: ${name}, age: ${age}" />
<echo message="${ant.java.version}" />
target>
project>
打印结果:
example:
[echo] name: jojo, age: 25
[echo] 1.8
BUILD SUCCESSFUL
Total time: 0 seconds
上例中用户设置了名为 name 和 age 的两个属性,这两个属性设置后,在下文中可以通过 ${name}
和 ${age}
分别取得这两个属性值,并打印了 Ant 的内置属性。
此外我们可以通过System.getProperties()
得到更多有关系统的属性,并且通过 Ant 打印出来,不过首先我们通过一个 Java 程序获得属性的 key。
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
public class SystemProperties {
@SuppressWarnings("rawtypes")
public static void main(String[] args){
Map map = System.getenv();
for(Iterator iterator = map.keySet().iterator(); iterator.hasNext();){
String key = (String) iterator.next();
String value = (String) map.get(key);
System.out.println(key + " : " + value);
}
System.out.println("---------------我是分割线---------------");
Properties properties = System.getProperties();
for(Iterator iterator = properties.keySet().iterator(); iterator.hasNext();){
String key = (String) iterator.next();
String value = (String) properties.get(key);
System.out.println(key + " : " + value);
}
}
}
输出结果(这里只是随便截取了几个值,上面的是系统的 environment,下面的是 properties,关于 environment 我们会在下面的章节详细介绍)。
PUBLIC:C:\Users\Public
NUMBER_OF_PROCESSORS:4
windir:C:\Windows
=:::::\
---------------我是分割线---------------
java.runtime.name:Java(TM) SE Runtime Environment
sun.boot.library.path:D:\Program Files\Java\jdk1.8.0_101\jre\bin
java.vm.version:25.101-b13
然后打印一个 property 和 environment。
<project name="ant-project" default="example">
<property environment="env"/>
<target name="example">
<echo message="${java.vm.version}" />
<echo message="${env.NUMBER_OF_PROCESSORS}" />
target>
project>
输出结果:
example:
[echo] 25.101-b13
[echo] 4
BUILD SUCCESSFUL
Total time: 0 seconds
copy 将文件或者 FileSet 复制到一个新的文件或者目录。 FileSet 用于指定用于复制的文件集合,如果使用了 FileSet,copy 的 todir 必须指定一个值,copy 支持的常用的 attribute 如下:
attribute | description |
---|---|
file | 要复制的文件 |
tofile | 复制到新的文件的文件名 |
todir | 复制到新的目录的目录名 |
overwrite | 默认值为false,也就是只有当被复制的文件比目标文件新的时候才复制,如果需要强制覆盖目标文件,需要将overwrite设置为true |
copy 主要用来对文件和目录的复制功能。举例如下:
复制单个文件:
<copy file="old.txt" tofile="new.txt"/>
对文件目录进行复制:
<copy todir="../dest_dir">
<fileset dir="src_dir"/>
copy>
<copy todir="../dest/dir">
<fileset dir="src_dir">
<exclude name="**/*.java"/>
fileset>
copy>
<copy todir="../dest/dir">
<fileset dir="src_dir" excludes="**/*.java"/>
copy>
将文件复制到另外的目录:
<copy file="src.txt" todir="../some/other/dir"/>
有本地的 copy 就有远程的 copy,我们可以只用 Ant 把本地的内容复制到远程的服务器上面,不过大多数情况下我们都需要先连接到远程服务器
"172.17.227.186" username="root" password="feixun*123" command="service jboss stop" trust="true" />
使用 sshexec 连接到服务器,接下来就可以使用 scp 命令复制了
<scp todir="user:password@IP:/usr/local/jboss/standalone/deployment s" trust="true">
<fileset dir="../build/compileDir/compileWar">
<include name="**/*.war" />
fileset>
scp>
对文件或目录进行删除,举例如下:
删除某个文件:
<delete file="/res/image/cat.jpg"/>
删除某个目录:
dir="/res/image"/>
删除所有的jar文件或空目录:
<delete includeEmptyDirs="true">
<fileset dir="." includes="**/*.jar"/>
delete>
创建目录
dir="/home/philander/build/classes"/>
<project name="propertyStudy" default="mkdir" description="创建目录">
<target name="mkdir">
<mkdir dir="mkdirTest"/>
target>
project>
移动文件或目录,举例如下:
移动单个文件:
<move file="sourcefile" tofile="destfile"/>
移动单个文件到另一个目录:
<move file="sourcefile" todir="movedir"/>
移动某个目录到另一个目录:
<move todir="newdir">
<fileset dir="olddir">
<include name="**/*.jar"/>
<exclude name="**/ant.jar"/>
fileset>
move>
echo 的作用是根据日志或监控器的级别输出信息。它包括 message、 file 、append 和 level 四个属性,举例如下
<echo message="ant message" file="/logs/ant.log" append="true">
标签用来生成一个JAR文件,其属性如下。
destfile
表示JAR文件名。 basedir
表示被归档的文件名。 includes
表示别归档的文件模式。 exchudes
表示被排除的文件模式。compress
表示是否压缩。我们在写程序的时候,会需要依赖很多第三方 jar 包,这些 jar 包就是别人写好的程序然后打成 jar 包让我们使用的,其实我们也可是自己写一些程序然后打成 jar 包,以后能用到的时候直接把 jar 包引入进来就好了。
示例:
<jar destfile="${webRoot}/${ash_jar}" level="9" compress="true" encoding="utf-8" basedir="${dest}">
<manifest>
<attribute name="Implementation-Version" value="Version: 2.2"/>
manifest>
jar>
上面的 mainfest 是jar包中的 MEAT-INF 中的 MANIFEST.MF 中的文件内容。
同样打包操作的的还有 war、tgz,解压操作 uzip
<zip basedir="${basedir}\classes" zipfile="temp\output.zip"/>
<gzip src="classes\**\*.class" zipfile="output.class.gz"/>
<unzip src="output.class.gz" dest="extractDir"/>
<war destfile="${webRoot}/ash.war" basedir="${basedir}/web" webxml="${basedir}/web/WEB-INF/web.xml">
<exclude name="WEB-INF/classes/**"/>
<exclude name="WEB-INF/lib/**"/>
<exclude name="WEB-INF/work/_jsp/**"/>
<lib dir="${lib.dir}" includes="**/*.jar, **/*.so, **/*.dll">
<exclude name="${webRoot}\${helloworld_jar}"/>
lib>
<lib file="${webRoot}/${helloworld_jar}"/>
<classes dir="${dest}" includes="**/*.xml, **/*.properites, **/*.xsd"> classes>
war>
把程序打成 jar 包的一个简单例子
<project name="javaTest" default="jar" basedir=".">
<target name="jar" description="打JAR包">
<jar destfile="helloworld.jar" basedir="build/classes">
<manifest>
<attribute name="Main-class" value="HelloWorld" />
manifest>
jar>
target>
project>
这个例子只是一个最简单的构建文件,仅仅把编译后的文件坐在的目录 classes 中的 class 文件打成 jar包。
javadoc 是一个非常复杂的 task,一共有50多个 attribute,幸好只有四个 attribute 是必须的,那就是 sourcepath、sourcefiles、sourcepathref 和 destdir,对于sourcepath、sourcefiles 和 sourcepathref 只需要指定其中一个,如果都没有指定,那么必须提供嵌套的 sourcepath、 fileset 或者 packageset,从字面上很容易理解 sourcepath、sourcefiles、sourcepathref 是源文件所在的目录,而 destdir 是生成的 javadoc API 所在的目录。
下面是一些我们常用的 attribute:
attribute | description |
---|---|
sourcepath | 源文件目录,可用嵌套的 sourcepath 代替 |
sourcefiles | 逗号分割的文件列表,可用嵌套的 source 代替 |
destdir | 输出文件所在的目录,通常我们习惯将结果保存在 doc/api 下 |
packagenames | 逗号分割的 java 包列表,可用嵌套的 package 代替 |
packageList | 指定一个文件,文件内容是 javadoc 需要处理的包的列表 |
classpath | 指定 class 文件所在的位置,可用嵌套的 classpath 代替 |
use | 生成 class 和 package 的用法 |
version | 生成 @version 的信息 |
author | 生成 @author 的信息 |
windowtitle | 生成的 doc 的浏览的窗口的标题 |
header | 每一页的 header 信息 |
footer | 每一页的 footer 信息 |
bottom | 每一页的 bottom 信息 |
nodeprecated | 不生成 @deprecated 的信息 |
常见的可以嵌套的参数如下:packageset、fileset、package、excludepackage、source、doctitle、header、footer 和 bottom,它们的具体用法请参考官方文档。
javadoc 的 sourcepath、classpath 和 bootclasspath 是 path-like 机构,也就是完全可以用嵌套的 sourcepath、 classpath 和 bootclasspath 来代替。
<javadoc packagenames="com.dummy.test.*"
sourcepath="src"
defaultexcludes="yes"
destdir="docs/api"
author="true"
version="true"
use="true"
windowtitle="Test API">
<classpath refid="compile.classpath"/>
javadoc>
<javadoc author="true"
destdir="{javadoc.dir}"
packagenames="org.example.helloworld.*"
sourcepath="src"
use="true"
version="true"
windowtitle="Helloworld api spec"
private="true">
<classpath refid="compile.classpath"/>
javadoc>
<javadoc destdir="docs/api" author="true" version="true" use="true" windowtitle="Test API">
<packageset dir="src" defaultexcludes="yes">
<include name="com/dummy/test/**" />
<exclude name="com/dummy/test/doc-files/**"/>
packageset>
<doctitle>
Test]]>
doctitle>
<bottom>
Copyright © 2000 Dummy Corp. All Rights Reserved.]]>
bottom>
<tag name="todo" scope="all" description="To do:" />
<group title="Group 1 Packages" packages="com.dummy.test.a*"/>
<group title="Group 2 Packages" packages="com.dummy.test.b*:com.dummy.test.c*"/>
<link offline="true" href="::URL::http://java.sun.com/ products/jdk/1.2/docs/api/" packagelistLoc="C:/tmp"/>
<link href="::URL::http://developer.java.sun.com/developer/products/xml/docs/api/"/>
javadoc>
由Ant构建文件调用的外部命令或程序,env 元素制定了哪些环境变量要传递给正在执行的系统命令,env 元素可以接受以下属性。
file
表示环境变量值的文件名。此文件名要被转换位一个绝对路径。 path
表示环境变量的路径。Ant会将它转换为一个本地约定。 value
表示环境变量的一个直接变量。 key
表示环境变量名。注意 file、path 或 value 只能取一个,在 property 部分我们已经介绍了该元素的功能,这里就不重复了。
由Ant构建文件调用的程序,可以通过 arg 元素向其传递命令行参数,如 apply、exec 和 java 任务均可接受嵌套 arg 元素,可以为各自的过程调用指定参数。以下是 arg 的所有属性。
values
是一个命令参数。如果参数中有空格,但又想将它作为单独一个值,则使用此属性。 file
表示一个参数的文件名。在构建文件中,此文件名相对于当前的工作目录。 line
表示用空格分隔的多个参数列表。 pathref
引用的 path (使用 path 元素节点定义 path)的id prefix
前缀 suffix
后缀例子
value="-l -a"/>
是一个含有空格的单个的命令行变量。
<arg line="-l -a"/>
是两个空格分隔的命令行变量。
<arg path="/dir;/dir2:\dir3"/>
是一个命令行变量,其值在DOS系统上为\dir;\dir2;\dir3
;在Unix系统上为/dir:/dir2:/dir3
。
javac 使我们再熟悉不过的了,其功能就是把 java 程序编译成 class 文件,我们来看看在 Ant 中它又焕发可什么样的光彩。
先看一个基本的 javac 命令:
javac -d build/classes -classpath %CLASSPATH%;lib/outlib.jar -sourcepath src -g
该标签用于编译一个或一组java文件,其属性如下。
srcdir
表示源程序的目录,相当于 javac 命令的 -sourcepath 参数,srcdir 也是 javac 的隐式 implicit 的 FileSet,因此 srcdir 支持 FileSet 的所有特征。 destdir
表示 classes 文件的输出目录,相当于 javac 命令的 -d 参数。 include
表示被编译的文件的模式。 excludes
表示被排除的文件的模式。 classpath
表示所使用的类路径。 debug
表示包含的调试信息,debug=”yes” 相当于 javac 命令的 -g 参数,debug=”no” 相当于 javac 命令的 -g:none 参数。 optimize
表示是否使用优化,默认是 off,optimize=”on” 相当于 javac 命令的 -o 参数。 verbose
表示提供详细的输出信息。 fileonerror
如果编译错误,build 是否继续,默认是 true。deprecation
输出对于那些使用了 deprecation 的API的源文件的位置,默认是 off,deprecation=”on” 相当于 javac 命令的 -deprecation 参数。fork
使用外部的JDK编译器来运行 javac,默认是 no,采用 yes 可以取得更好的效率,当然对机器的要求也高。示例:
<javac srcdir="${src}" destdir="${dest}"/>
<javac destdir="{build.classes.dir}"
debug="{build.debug}"
includeAntRuntime="yes"
srcdir="{sirc.dir}">
<classpath refid="compile.classpath"/>
<include name="**/*.java"/>
javac>
<project name="javacTest" default="compile" basedir=".">
<target name="clean" description="删除编译后产生的目录及文件">
<delete dir="build" />
target>
<target name="compile" depends="clean" description="编译源文件">
<mkdir dir="build/classes" />
<javac srcdir="src" destdir="build/classes" />
target>
project>
java 我们也是很熟悉,它就是用来执行编译生成的.class文件,其属性如下:
classname
表示将执行的类名。 jar
表示包含该类的JAR文件名。 classpath
所表示用到的类路径。 fork
表示在一个新的虚拟机中运行该类。 failonerror
表示当出现错误时自动停止。 output
表示输出文件。 append
表示追加或者覆盖默认文件。示例
"com.hoo.test.HelloWorld" classpath="${hello_jar}"/>
编译并运行JAVA程序
<project name="javaTest" default="run" basedir=".">
<target name="clean" description="清除目录">
<delete dir="build" />
target>
<target name="compile" depends="clean" description="编译">
<mkdir dir="build/classes" />
<javac srcdir="src" destdir="build/classes" />
target>
<target name="run" depends="compile" description="运行">
<java classname="AntTest">
<classpath>
<pathelement path="build/classes" />
classpath>
java>
target>
project>
filelist 是一个支持命名的文件列表的数据类型,包含在一个 filelist 类型中的文件不一定是存在的文件。以下是其所有的属性:
dir
是用于计算绝对文件名的目录。 files
是用逗号分隔的文件名列表。 refid
是对某处定义的一个 filelist 的引用。 注意 dir 和 files 都是必要的,除非指定了 refid (这种情况下,dir 和 files 都不允许使用)。
示例
文件集合 ${doc.src}/foo.xml和${doc.src}/bar.xml. 这些文件也许还是不存在的文件.
<filelist id="docfiles" dir="${doc.src}" files="foo.xml bar.xml"/>
<filelist refid="docfiles"/>
<filelist id="docfiles" dir="${doc.src}">
<file name="foo.xml"/>
<file name="bar.xml"/>
filelist>
fileset 数据类型定义了一组文件,并通常表示为 fileset 元素。不过,许多 ant 任务构建成了隐式的 fileset,这说明他们支持所有的 fileset 属性和嵌套元素,与 filelist 不同的是 fileset 下的文件必须存在。以下为 fileset 的属性列表:
dir
表示 fileset 的基目录。 casesensitive
的值如果为 false,那么匹配文件名时,fileset 不是区分大小写的,其默认值为 true。 defaultexcludes
用来确定是否使用默认的排除模式,默认为 true。 excludes
是用逗号分隔的需要派出的文件模式列表。 excludesfile
表示每行包含一个排除模式的文件的文件名。 includes
是用逗号分隔的,需要包含的文件模式列表。 includesfile
表示每行包括一个包含模式的文件名。示例
<fileset id="lib.runtime" dir="${lib.path}/runtime">
<include name="**/*.jar"/>
<include name="**/*.so"/>
<include name="**/*.dll"/>
fileset>
<fileset id="lib.container" dir="${lib.path}/container">
<include name="**/*.jar"/>
fileset>
<fileset id="lib.extras" dir="${lib.path}">
<include name="test/**/*.jar"/>
fileset>
fileset 是对文件的分组,而 patternset 是对模式的分组,他们是紧密相关的概念。
patternset 支持4个属性:includes、excludex、includexfile、excludesfile,这些与 fileset 相同。
patternset 还允许以下嵌套元素:include、exclude、includefile 和 excludesfile。
示例
<patternset id="non.test.sources">
<include name="**/*.java"/>
<exclude name="**/*Test*"/>
patternset>
<patternset id="sources">
<include name="std/**/*.java"/>
<include name="prof/**/*.java" if="professional"/>
<exclude name="**/*Test*"/>
patternset>
<patternset includesfile="some-file"/>
<patternset>
<includesfile name="some-file"/>
<patternset/>
<patternset>
<includesfile name="some-file"/>
<includesfile name="${some-other-file}" if="some-other-file"/>
<patternset/>
下面看一下 fileset 和 patternset 的简单使用:
<fileset id="sources" dir="src"
includes="**/*.java"
excludes="**/test/**/*.java">
fileset>
等价于
<fileset id="sources" dir="src">
<patternset>
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
patternset>
fileset>
等价于
<patternset id="non.test.source">
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
patternset>
<fileset id="sources" dir="src">
<patternset refid="non.test.source"/>
fileset>
filterset 定义了一组过滤器,这些过滤器将在文件移动或复制时完成文件的文本替换。
主要属性如下:
begintoken
表示嵌套过滤器所搜索的记号,这是标识其开始的字符串。 endtoken
表示嵌套过滤器所搜索的记号这是标识其结束的字符串。 id
是过滤器的唯一标志符。 refid
是对构建文件中某处定义一个过滤器的引用。示例
<copy file="${build.dir}/version.txt" toFile="${dist.dir}/version.txt">
<filterset>
<filter token="DATE" value="${TODAY}"/>
filterset>
copy>
<copy file="${build.dir}/version.txt" toFile="${dist.dir}/version.txt">
<filterset begintoken="%" endtoken="*">
<filter token="DATE" value="${TODAY}"/>
filterset>
copy>
<copy toDir="${dist.dir}/docs">
<fileset dir="${build.dir}/docs">
<include name="**/*.html">
fileset>
<filterset begintoken="%" endtoken="*">
<filtersfile file="${user.dir}/dist.properties"/>
filterset>
copy>
<filterset id="myFilterSet" begintoken="%" endtoken="*">
<filter token="DATE" value="${TODAY}"/>
filterset>
<copy file="${build.dir}/version.txt" toFile="${dist.dir}/version.txt">
<filterset refid="myFilterSet"/>
copy>
path 元素用来表示一个类路径,不过它还可以用于表示其他的路径。在用作几个属性时,路经中的各项用分号或冒号隔开。在构建的时候,此分隔符将代替当前平台中所有的路径分隔符,其拥有的属性如下:
location
表示一个文件或目录。Ant在内部将此扩展为一个绝对路径。refid
是对当前构建文件中某处定义的一个 path 的引用。 path
表示一个文件或路径名列表。path 支持的嵌套元素如下:
0..n
个嵌套的 pathelement, pathlelement 定义在路径下的一个或者多个文件,pathelement 支持 location 和 path 这两个 attribute,用法和 path 元素一样;0..n
个嵌套的 fileset ;0..n
个嵌套的 path; 示例
<path id="buildpath">
<fileset refid="lib.runtime"/>
<fileset refid="lib.container"/>
<fileset refid="lib.extras"/>
path>
<path id="src.paths">
<fileset id="srcs" dir=".">
<include name="src/**/*.java"/>
fileset>
path>
<path>
<pathelement location="{libdir}/servlet.jar"/>
<pathelement location="{libdir}/logging.jar"/>
<pathelement path="{builddir}"/>
<pathelement path="{utilpath}"/>
path>
path 最常用的是 classpath 这个形式,但是也用于其他用途。
<classpath>
<pathelement path="{builddir}"/>
classpath>
等价于
<classpath path="{builddir}"/>
<classpath>
<pathelement path="{builddir1}"/>
<pathelement path="{builddir2}"/>
classpath>
等价于
<classpath path="{builddir1};{builddir2}"/>
在学习 Ant 的时候,我们会发现知识太繁多,根本没兴趣一直学下来,其实学习 Ant 主要的就是 抄。
这里我告诉大家如何能够对 Ant 有更深的掌握。
1. 首先我们需要知道一些基本的元素以及使用方法,这些看前面我们讲解的内容皆可以获得;
2. 写一些简单的程序,然后在构建文件中使用我们讲解的元素来编译运行程序;
3. 遇到比较多的程序的时候,我们需要借助一些现有的构建文件,比如我们需要把一个 webproject 打成 war 包,但是我们又不会写构建文件,这个时候我们就需要一个别人写好的构建文件,然后仔细揣摩写法,遇到不懂的地方再查阅该元素的详细用法,最后修改构建文件满足自己的要求;
4. 遇到新的功能我们也要尝试使用 Ant 来完成,比如我们需要使用 JUnit 来测试程序,这个时候我们就需要借助 Ant 的 JUnit 任务,我们首先查阅 junit 的用法,然后在构建文件中写入该任务,不断修改直至成功运行。
下面我给出几个常用的构建过程,大家可以拿来仔细研究,对于不懂的部分,有些注释写的也很详细。
<project name="antwebproject" default="war" basedir=".">
<property name="classes" value="build/classes" />
<property name="build" value="build" />
<property name="lib" value="WebRoot/WEB-INF/lib" />
<target name="clean">
<delete dir="build" />
target>
<target name="compile" depends="clean">
<mkdir dir="${classes}" />
<javac srcdir="src" destdir="${classes}" />
target>
<target name="war" depends="compile">
<war destfile="${build}/AntProject.war" webxml="WebRoot/WEB-INF/web.xml">
<fileset dir="WebRoot" includes="**/*.jsp" />
<lib dir="${lib}" />
<classes dir="${classes}" />
war>
target>
project>
<project name="ant_firsttest" default="dist" basedir=".">
<description>ant firsttest!description>
<property name="src" location="src"/>
<property name="build" location="build"/>
<property name="dist" location="dist"/>
<property name="web" location="web"/>
<target name="init">
<tstamp/>
<mkdir dir="${build}/WEB-INF/lib"/>
<mkdir dir="${build}/WEB-INF/classes"/>
<mkdir dir="${build}/WEB-INF/classes/javafile/package1"/>
<mkdir dir="${build}/WEB-INF/classes/javafile/package2"/>
target>
<target name="compile" depends="init"
description="compile the source " >
<javac srcdir="${src}/javafile/package1" destdir="${build}/WEB-INF/classes/javafile/package1"/>
<javac srcdir="${src}/javafile/package2" destdir="${build}/WEB-INF/classes/javafile/package2"/>
<copy file="${src}/hello_ant.xml" tofile="${build}/WEB-INF/classes/hello_ant.xml" />
target>
<target name="dist" depends="compile"
description="generate the distribution" >
<mkdir dir="${dist}"/>
<copy file="${web}/image/a.gif" tofile="${build}/image/a.gif" />
<copy file="${web}/WEB-INF/web.xml" tofile="${build}/WEB-INF/web.xml" />
<copy file="${web}/WEB-INF/lib/a.jar" tofile="${build}/WEB-INF/lib/a.jar" />
<copy file="${web}/index.jsp" tofile="${build}/index.jsp" />
<jar jarfile="${dist}/ant_firsttest.jar" basedir="${build}"/>
target>
<target name="clean"
description="clean up" >
<delete dir="${build}"/>
<delete dir="${dist}"/>
target>
project>
<project name="HelloWorld" default="run" basedir=".">
<property name="src" value="src"/>
<property name="dest" value="classes"/>
<property name="hello_jar" value="helloWorld.jar"/>
<target name="init">
<mkdir dir="${dest}"/>
<mkdir dir="temp"/>
<mkdir dir="temp2"/>
target>
<target name="compile" depends="init">
<javac srcdir="${src}" destdir="${dest}"/>
target>
<target name="build" depends="compile">
<jar destfile="${hello_jar}" basedir="classes">
<manifest>
<attribute name="Built-By" value="${user.name}"/>
<attribute name="Main-class" value="package.Main"/>
manifest>
jar>
<copy file="${hello_jar}" tofile="${dest}\temp.jar"/>
<copy todir="temp">
<fileset dir="src">
<include name="**/*.java"/>
fileset>
copy>
<copy todir="temp2">
<fileset dir="src">
<and>
<contains text="main"/>
<size value="1" when="more"/>
and>
fileset>
copy>
<move file="${dest}\temp.jar" tofile="temp\move-temp.jar"/>
<zip basedir="${basedir}\classes" zipfile="temp\output.zip"/>
<gzip src="classes\**\*.class" zipfile="output.class.gz"/>
<unzip src="output.class.gz" dest="extractDir"/>
target>
<target name="run" depends="build">
<java classname="com.hoo.test.HelloWorld" classpath="${hello_jar}"/>
target>
<target name="clean">
<delete dir="${dest}"/>
<delete file="${hello_jar}"/>
target>
<tstamp>
<format property="OFFSET_TIME"
pattern="HH:mm:ss"
offset="10" unit="minute"/>
tstamp>
<target name="rerun" depends="clean,run">
<echo message="###${TSTAMP}#${TODAY}#${DSTAMP}###"/>
<aunt target="clean"/>
<aunt target="run"/>
target>
project>
<project name="structured" default="archive">
<description>编译并打包一个应用程序description>
<target name="init" description="创建目录">
<mkdir dir="build/classes" />
<mkdir dir="dist" />
target>
<target name="compile" depends="init" description="编译">
<javac srcdir="src" destdir="build/classes" />
target>
<target name="archive" depends="compile" description="打包">
<war destfile="dist/antwebproject.war" basedir="build/classes" />
<jar destfile="dist/project.jar" basedir="build/classes" />
target>
<target name="clean" depends="init" description="清理">
<delete dir="build" />
<delete dir="dist" />
target>
project>
在各个地方我们都能看到目录的组织都大致按照一套标准,比如 Linux 系统中,每个目录中都存放特定的文件,有着特定的功能,组织一个良好的目录结构能够你的程序可理解性更强,比如我们文章开头介绍的 Ant 的目录结构。
下面我们来看看一个良好的目录结构的基本组成。
project_home #项目所在目录
|-src #项目源代码
| |-java #Java主程序代码
| |-test #Java测试程序代码
| |-conf
| | mainfest #manifest声明文件
|-lib #库文件目录
|-doc #项目有关说明文档,如果是开源项目,通常该目录下是相应的web content
| |-api #生成项目的Java API文档的地方
|-build #用于创建的临时目录
| |-classes #编译的结果保存在该目录
|-dist #用于发布的临时目录,通常会将build/classes下的文件打包成jar,保存在该目录下
| README #项目的说明文件
| RELEASE #项目的发布文件
| LICENSE #项目的license文件
发布项目
让我们先看看发布前必须做的一些准备工作:
1. 撰写文档。
2. 撰写和平台相关的自启动脚本 bootstrap script,批处理文件 batch file,或者程序。
3. 撰写安装脚本,使用安装工具。
4. 检查版本控制系统中和项目相关的源码,文档,以及其他资料。
5. 将版本控制系统中的源码打上标签。
6. 运行一次完整的 build。
7. 运行一次完整的测试。
8. 将软件打包成适当的形式,用于发布和安装。
对于打包这个步骤,如下图:
Data files Java source Documentation
| | |
| _____|_____ |
| | | |
| |
| | | |
|_____________| |__________________|
| |
jar |
| |
jar file |
|____________________________|
|
|
|
|
Distribution
package
将源码和数据文件打包成 jar,将文档和生成的文档保存在某个目录,然后将整体打包 zip 或者 tar,为不同的平台提供最终的版本以供下载,这就是打包的过程。
任何一个复杂的程序除了代码之外还包括了数据,例如初始化数据,配置文件,xml 文件,schema 文件,还有些国际化和本地化文本文件,最理想化的方式是将这些数据打包在 jar 文件里面,然后通过 java 的 getResource 或者 getResourceAsStream 这样的方法来访问这些数据,对于打包在 jar 文件中的文件,java 提供了目录模式 reference pattern 来访问它们,如 org/example/helloworld/helloworld.properties。
在本篇文章中我们首先介绍了 Ant 的作用和目录结构,然后介绍了 Ant 在不同操作系统的安装,接着介绍 Ant 的基本使用以及其在IDE中的集成,之后又介绍了部分 Ant 的 Tasks 和 Types,接着给出了一些构建过程,最后介绍了程序目录的组成和发布过程。
学习 Ant 不是仅仅学习如何使用 Ant 构建程序,我们从中学习到的更多的是对 Java 程序的构建方式以及构建过程的了解,我们甚至能够知道那些IDE是如何来编译运行程序的(也许它们也是靠着一个 build.xml 文件来相应我们鼠标点下的 compile 和 run 按钮的),此外因为使用 Ant 我们需要创建很多的目录来放置不同类型的文件,我们也就更能了解一个良好程序的目录组织的重要性。