[Java] Apache Ant 构建基础教程
环境:Ubuntu 12.04, java 1.7.0, ant 1.8.2。
前言
Apache Ant 是一个软件自动化构建工具,构建过程包括编译、测试和部署等。它和 Make 工具相似,但由 Java 实现,所以要求 Java 运行环境,非常适合构建 Java 程序。
历史
搭建环境
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 |
<mkdir dir="build/classes"/> |
增强版构建文件
许多时候,我们在构建过程中会反复引用相同的目录,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 构建基础教程的更多相关文章
- java.util.logging.Logger基础教程
从JDK1.4开始即引入与日志相关的类java.util.logging.Logger,但由于Log4J的存在,一直未能广泛使用.综合网上各类说法,大致认为: (1)Logger:适用于小型系统,当日 ...
- [Android] 基于 Linux 命令行构建 Android 应用(五):Ant 构建命令
Android SDK 提供的 android 工具可以在项目根目录自动生成 Ant 构建文件 build.xml[1].进入项目根目录后,你可以使用以下 Ant 命令[2]. ant clean 清 ...
- Ant构建与部署Java项目---入门
原文地址:http://tech.it168.com/j/2007-11-09/200711091344781.shtml Ant是一个Apache基金会下的跨平台的构件工具,它可以实现项目的自动构建 ...
- ant 安装及基础教程 !
这篇文章主要介绍了ant使用指南详细入门教程,本文详细的讲解了安装.验证安装.使用方法.使用实例.ant命令等内容,需要的朋友可以参考下 一.概述 ant 是一个将软件编译.测试.部署等步骤联系在 ...
- Apache Ant 项目构建
项目构建:通过构建工具对多个项目进行统一批量的编译和运行,比如,对多个Jmeter脚本批量运行 1.Ant是什么? Ant是 构建工具,Apache Ant是一个将软件编译.测试.部署等步骤联系在一起 ...
- Java 中三大构建工具:Ant、Maven和Gradle
Java世界中主要有三大构建工具:Ant.Maven和Gradle 目前:Ant已经销声匿迹.Maven也没落了,而Gradle的发展则如日中天. Maven的主要功能主要分为5点,分别是依赖管理系统 ...
- Java基础教程:反射基础
Java基础教程:反射基础 引入反射 反射是什么 能够动态分析类能力的程序称为反射. 反射是一种很强大且复杂的机制. Class类 在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时 ...
- Java基础教程(25)--I/O
一.I/O流 I/O流表示输入源或输出目标.流可以表示许多不同类型的源和目标,例如磁盘文件.设备.其他程序等. 流支持许多不同类型的数据,包括字节.原始数据类型.字符和对象等.有些流只传递数据 ...
- Java基础教程(24)--集合
一.Java集合框架 集合,有时也称为容器,是一个用来存储和管理多个元素的对象.Java中的集合框架定义了一套规范,用来表示和操作集合,使具体操作与实现细节解耦.集合框架都包含下列内容: 接口:这 ...
随机推荐
- c++文件打包工具实现
没事做就来写一个打包的工具吧.很多是网络上面找的,只是我把他修改一下合并在一起. // PacketFile.cpp : 定义控制台应用程序的入口点. #include "stdafx.h& ...
- 手把手教你 Mockito 的使用
什么是 Mockito Mockito 是一个强大的用于 Java 开发的模拟测试框架, 通过 Mockito 我们可以创建和配置 Mock 对象, 进而简化有外部依赖的类的测试.使用 Mockito ...
- 职场之KPI
当一个公司开始执行KPI考核的时候,任何人的工作性质就发生了变化,而处于底层的员工就惨了,因为一个公司的资源是有限的,一个人的精力也是有限的,当你和你上司负责不同的项目时,而当你的所谓产品经理负责两个 ...
- puppet应用案例
- 解决Nginx的13: Permission denied) while connecting to upstream
一.问题 做Nginx负载的时候,经常遇到这样的情况: // :: [crit] #: * connect() to failed (: Permission denied) while connec ...
- 动态库DLL中类的使用
一.DLL中类的导出 在类名称前添加 _declspec(dllexport)定义,比如: class _declspec(dllexport) CMath{ .... }; 通常使用预编译开关切换类 ...
- SpringBoot集成redis的LBS功能
下面的代码实现了添加经纬度数据 和 搜索经纬度数据的功能: import java.util.List; import com.longge.goods.dto.BuildingDto; import ...
- .NET 同步与异步之锁(ReaderWriterLockSlim)(八)
本随笔续接:.NET 同步与异步之锁(Lock.Monitor)(七) 由于锁 ( lock 和 Monitor ) 是线程独占式访问的,所以其对性能的影响还是蛮大的,那有没有一种方式可是实现:允许多 ...
- Linux 系统实时监控的瑞士军刀 —— Glances
Linux 系统实时监控的瑞士军刀 —— Glances 对于 RHEL/CentOS/Fedora 发行版 ## RHEL/CentOS 7 64-Bit ## # wget http://dl.f ...
- css组合选择器
组合选择器:1,后代选择器 .main h2 {...}, 使用空格表示 IE6+2,子选择器 .main>h2 {...}, 使用 > 表示 IE7+3,兄弟选择器 h2+p {...} ...