Apache Ant 是基於 Java 的構建工具,以 XML 配置構建腳本,跨平台、可擴展。雖然 Maven/Gradle 成為主流,但 Ant 在舊專案維護、特定構建邏輯、嵌入式構建中仍有廣泛應用。本文系統講解 Ant 核心概念、構建文件結構、屬性系統、依賴管理與實戰技巧。
# 解壓至任意目錄 tar -xzf apache-ant-1.10.14-bin.tar.gz # 或 Windows 解壓至 D:\apache-ant-1.10.14
| 系統 | 變量 | 值 |
|---|---|---|
| Windows | ANT_HOME | D:\apache-ant-1.10.14 |
| Windows | PATH | %PATH%;%ANT_HOME%\bin |
| Linux/Mac | ANT_HOME | /opt/apache-ant-1.10.14 |
| Linux/Mac | PATH | $PATH:$ANT_HOME/bin |
ant -version # Apache Ant(TM) version 1.10.14 compiled on ... # Buildfile: build.xml does not exist! (正常,需在項目目錄執行)
<?xml version="1.0" encoding="UTF-8"?>
<project name="MyProject" default="init" basedir=".>
<target name="init">
<echo message="Hello Ant!"/>
</target>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project name="AntDemo" default="dist" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant">
<!-- 1. 全局屬性定義 -->
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="dist.dir" value="dist"/>
<property name="lib.dir" value="lib"/>
<property file="build.properties"/> <!-- 外部屬性文件 -->
<!-- 2. 路徑結構定義 (用於編譯類路徑) -->
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
</path>
<!-- 3. 初始化:建立目錄 -->
<target name="init">
<mkdir dir="${build.dir}/classes"/>
<mkdir dir="${dist.dir}"/>
</target>
<!-- 4. 編譯:依賴 init -->
<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${build.dir}/classes"
includeantruntime="false" encoding="UTF-8">
<classpath refid="classpath"/>
</javac>
</target>
<!-- 5. 打包 JAR:依賴 compile -->
<target name="jar" depends="compile">
<jar destfile="${dist.dir}/${ant.project.name}.jar"
basedir="${build.dir}/classes">
<manifest>
<attribute name="Main-Class" value="com.example.Main"/>
<attribute name="Class-Path" value="."/>
</manifest>
</jar>
</target>
<!-- 6. 執行:依賴 jar -->
<target name="run" depends="jar">
<java classname="com.example.Main" fork="true">
<classpath>
<path refid="classpath"/>
<pathelement location="${dist.dir}/${ant.project.name}.jar"/>
</classpath>
</java>
</target>
<!-- 7. 清理 -->
<target name="clean">
<delete dir="${build.dir}"/>
<delete dir="${dist.dir}"/>
</target>
<!-- 8. 完整構建:清理 -> 打包 -->
<target name="dist" depends="clean, jar" description="完整構建發布包"/>
</project>
| 任務 | 用途 | 關鍵屬性 |
|---|---|---|
<mkdir> | 建立目錄 | dir |
<delete> | 刪除文件/目錄 | dir, file, includeEmptyDirs |
<copy> | 複製文件/目錄 | todir, tofile, overwrite, filtering |
<move> | 移動文件/目錄 | todir, tofile |
<unzip/zip> | 解壓/壓縮 | src, dest |
<!-- Java 編譯 -->
<javac srcdir="${src.dir}" destdir="${build.dir}/classes"
source="1.8" target="1.8" encoding="UTF-8"
includeantruntime="false" debug="on" deprecation="on">
<classpath refid="classpath"/>
<compilerarg line="-Xlint:unchecked"/>
</javac>
<!-- JAR 打包 -->
<jar destfile="${dist.dir}/app.jar" basedir="${build.dir}/classes">
<manifest>
<attribute name="Main-Class" value="com.example.Main"/>
<attribute name="Built-By" value="${user.name}"/>
<attribute name="Build-Date" value="${TODAY}"/>
</manifest>
<!-- 排除測試類 -->
<exclude name="**/*Test.class"/>
</jar>
<!-- WAR 打包 (Web 專案) -->
<war destfile="${dist.dir}/app.war" webxml="web/WEB-INF/web.xml">
<fileset dir="web"/>
<lib dir="${build.dir}/lib"/>
<classes dir="${build.dir}/classes"/>
</war>
<target name="test" depends="compile">
<junit printsummary="yes" haltonfailure="false">
<classpath>
<path refid="classpath"/>
<pathelement location="${build.dir}/classes"/>
<pathelement location="${build.dir}/test-classes"/>
</classpath>
<formatter type="xml"/>
<batchtest fork="yes" todir="${build.dir}/test-reports">
<fileset dir="${src.dir}/test" includes="**/*Test.java"/>
</batchtest>
</junit>
<!-- 生成 HTML 報告 -->
<junitreport todir="${build.dir}/test-reports">
<fileset dir="${build.dir}/test-reports" includes="TEST-*.xml"/>
<report format="frames" todir="${build.dir}/test-reports/html"/>
</junitreport>
</target>
<!-- 引入 Ivy 命名空間 -->
<project xmlns:ivy="antlib:org.apache.ivy.ant" ...>
<target name="resolve">
<ivy:retrieve pattern="${lib.dir}/[artifact]-[revision].[ext]"/>
</target>
<!-- ivy.xml 依賴聲明 -->
<ivy-module version="2.0">
<info organisation="com.example" module="AntDemo"/>
<dependencies>
<dependency org="org.springframework" name="spring-core" rev="5.3.21"/>
<dependency org="mysql" name="mysql-connector-java" rev="8.0.33"/>
</dependencies>
</ivy-module>
<!-- 1. 內聯定義 -->
<property name="version" value="1.0.0"/>
<!-- 2. 外部文件載入 -->
<property file="build.properties"/>
<property file="${user.home}/.ant-global.properties"/>
<!-- 3. 環境變量前綴載入 -->
<property environment="env"/> <!-- 訪問: ${env.JAVA_HOME} -->
<!-- 4. 命令行傳遞 (最高優先級) -->
# ant -Dversion=2.0.0 dist
核心原則:Ant 屬性一旦設置,**不可修改**。後續設置同名屬性會被忽略。
<property name="x" value="1"/>
<property name="x" value="2"/> <!-- 無效,x 仍為 1 -->
<echo message="${x}"/> <!-- 輸出 1 -->
優先級順序 (高到低):命令行 -D > 內聯定義 > 外部文件 > 環境變量
<property name="build.dir" location="build"/> <!-- 自動轉換為當前平台絕對路徑:Windows 用 \,Unix 用 / -->
<target name="A"/> <target name="B" depends="A"/> <-- B 依賴 A --> <target name="C" depends="A,B"/> <-- C 依賴 A 和 B --> <target name="D" depends="C"/> <-- D 依賴 C --> <!-- 執行順序:A → B → C → D -->
<property name="skipTests" value="false"/>
<target name="test" depends="compile" unless="skipTests">
<!-- skipTests 為 false 時執行 -->
</target>
<target name="deploy" if="deploy.enabled">
<!-- deploy.enabled 為 true 時執行 -->
</target>
<!-- 父構建文件 -->
<target name="compile">...</target>
<!-- 子構建文件 import 父文件 -->
<import file="../parent-build.xml"/>
<!-- 重寫 compile 目標 -->
<target name="compile" depends="parent.compile">
<!-- 執行父目標後,再執行額外邏輯 -->
</target>
project/ ├── build.xml # 主構建文件 ├── build.properties # 全局屬性 ├── ant-lib/ │ ├── compile.xml # 編譯邏輯 │ ├── package.xml # 打包邏輯 │ ├── test.xml # 測試邏輯 │ └── deploy.xml # 部署邏輯 └── src/
<import file="ant-lib/compile.xml"/> <import file="ant-lib/package.xml"/> <import file="ant-lib/test.xml"/>
<macrodef name="create-jar">
<attribute name="jar.name"/>
<attribute name="main.class"/>
<sequential>
<jar destfile="${dist.dir}/@{jar.name}.jar" basedir="${build.dir}/classes">
<manifest>
<attribute name="Main-Class" value="@{main.class}"/>
</manifest>
</jar>
</sequential>
</macrodef>
<!-- 調用 -->
<create-jar jar.name="module-a" main.class="com.example.ModuleA"/>
| 問題 | 原因 | 解決 |
|---|---|---|
| 屬性不生效 | 優先級衝突、拼寫錯誤 | 用 ant -debug 查看屬性載入順序 |
| 路徑分隔符錯誤 | 硬編碼 / 或 \ | 使用 location 屬性或 ${file.separator} |
| 類路徑找不到 | classpath 引用錯誤 | 檢查 <path id="..."> 定義與 refid 引用 |
| 中文亂碼 | 編碼不一致 | 統一 UTF-8:<property name="file.encoding" value="UTF-8"/> |
Ant 以 XML 聲明式構建、不可變屬性系統、靈活的 Target 依賴圖為核心特點。雖非主流選擇,但其簡單直接的任務模型、無侵入式依賴管理 (Ivy)、強大的文件系統操作,使其在特定場景 (舊專案、嵌入式構建、跨平台腳本) 仍具備不可替代的價值。掌握 Ant 有助於理解構建工具演進史,並能勝任遺留系統維護工作。