[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中的集合框架定义了一套规范,用来表示和操作集合,使具体操作与实现细节解耦.集合框架都包含下列内容: 接口:这 ...
随机推荐
- lambda、map、reduce、filter函数讲解
# coding:utf-8 """ 几个特殊的函数: lambda lambda后面直接跟变量 变量后面是冒号 冒号后面是表达式,表达式计算结果就是本函数的返回值 作用 ...
- .NetCore中EFCore的使用整理(二)-关联表查询
EF常用处理关联加载的方式有3中:延迟加载(Lazy Loading).贪婪加载 (Eager Loading)以及显示加载. 一.EF Core 1.1 1.当前的版本,还不支持延迟加载(Lazy ...
- Python中Queue模块及多线程使用
Python的Queue模块提供一种适用于多线程编程的FIFO实现.它可用于在生产者(producer)和消费者(consumer)之间线程安全(thread-safe)地传递消息或其它数据,因此多个 ...
- Android典型界面设计——FragmentTabHost+Fragment实现底部tab切换
一.问题描述 在上次博文中,我们使用RadioGroup+ViewPage+Fragmen实现了顶部滑动导航(查看文章:http://www.cnblogs.com/jerehedu/p/460759 ...
- Spark2.2(三十三):Spark Streaming和Spark Structured Streaming更新broadcast总结(一)
背景: 需要在spark2.2.0更新broadcast中的内容,网上也搜索了不少文章,都在讲解spark streaming中如何更新,但没有spark structured streaming更新 ...
- 在Linux平台上搭建EasyDarwin,编译代码并简单部署
測试环境: Ubuntu gcc / g++ 从https://github.com/EasyDarwin/EasyDarwin下载代码 1.编译 第一步:进入源码文件夹下 cd ./EasyDarw ...
- Main.storyboard: WKWebView before iOS 11.0 (NSCoding support was broken in previous versions)
在工程里用 故事板写了 wkwebview 如果运行在 ios11以下 就会报这个错误,如果要支持iOS 11 以下的用户,请重写View部分,使用代码调用WKWebView,而不用使用故事版来加 ...
- C#中回滚TransactionScope的使用方法和原理
TransactionScope只要一个操作失败,它会自动回滚,Complete表示事务完成 实事上,一个错误的理解就是Complete()方法是提交事务的,这是错误的,事实上,它的作用的表示本事 ...
- java Serializable和Externalizable序列化反序列化详解(转载)
一.什么是序列化? “对象序列化”(Object Serialization)是 Java1.1就开始有的特性. 简单地说,就是可以将一个对象(标志对象的类型)及其状态转换为字节码,保存起来(可以保存 ...
- 看不见的攻击面:查看 SQLite 数据库就中招?
Navicat 客户端存在一个 XSS,在查看表字段时,没有对内容进行处理,导致一个 XSS 问题.利用这个漏洞可以读取敏感文件,比如 /Users/XXXX/.bash_history . 漏洞发现 ...