环境:Ubuntu 12.04, java 1.7.0, ant 1.8.2。

前言

Apache Ant 是一个软件自动化构建工具,构建过程包括编译、测试和部署等。它和 Make 工具相似,但由 Java 实现,所以要求 Java 运行环境,非常适合构建 Java 程序。

 
    Ant 和 Make 明显不同之处在于 Ant 使用 XML 来表述构建过程与依赖关系,而 Make 使用 Makefile 格式文件。Ant 默认的构建文件名为 build.xml。每一个 build.xml 文件包含一个 <project> 和至少一个默认的 <target>,<target> 中包含许多 task elements。每个 task element 有一个作为引用的唯一 ID。
 
    Ant 是 Apache 开源项目,以 Apache License 发行。
 

历史

    Ant(意为 Another Neat Tool,另一个好用的工具。)最初的构想来自于 James Duncan Davidson,当时他正在开发 Sun 公司的 JSP/Servlet 引擎,也就是后来的 Apache Tomcat。当时 Solaris 平台有一个专属版本的 make 可以用来构建 JSP/Servlet,但是在开源世界不能对使用那个平台来构建 Tomcat 做出控制,因此 Ant 被开发出来,作为一个与平台无关的工具来构建 Tomcat,并使用 XML 文件作为其构建文件。Ant 1.1 在 2000 年 7 月 19 日作为独立产品公开发行。
 
    有一些提案被用于 Ant 2,例如 James Duncan Davidson 的 AntEater, Peter Donald 的 Myrmidon,Conor MacNeill 的 Mutant。但它们都没有被开发者社区广泛接受。
 
    在 2002 年的时候,Ant 是大部分 Java 项目的构建工具。大多数开源项目开发者都将 build.xml 文件包含在他们的发行版本中。
 
    因为 Ant 可以轻松将 JUnit 集成到构建过程,这给测试驱动开发甚者极限编程的开发者带来极大意愿来使用它。
 

搭建环境

  1. 安装 JDK。

  我们将安装 Oracle JDK 而不是 Open JDK(也可以用安装 Open JDK),因此首先添加第三方库:

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update

  

  然后安装 oracle-java7-set-default:

$ sudo apt-get install oracle-java7-set-default

  2. 安装 ant(Another Neat Tool):

$ sudo apt-get install ant-optional

准备项目

  我们将源代码文件和生成的文件分开保管,我们将源代码文件保存在 src 目录,所有生成的文件保存在 build 目录,其子目录 classes 用以保存编译后的 Java 类文件,子目录 jar 用以保存 JAR 文件。

  首先创建 src 目录:

$ mkdir src

  接下来让我们创建一个 Java 类,该类会使用标准输出打印一句话 Hello World。让我们创建保存该类的源代码文件 src/oata/HelloWorld.java:

package oata;

public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

  编译 HelloWorld.java 源代码并运行:

$ mkdir build/classes
$ javac -sourcepath src -d build/classes src/oata/HelloWorld.java
$ java -cp build/classes oata.HelloWorld
Hello World

  接着我们创建一个 jar 文件,要创建一个 jar 文件并不难。但要创建一个可启动的 jar 文件则需要有以下几步:创建一个 manifest 文件,其中包括启动类,创建目标目录并将文件归档。

$ echo Main-Class: oata.HelloWorld>myManifest
$ mkdir build/jar
$ jar cfm build/jar/HelloWorld.jar myManifest -C build/classes .
$ java -jar build/jar/HelloWorld.jar
Hello World

  注意:在 echo Main-Class 语句中 > 字符的两边不要有空格。

运行 Java 程序的四个步骤

  我们现在来考虑一下我们的构建过程。

    1. 编译 - 编译源代码,否则无法启动程序。

    2. 执行 - 运行程序或者编译命令,下文 build.xml 中每个 target 对应着一个执行。

    3. 打包 - 虽然目前我们的程序只有一个类,但如果我们要对外发布我们的程序,没人愿意下载几百个文件。因此我们要创建 jar 文件,最好是可执行 jar 文件。

    4. 清理 - 清理自动生成的东西。事实证明许多错误都是没有做好清理工作导致。

  

  Ant 默认的构建文件是 build.xml,构建过程中每一个步骤就是一个 target。现在为我们的项目新建一个构建文件 ./build.xml:

<project>

    <target name="clean">
<delete dir="build"/>
</target> <target name="compile">
<mkdir dir="build/classes"/>
<javac srcdir="src" destdir="build/classes"/>
</target> <target name="jar">
<mkdir dir="build/jar"/>
<jar destfile="build/jar/HelloWorld.jar" basedir="build/classes">
<manifest>
<attribute name="Main-Class" value="oata.HelloWorld"/>
</manifest>
</jar>
</target> <target name="run">
<java jar="build/jar/HelloWorld.jar" fork="true"/>
</target> </project>

  现在可以使用 Ant 进行编译、打包和运行程序:

# 编译
$ ant compile # 打包
$ ant jar # 运行
$ ant run

  也可以简化为:

$ ant compile jar run

  对比一下使用 JDK 自身工具和使用 Ant 的构建过程:

java-only Ant
$ mkdir build/classes
$ javac
-sourcepath src
-d build/classes
src/oata/HelloWorld.java
$ echo Main-Class: oata.HelloWorld>myManifest
$ mkdir build/jar
$ jar cfm
build/jar/HelloWorld.jar
mf
-C build/classes
. $ java -jar build/jar/HelloWorld.jar
<mkdir dir="build/classes"/>
<javac
srcdir="src"
destdir="build/classes"/>
<!-- automatically detected -->
<!-- obsolete; done via manifest tag -->
<mkdir dir="build/jar"/>
<jar
destfile="build/jar/HelloWorld.jar" basedir="build/classes">
<manifest>
<attribute name="Main-Class" value="oata.HelloWorld"/>
</manifest>
</jar>
<java jar="build/jar/HelloWorld.jar" fork="true"/>

增强版构建文件

  许多时候,我们在构建过程中会反复引用相同的目录,main-class 和 jar 文件,这些目前都是硬编码在构建文件中,另外我们还得记住构建步骤不能搞错。

  为解决反复引用相同的东西和避免硬编码,我们可以使用 properties,而主类我们可以使用 <project> 标签的属性来指定,另外使用依赖包来保持构建过程稳步有序。让我们重新编辑我们的 build.xml:

<project name="HelloWorld" basedir="." default="main">

    <property name="src.dir"     value="src"/>

    <property name="build.dir"   value="build"/>
<property name="classes.dir" value="${build.dir}/classes"/>
<property name="jar.dir" value="${build.dir}/jar"/> <property name="main-class" value="oata.HelloWorld"/> <target name="clean">
<delete dir="${build.dir}"/>
</target> <target name="compile">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}"/>
</target> <target name="jar" depends="compile">
<mkdir dir="${jar.dir}"/>
<jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
<manifest>
<attribute name="Main-Class" value="${main-class}"/>
</manifest>
</jar>
</target> <target name="run" depends="jar">
<java jar="${jar.dir}/${ant.project.name}.jar" fork="true"/>
</target> <target name="clean-build" depends="clean,jar"/> <target name="main" depends="clean,run"/> </project>

  现在只需要执行 ant:

$ ant
Buildfile: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml clean:
[delete] Deleting directory /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build compile:
[mkdir] Created dir: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes
[javac] /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml:: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[javac] Compiling source file to /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes jar:
[mkdir] Created dir: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar
[jar] Building jar: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar/HelloWorld.jar run:
[java] Hello World main: BUILD SUCCESSFUL
Total time: seconds

使用第三方库

  总是有人会告诉你不要使用 syso-statements,即不要使用 System.out.println() 来记录日志,而应该使用日志 API。下面我们就在我们的项目引入一个第三方库记录日志 Log4J。

  我们将第三方库文件放在 lib 目录下。你可以点击这里下载 Log4J 库。创建 lib 目录,并将 log4j-1.2.13.jar 放到 lib 目录下。

$ wget https://archive.apache.org/dist/logging/log4j/1.2.13/logging-log4j-1.2.13.tar.gz

$ cd lib
$ tar zvxf logging-log4j-1.2..tar.gz $ mv logging-log4j-1.2./dist/lib/log4j-1.2..jar .

  接下来要修改我们的源代码和构建文件,使得在编译和运行我们的程序时可以访问到这个第三方库。

  $ vi src/oata/HelloWorld.java

package oata;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
public class HelloWorld {
static Logger logger = Logger.getLogger(HelloWorld.class); public static void main(String[] args) {
BasicConfigurator.configure();
logger.info(
"Hello World"); // the old SysO-statement
}
}

  现在还不能执行 ant,因为 Log4J 还不在我们的类搜索路径中。我们要做的不是修改 CLASSPATH 环境变量,因为这样做可能会影响到其他项目,我们只是在这个项目中引入 Log4J,我们要告诉 ant 所有第三方库(jar 文件)都放在 ./lib 目录下:

  $ vi build.xml

<project name="HelloWorld" basedir="." default="main">

    <property name="src.dir" value="src"></property>
<property name="build.dir" value="build"></property>
<property name="classes.dir" value="${build.dir}/classes"></property>
<property name="jar.dir" value="${build.dir}/jar"></property>
<property name="main-class" value="oata.HelloWorld"></property>

  <!-- 新增 -->
<property name="lib.dir" value="lib"></property>
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"></fileset>
</path>
<target name="clean">
<delete dir="${build.dir}"></delete>
</target>

  <!-- 修改后 -->
<target name="compile">
<mkdir dir="${classes.dir}"></mkdir>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"></javac>
</target> <target name="jar" depends="compile">
<mkdir dir="${jar.dir}"></mkdir>
<jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
<manifest>
<attribute name="Main-Class" value="${main-class}"></attribute>
</manifest>
</jar>
</target>   <!-- 修改后 -->
<target name="run" depends="jar">
<java fork="true" classname="${main-class}">
<classpath>
<path refid="classpath"></path>
<path location="${jar.dir}/${ant.project.name}.jar"></path>
</classpath>

</java>
</target> <target name="clean-build" depends="clean,jar"></target>
<target name="main" depends="clean,run"></target> </project>

  运行 ant:

$ ant run

...

run:
[java] [main] INFO oata.HelloWorld - Hello World

  以上内容表示名为 run 的任务日志为:[java] 1 [main] INFO oata.HelloWorld  - Hello World

  1. [java]:表示 ant 任务正在运行 java 命令。

  2. 1:Log4J 库定义的字段,详情请查阅 Apache Log4J

  3. [main]:表示当前线程为主线程。

  4. INFO:表示日志级别。

  5. oata.HelloWorld:日志消息来自的类名。

  6. -:分隔符。

  7. Hello World:日志消息。

配置文件

  虽然我们使用了 Log4J,但目前为止我们仍然是硬编码,因为我们只是简单调用了 BasicConfigurator.configure(),如果我们想输出不同格式的日志消息,那我们就应该使用一个 property 文件。

  我们在源代码删除 BasicConfigurator.configure() 所在行,以及相关的 import 语句。此时运行 ant 会提示:

...
[java] log4j:WARN No appenders could be found for logger (oata.HelloWorld).
[java] log4j:WARN Please initialize the log4j system properly.

  现在让我们为 Log4J 创建一个配置文件 src/log4j.properties,这是 Log4J 默认的配置文件名,它会自动搜索该配置文件。

log4j.rootLogger=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%m%n

  以上配置文件表示创建一个输出通道(Appender)到控制台的标准输出 stdout,标准输出流将打印出日志消息(%m),消息末尾添加一个换行符(%n)。这个和之前的 System.out.println() 效果相同。

  创建了配置文件,我们还要激活该配置文件,编辑构建文件 build.xml,修改 <target name="compile">:

    <target name="compile">
<mkdir dir="${classes.dir}"></mkdir>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"></javac>
<copy todir="${classes.dir}">
<fileset dir="${src.dir}" excludes="**/*.java"></fileset>
</copy>

</target>

  <copy> 节点表示复制所有的非 .java 后缀的资源文件到 build 目录,这样我们就可以启动 build 目录下的程序并且将这些资源文件包含到 jar 文件中。运行 ant:

$ ant
Buildfile: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml clean:
[delete] Deleting directory /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build compile:
[mkdir] Created dir: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes
[javac] /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml:20: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[javac] Compiling 1 source file to /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes
[copy] Copying 2 files to /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes jar:
[mkdir] Created dir: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar
[jar] Building jar: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar/HelloWorld.jar run:
[java] Hello World

  log4j.properties 被复制到了 build/classes 目录,run 的输出日志为 [java] Hello World。

测试 Java 类

  Ant 内置了 JUnit,你可以直接使用 JUnit 测试框架来测试你的代码。新建一个测试类 src/HelloWorldTest.java:

public class HelloWorldTest extends junit.framework.TestCase {

    public void testNothing() {
} public void testWillAlwaysFail() {
fail("An error message");
} }

  注:本文直接使用 ant 内置 JUnit 导致了 package junit.framework does not exist 的报错。尝试将 /usr/share/java/ant-junit4-1.8.2.jar 复制到 ./lib 目录依然无法解决报错。最后只能下载一个 junit.jar 到 ./lib 目录中才解决该问题。

$ wget http://search.maven.org/remotecontent?filepath=junit/junit/4.11/junit-4.11.jar -O ./lib

  因为我们的项目还没有真正的业务逻辑,所以这个测试类非常简单,只是展示如何使用它而已。要了解更多关于 JUnit 测试框架,请查阅 junit 手册。

  让我们把 juni 指令添加到我们的构建文件 build.xml 中:

    ...

    <path id="application" location="${jar.dir}/${ant.project.name}.jar"/>

    <target name="run" depends="jar">
<java fork="true" classname="${main-class}">
<classpath>
<path refid="classpath"/>
<path refid="application"/>
</classpath>
</java>
</target> <target name="junit" depends="jar">
<junit printsummary="yes">
<classpath>
<path refid="classpath"/>
<path refid="application"/>
</classpath> <batchtest fork="yes">
<fileset dir="${src.dir}" includes="*Test.java"/>
</batchtest>
</junit>
</target>
...

  我们给我们这项目生成的 jar 文件路径一个 ID,并让它成为一个全局变量,这样我们在 target run 中就可以使用它的 ID 来引用它。printsummary=yes 可以输出更多的信息给我们,而不是简单的 FAILED 或 PASSED,例如失败了多少项,什么失败了,printsummary 都可以提供。classpath 是用来寻找我们的类。batchtest 是为了更方便测试在将来你添加了新的测试用例,约定俗成的测试类命名为 *Test.java。

$ ant junit
Buildfile: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml compile:
[javac] /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml:: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[copy] Copying file to /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes jar:
[jar] Building jar: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar/HelloWorld.jar junit:
[junit] Running HelloWorldTest
[junit] Tests run: , Failures: , Errors: , Time elapsed: 0.02 sec
[junit] Test HelloWorldTest FAILED BUILD SUCCESSFUL
Total time: seconds

  为方便阅读测试结果,我们可以生成一个测试报告。首先,让 <junit> 负责记录测试数据;其次,将数据转换为可阅读文本。编辑构建文件 build.xml:

    ...
<property name="report.dir" value="${build.dir}/junitreport"/>
...
<target name="junit" depends="jar">
<mkdir dir="${report.dir}"/>
<junit printsummary="yes">
<classpath>
<path refid="classpath"/>
<path refid="application"/>
</classpath> <formatter type="xml"/> <batchtest fork="yes" todir="${report.dir}">
<fileset dir="${src.dir}" includes="*Test.java"/>
</batchtest>
</junit>
</target> <target name="junitreport">
<junitreport todir="${report.dir}">
<fileset dir="${report.dir}" includes="TEST-*.xml"/>
<report todir="${report.dir}"/>
</junitreport>
</target>

  因为我们可能会产生大量的文件,而这些文件默认都保存在当前目录下,因此我们定义了一个 report 目录,ant 会在运行 junit 之前创建该目录并将日志输出到该目录。因为日志格式为 XML,这可以让 junitreport 对其进行解析。另外一个 target junitreport 会将 report 目录下所有 XML 文件创建相应的可阅读的 HTML 格式文档。你可以打开 ${report.dir}/index.html 文件查看所有测试结果(看起来很像 JavaDoc)。

  你可以将测试与制作测试报告分为两个任务,因为生成 HTML 报告需要一定的时间,而你完全没有必要在测试的时候停下来等待报告生成。

  

附:

1. Tutorial Hello World with Ant

2. Ant 入门教程

[Java] Apache Ant 构建基础教程的更多相关文章

  1. java.util.logging.Logger基础教程

    从JDK1.4开始即引入与日志相关的类java.util.logging.Logger,但由于Log4J的存在,一直未能广泛使用.综合网上各类说法,大致认为: (1)Logger:适用于小型系统,当日 ...

  2. [Android] 基于 Linux 命令行构建 Android 应用(五):Ant 构建命令

    Android SDK 提供的 android 工具可以在项目根目录自动生成 Ant 构建文件 build.xml[1].进入项目根目录后,你可以使用以下 Ant 命令[2]. ant clean 清 ...

  3. Ant构建与部署Java项目---入门

    原文地址:http://tech.it168.com/j/2007-11-09/200711091344781.shtml Ant是一个Apache基金会下的跨平台的构件工具,它可以实现项目的自动构建 ...

  4. ant 安装及基础教程 !

    这篇文章主要介绍了ant使用指南详细入门教程,本文详细的讲解了安装.验证安装.使用方法.使用实例.ant命令等内容,需要的朋友可以参考下   一.概述 ant 是一个将软件编译.测试.部署等步骤联系在 ...

  5. Apache Ant 项目构建

    项目构建:通过构建工具对多个项目进行统一批量的编译和运行,比如,对多个Jmeter脚本批量运行 1.Ant是什么? Ant是 构建工具,Apache Ant是一个将软件编译.测试.部署等步骤联系在一起 ...

  6. Java 中三大构建工具:Ant、Maven和Gradle

    Java世界中主要有三大构建工具:Ant.Maven和Gradle 目前:Ant已经销声匿迹.Maven也没落了,而Gradle的发展则如日中天. Maven的主要功能主要分为5点,分别是依赖管理系统 ...

  7. Java基础教程:反射基础

    Java基础教程:反射基础 引入反射 反射是什么 能够动态分析类能力的程序称为反射. 反射是一种很强大且复杂的机制. Class类 在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时 ...

  8. Java基础教程(25)--I/O

    一.I/O流   I/O流表示输入源或输出目标.流可以表示许多不同类型的源和目标,例如磁盘文件.设备.其他程序等.   流支持许多不同类型的数据,包括字节.原始数据类型.字符和对象等.有些流只传递数据 ...

  9. Java基础教程(24)--集合

    一.Java集合框架   集合,有时也称为容器,是一个用来存储和管理多个元素的对象.Java中的集合框架定义了一套规范,用来表示和操作集合,使具体操作与实现细节解耦.集合框架都包含下列内容: 接口:这 ...

随机推荐

  1. lambda、map、reduce、filter函数讲解

    # coding:utf-8 """ 几个特殊的函数: lambda lambda后面直接跟变量 变量后面是冒号 冒号后面是表达式,表达式计算结果就是本函数的返回值 作用 ...

  2. .NetCore中EFCore的使用整理(二)-关联表查询

    EF常用处理关联加载的方式有3中:延迟加载(Lazy Loading).贪婪加载 (Eager Loading)以及显示加载. 一.EF Core  1.1 1.当前的版本,还不支持延迟加载(Lazy ...

  3. Python中Queue模块及多线程使用

    Python的Queue模块提供一种适用于多线程编程的FIFO实现.它可用于在生产者(producer)和消费者(consumer)之间线程安全(thread-safe)地传递消息或其它数据,因此多个 ...

  4. Android典型界面设计——FragmentTabHost+Fragment实现底部tab切换

    一.问题描述 在上次博文中,我们使用RadioGroup+ViewPage+Fragmen实现了顶部滑动导航(查看文章:http://www.cnblogs.com/jerehedu/p/460759 ...

  5. Spark2.2(三十三):Spark Streaming和Spark Structured Streaming更新broadcast总结(一)

    背景: 需要在spark2.2.0更新broadcast中的内容,网上也搜索了不少文章,都在讲解spark streaming中如何更新,但没有spark structured streaming更新 ...

  6. 在Linux平台上搭建EasyDarwin,编译代码并简单部署

    測试环境: Ubuntu gcc / g++ 从https://github.com/EasyDarwin/EasyDarwin下载代码 1.编译 第一步:进入源码文件夹下 cd ./EasyDarw ...

  7. Main.storyboard: WKWebView before iOS 11.0 (NSCoding support was broken in previous versions)

    在工程里用  故事板写了 wkwebview  如果运行在 ios11以下 就会报这个错误,如果要支持iOS 11 以下的用户,请重写View部分,使用代码调用WKWebView,而不用使用故事版来加 ...

  8. C#中回滚TransactionScope的使用方法和原理

    TransactionScope只要一个操作失败,它会自动回滚,Complete表示事务完成   实事上,一个错误的理解就是Complete()方法是提交事务的,这是错误的,事实上,它的作用的表示本事 ...

  9. java Serializable和Externalizable序列化反序列化详解(转载)

    一.什么是序列化? “对象序列化”(Object Serialization)是 Java1.1就开始有的特性. 简单地说,就是可以将一个对象(标志对象的类型)及其状态转换为字节码,保存起来(可以保存 ...

  10. 看不见的攻击面:查看 SQLite 数据库就中招?

    Navicat 客户端存在一个 XSS,在查看表字段时,没有对内容进行处理,导致一个 XSS 问题.利用这个漏洞可以读取敏感文件,比如 /Users/XXXX/.bash_history . 漏洞发现 ...