教學課程:使用 Apache Ant 進行 Hello World

本文檔提供逐步教學課程,說明如何使用 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 可以從記錄首頁 下載 [1]。建立 lib 目錄,並將 log4j-1.2.17.jar 解壓縮到該目錄中。之後,我們必須修改我們的 Java 原始檔,以便使用該程式庫和我們的建置檔,以便可以在編譯和執行期間存取此程式庫。

在 Log4J 的手冊中記載了如何使用 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

那是什麼?

對於其他配置 ... 請參閱 Log4J 的文件,了解如何使用其他 PatternLayouts。

設定檔

為什麼我們使用 Log4J?「它具有高度的可設定性」?不,一切都經過硬編碼!但那不是 Log4J 的錯,而是我們的錯。我們編寫了 BasicConfigurator.configure();,它暗示一個簡單但經過硬編碼的設定。使用屬性檔會更方便。在 Java 原始檔中,從 main() 方法中刪除 BasicConfiguration 行(以及相關的 import 語句)。然後,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 中。

測試類別

在這個步驟中,我們將介紹 JUnit [3] 測試架構與 Ant 結合使用的用法。由於 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>

    ...

我們重複使用 run 目標中定義的我們自己的 jar 檔案路徑,方法是給它一個 id 並讓它在全球可用。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
...

我們也可以產生一個報告。在關閉殼層後,您(和其他人)可以閱讀的東西 ... 有兩個步驟: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 報告來進行測試,例如,如果您正在修正錯誤或整合伺服器正在執行作業。

資源

  1. https://archive.apache.org/dist/logging/log4j/1.2.17/log4j-1.2.17.zip
  2. https://logging.apache.org/log4j/1.2/manual.html
  3. https://junit.dev.org.tw/junit4