本文档提供了一个使用 Apache Ant 开始 Java 编程的逐步教程。它**不**包含关于 Java 或 Ant 的更深入的知识。本教程的目标是让你了解如何在 Ant 中执行最简单的步骤。
我们希望将源代码与生成的代码分开,因此我们的 Java 源代码文件将位于 src 文件夹中。所有生成的代码都应该位于 build 文件夹下,并根据各个步骤分成几个子文件夹:classes 用于编译后的文件,jar 用于我们自己的 JAR 文件。
我们只需要创建 src 目录。(因为我在 Windows 上工作,所以这里使用的是 Windows 语法,请将其翻译成你的 shell)
md src
以下简单的 Java 类只是将固定消息输出到 STDOUT,因此只需将此代码写入 src\oata\HelloWorld.java 中。
package oata; public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } }
现在尝试编译并运行它
md build\classes javac -sourcepath src -d build\classes src\oata\HelloWorld.java java -cp build\classes oata.HelloWorld这将导致
Hello World
创建 jar 文件并不困难。但是创建可启动的 jar 文件需要更多步骤:创建包含启动类的清单文件,创建目标目录并归档文件。
echo Main-Class: oata.HelloWorld>myManifest md build\jar jar cfm build\jar\HelloWorld.jar myManifest -C build\classes . java -jar build\jar\HelloWorld.jar
注意:在 echo Main-Class 指令中不要在 > 符号周围留空格,因为这会导致错误!
完成 Java 相关的步骤后,我们必须考虑我们的构建过程。我们必须编译我们的代码,否则我们就无法启动程序。哦,"启动",是的,我们可以为此提供一个目标。我们应该打包我们的应用程序。现在它只是一个类,但是如果你想提供下载,没有人会下载数百个文件...(想想一个复杂的 Swing GUI,所以让我们创建一个 jar 文件。一个可启动的 jar 文件会很好... 而且养成一个 "clean" 目标的习惯很好,它会删除所有生成的代码。许多错误可以通过 "clean build" 解决。
默认情况下,Ant 使用 build.xml 作为构建文件的名称,因此我们的 .\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 compile ant jar ant run
或者更简短地使用
ant compile jar run
在查看构建文件时,我们会发现 Ant 和 Java 相关的命令之间有一些类似的步骤
Java 相关 | Ant |
---|---|
md build\classes javac -sourcepath src -d build\classes src\oata\HelloWorld.java echo Main-Class: oata.HelloWorld>mf md 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"/> |
现在我们有了可用的构建文件,我们可以进行一些增强:很多时候你会引用相同的目录,主类和 jar 文件名是硬编码的,并且在调用时你必须记住构建步骤的正确顺序。
第一个和第二个问题可以通过属性解决,第三个问题可以通过一个特殊的属性解决,它是 <project>
标签的一个属性,第四个问题可以通过依赖关系解决。
<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,你将获得
Buildfile: build.xml clean: compile: [mkdir] Created dir: C:\...\build\classes [javac] Compiling 1 source file to C:\...\build\classes jar: [mkdir] Created dir: C:\...\build\jar [jar] Building jar: C:\...\build\jar\HelloWorld.jar run: [java] Hello World main: BUILD SUCCESSFUL
有人告诉我们不要使用 System
语句。对于输出,我们应该使用一个日志记录 API,它可以高度定制(包括在正常运行时(即非开发时)执行时关闭)。我们使用 Log4J,因为
我们将外部库存储在一个新的目录 lib 中。Log4J 可以从 Logging 的主页下载 [1]。创建 lib 目录并将 log4j-1.2.17.jar 解压缩到该目录中。之后,我们必须修改 Java 源代码文件以使用该库,以及修改构建文件,以便在编译和运行时可以访问该库。
Log4J 的使用方式在它的手册中有所说明。这里我们使用 简短手册 [2] 中的 MyApp 示例。首先,我们必须修改 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
}
}
大多数修改都是 "框架开销",只需要做一次。蓝色的行是我们的 "旧的 System-out" 语句。
不要尝试运行 ant,你只会得到很多编译错误。Log4J 不在类路径中,因此我们必须做一些工作。但是不要更改 CLASSPATH
环境变量!这只是针对这个项目,你可能会破坏其他环境(这是使用 Ant 时最常见的错误之一)。我们将 Log4J(或者更准确地说,是所有位于 .\lib 下的库(jar 文件))引入到我们的构建文件中
<project name="HelloWorld" basedir="." default="main">
...
<property name="lib.dir" value="lib"/>
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
</path>
...
<target name="compile">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
</target>
<target name="run" depends="jar">
<java fork="true" classname="${main-class}">
<classpath>
<path refid="classpath"/>
<path location="${jar.dir}/${ant.project.name}.jar"/>
</classpath>
</java>
</target>
...
</project>
在这个例子中,我们不是通过它的 Main-Class
清单属性启动应用程序,因为我们无法提供 jar 文件名和类路径。因此,将我们的类添加到红色行中已定义的路径中,并像往常一样启动。运行 ant 将会得到(在通常的编译步骤之后)
[java] 0 [main] INFO oata.HelloWorld - Hello World
这是什么?
[java]
此时正在运行的 Ant 任务0
抱歉,不知道,一些 Log4J 相关的东西[main]
来自我们应用程序的运行线程INFO
该语句的日志级别oata.HelloWorld
该语句的来源-
分隔符Hello World
消息对于其他布局... 请查看 Log4J 文档中关于使用其他 PatternLayouts 的内容。
为什么我们使用 Log4J?"它高度可配置"?不,所有内容都是硬编码的!但这并不是 Log4J 的错,而是我们的错。我们编码了 BasicConfigurator.configure();
,这意味着一个简单但硬编码的配置。更方便的是使用属性文件。在 Java 源代码文件中,从 main()
方法(以及相关的 import
语句)中删除 BasicConfiguration
行。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
此配置创建一个名为 stdout
的控制台输出通道("Appender"),它打印消息("%m")后跟一个换行符("%n"),与之前的 System.out.println()
一样 :-) 哦,好吧,但我们还没有完成。我们也应该提供配置文件。因此,我们修改构建文件
... <target name="compile"> <mkdir dir="${classes.dir}"/> <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/> <copy todir="${classes.dir}"> <fileset dir="${src.dir}" excludes="**/*.java"/> </copy> </target> ...
这会将所有资源(只要它们没有 .java 后缀)复制到构建目录,因此我们可以从该目录启动应用程序,这些文件将被包含到 jar 文件中。
在此步骤中,我们将介绍在 Ant 中结合使用 JUnit [3] 测试框架。由于 Ant 内置了 JUnit 4.13.1,因此你可以直接开始使用它。在 src\oata\HelloWorldTest.java 中编写一个测试类
package oata; import org.junit.Test; import static org.junit.Assert.fail; public class HelloWorldTest { @Test public void testNothing() { } @Test public void testWillAlwaysFail() { fail("An error message"); } }
由于我们没有真正的业务逻辑要测试,因此这个测试类非常小:只是展示了如何开始。有关更多信息,请参阅 JUnit 文档 [3] 和 junit 任务的手册。现在,我们将 junit
指令添加到我们的构建文件中
... <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> ...
我们通过给它一个 id 并使其全局可用,来重用我们在 "run" 目标中定义的我们自己的 jar 文件的路径。printsummary=yes
使我们能够看到比仅仅 "FAILED" 或 "PASSED" 消息更详细的信息。有多少测试失败了?一些错误?printsummary 会让我们知道。类路径被设置为找到我们的类。为了运行测试,这里使用了 batchtest
,因此你可以在将来轻松地添加更多测试类,只需将它们命名为 *Test.java
。这是一个常见的命名方案。
在执行 ant junit 后,你将得到
... junit: [junit] Running oata.HelloWorldTest [junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0,01 sec [junit] Test oata.HelloWorldTest FAILED BUILD SUCCESSFUL ...
我们还可以生成报告。一些你(以及其他人)在关闭 shell 后可以阅读的内容... 有两个步骤:1. 让 <junit>
记录信息,2. 将这些日志文件转换为可读(可浏览)的内容。
... <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>
由于我们会生成很多文件,并且这些文件默认情况下会写入当前目录,因此我们定义了一个报告目录,在运行 "junit" 之前创建它,并将日志记录重定向到该目录。日志格式是 XML,因此 "junitreport" 可以解析它。在第二个目标 "junitreport" 中,应该为报告目录中的所有生成的 XML 日志文件创建一个可浏览的 HTML 报告。现在你可以打开 ${report.dir}\index.html 并查看结果(看起来有点像 JavaDoc)。
我个人使用两个不同的目标来运行 <junit>
和 <junitreport>
。生成 HTML 报告需要一些时间,并且你不需要在测试时使用 HTML 报告,例如,如果你正在修复错误,或者集成服务器正在执行任务。