[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中的集合框架定义了一套规范,用来表示和操作集合,使具体操作与实现细节解耦.集合框架都包含下列内容: 接口:这 ...
随机推荐
- 使用C3P0报错:java.lang.NoClassDefFoundError: com/mchange/v2/ser/Indirector
错误提示: java.lang.NoClassDefFoundError: com/mchange/v2/ser/Indirector at JDBC.ConnectionPool.testC3P0( ...
- JDBC(3)—ResultSet结果集
简介:ResultSet:结果集.封装了使用JDBC进行查询的结果.Statement只能进行更新操作,所以使用ResultSet进行查询操作. 1.调用Statement对象的executeQuer ...
- winform自动更新之AutoUpdater.NET
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zhaobw831/article/details/82226291使用AutoUpdater.NET ...
- FDMEMTABLE将修改后的数据序列为JSON
FDMEMTABLE将修改后的数据序列为JSON procedure TForm1.Button3Click(Sender: TObject); var memtable: TFDMemTable; ...
- Apache CXF JAX-WS example
1. 环境说明 jdk 1.6.0_29 apache cxf 2.7.7 2. 新建JavaProject 3. 添加jar包,将apache cxf下面lib里面的jar包都添加到项目中(可能有 ...
- android学习笔记(8)linearlayout与android:layout_weight学习
一,linearlayout 线性布局,布局文件里设置多个linearlayout来达到总体线性布局的风格. android:gravity="right"对齐方式,居右对齐,gr ...
- ESXI 迁移至KVM (V2V迁移)
1.1.1 ESXI将虚拟机导出 导出ova模板 将导出的ova模板导入到KVM环境中. 1.1.2 配置KVM环境 详情参考:http://www.cnblogs.com/clsn/p/836625 ...
- 微软BI 之SSIS 系列 - 在 SSIS 中使用 Web Service 以及 XML 解析
开篇介绍 Web Service 的用途非常广几乎无处不在,像各大门户网站上的天气预报使用到的第三方 Web Service API,像手机客户端和服务器端的交互等都可以通过事先设计好的 Web Se ...
- (98)Address already in use: make_sock: could not bind to address 80 [resolved] (2012-10-11 09:04)
以前遇到一个问题: sudo /etc/init.d/apache2 start * Starting web server apache2 apache2: Could not reliably d ...
- Spark机器学习(4):朴素贝叶斯算法
1. 贝叶斯定理 条件概率公式: 这个公式非常简单,就是计算在B发生的情况下,A发生的概率.但是很多时候,我们很容易知道P(A|B),需要计算的是P(B|A),这时就要用到贝叶斯定理: 2. 朴素贝叶 ...