撰寫自己的工作非常容易
org.apache.tools.ant.Task
或 另一個類別,其設計用於延伸。public void
方法,只有一個引數。方法名稱必須以 set
開頭,接著是屬性名稱,名稱的第一個字元為大寫,其餘為小寫*。也就是說,若要支援一個名為 file 的屬性,您會建立一個 setFile
方法。根據引數的類型,Ant 會為您執行一些轉換,請參閱 下方。parallel
),您的類別必須實作介面 org.apache.tools.ant.TaskContainer
。如果您這樣做,您的工作無法支援任何其他巢狀元素。請參閱 下方。public void addText(String)
方法。請注意,Ant 不會擴充傳遞給工作的文字上的屬性。public
方法,不帶引數,並傳回一個 Object
類型。建立方法的名稱必須以 create
開頭,接著是元素名稱。新增(或 addConfigured)方法必須是一個 public void
方法,只有一個引數,為 Object
類型,且具有無引數建構函式。新增(addConfigured)方法的名稱必須以 add
(addConfigured
)開頭,接著是元素名稱。如需更完整的討論,請參閱 下方。public void execute()
方法,不帶引數,會擲回 BuildException
。此方法實作工作本身。* 實際上,第一個字元之後的字母大小寫對 Ant 來說並不重要,但使用全部小寫是一個好習慣。
UnknownElement
。此 UnknownElement
會放置在目標物件中的清單內,或遞迴放置在另一個 UnknownElement
內。
perform()
方法呼叫每個 UnknownElement
。這會實例化工作。這表示工作只會在執行時間實例化。
project
和 location
變數取得其專案和在建置檔中的位置的參考。target
變數取得其所屬目標的參考。init()
會在執行階段呼叫。createXXX()
方法建立,或透過其 addXXX()
方法實例化並新增至此工作項。對應於 addConfiguredXXX()
的子元素會在此時建立,但實際的 addConfigured 方法不會呼叫。setXXX()
方法設定。addText()
方法新增至工作項。setXXX()
方法設定。addConfiguredXXX()
方法建立對應於此工作項的 XML 元素的子元素,這些方法會在此時呼叫。execute()
會在執行階段呼叫。如果 target1和
target2都依賴於
target3,則執行 ant target1 target2 會執行
target3中的所有工作項兩次。
在將屬性的值傳遞給對應的設定器方法之前,Ant 會永遠先展開屬性。從 Ant 1.8 開始,可以 延伸 Ant 的屬性處理,讓非字串物件成為包含單一屬性參考的字串評估結果。這些會透過符合類型的設定器方法直接指派。由於啟用此行為需要一些超越基礎知識的介入,因此標記打算允許此使用範例的屬性會是個好主意。
撰寫屬性設定值最常見的方式是使用 java.lang.String
參數。在此情況下,Ant 會將文字值(在屬性擴充後)傳遞給您的工作。但還有更多!如果您的設定值方法的參數是
boolean
,如果建置檔案中指定的數值是 true、
yes或
on之一,您的方法會傳遞數值
true,否則傳遞
false。
char
或 java.lang.Character
,您的方法會傳遞建置檔案中指定數值的第一個字元。int
、short
等),Ant 會將屬性的值轉換為此類型,從而確保您永遠不會收到該屬性不是數字的輸入。java.io.File
,Ant 會先確定建置檔案中給定的值是否代表絕對路徑名稱。如果不是,Ant 會將該值解釋為相對於專案 basedir 的路徑名稱。org.apache.tools.ant.types.Resource
,Ant 會將字串解析為 java.io.File
(如上所述),然後傳遞為 org.apache.tools.ant.types.resources.FileResource
。自 Ant 1.8 起org.apache.tools.ant.types.Path
,Ant 會將建置檔案中指定的數值進行分詞,接受 :和
;作為路徑分隔符號。相對路徑名稱將被解釋為相對於專案 basedir 的路徑名稱。
java.lang.Class
,Ant 會將建置檔案中給定的值解釋為 Java 類別名稱,並從系統類別載入器載入指定的類別。String
參數建構函式的類型,Ant 會使用此建構函式從建置檔案中給定的值建立新執行個體。org.apache.tools.ant.types.EnumeratedAttribute
的子類別,Ant 會呼叫此類別的 setValue
方法。如果您的工作應支援列舉屬性(值必須是預定義值集合一部分的屬性),請使用此方法。請參閱 org/apache/tools/ant/taskdefs/FixCRLF.java
和 setCr
中使用的內部 AddAsisRemove
類別以取得範例。EnumeratedAttribute
容易,而且可以產生更簡潔的程式碼。請注意,列舉中 toString()
的任何覆寫都會被忽略;建置檔案必須使用宣告的名稱(請參閱 Enum.getName()
)。您可能希望使用小寫的列舉常數名稱,與一般的 Java 樣式相反,以便在建置檔案中看起來更好。自 Ant 1.7.0 起如果給定屬性存在多個 setter 方法,會發生什麼事?採用 String
參數的方法永遠會敗給更明確的方法。如果還有更多 setter 可供 Ant 選擇,只會呼叫其中一個,但我們不知道會呼叫哪一個,這取決於 Java 虛擬機的實作。
假設你的工作需要支援名稱為 inner
的巢狀元素。首先,你需要一個類別來表示這個巢狀元素。通常你只需要使用 Ant 的類別之一,例如 org.apache.tools.ant.types.FileSet
來支援巢狀 fileset
元素。
巢狀元素或其巢狀子元素的屬性將使用與工作相同的機制處理(也就是屬性的 setter 方法、巢狀文字的 addText()
,以及子元素的 create/add/addConfigured 方法)。
現在你有一個類別 NestedElement
,假設要使用在你的巢狀 <inner>
元素,你有三個選項
public NestedElement createInner()
public void addInner(NestedElement anInner)
public void addConfiguredInner(NestedElement anInner)
差異是什麼?
選項 1 讓工作建立 NestedElement
的執行個體,類型沒有限制。對於選項 2 和 3,Ant 必須先建立 NestedInner
的執行個體,才能傳遞給工作,這表示 NestedInner
必須有一個 public
無參數建構函式或一個 public
單參數建構函式,採用 Project
類別作為參數。這是選項 1 和 2 之間唯一的差異。
選項 2 和 3 之間的差異在於 Ant 在將物件傳遞給方法之前對物件執行的動作。在呼叫建構函式後,addInner()
會直接收到物件,而 addConfiguredInner()
則是在處理這個新物件的屬性和巢狀子項後才取得物件。
如果你使用多個選項,會發生什麼事?只會呼叫其中一個方法,但我們不知道會呼叫哪一個,這取決於你的 JVM 實作。
如果你的工作需要巢狀使用已使用 <typedef>
定義的任意類型,你有兩個選項。
public void add(Type 類型)
public void addConfigured(Type 類型)
1 和 2 之間的差異與前一節中 2 和 3 之間的差異相同。
例如,假設某人想要處理 org.apache.tools.ant.taskdefs.condition.Condition
類型的物件,他可能有一個類別
public class MyTask extends Task { private List conditions = new ArrayList(); public void add(Condition c) { conditions.add(c); } public void execute() { // iterator over the conditions } }
可以這樣定義和使用這個類別
<taskdef name="mytask" classname="MyTask" classpath="classes"/> <typedef name="condition.equals" classname="org.apache.tools.ant.taskdefs.conditions.Equals"/> <mytask> <condition.equals arg1="${debug}" arg2="true"/> </mytask>
以下是更複雜的範例
public class Sample { public static class MyFileSelector implements FileSelector { public void setAttrA(int a) {} public void setAttrB(int b) {} public void add(Path path) {} public boolean isSelected(File basedir, String filename, File file) { return true; } } interface MyInterface { void setVerbose(boolean val); } public static class BuildPath extends Path { public BuildPath(Project project) { super(project); } public void add(MyInterface inter) {} public void setUrl(String url) {} } public static class XInterface implements MyInterface { public void setVerbose(boolean x) {} public void setCount(int c) {} } }
此類別定義了許多靜態類別,這些類別實作/延伸 Path
、MyFileSelector
和 MyInterface
。可以如下定義和使用這些類別
<typedef name="myfileselector" classname="Sample$MyFileSelector" classpath="classes" loaderref="classes"/> <typedef name="buildpath" classname="Sample$BuildPath" classpath="classes" loaderref="classes"/> <typedef name="xinterface" classname="Sample$XInterface" classpath="classes" loaderref="classes"/> <copy todir="copy-classes"> <fileset dir="classes"> <myfileselector attra="10" attrB="-10"> <buildpath path="." url="abc"> <xinterface count="4"/> </buildpath> </myfileselector> </fileset> </copy>
TaskContainer
包含一個單一方法 addTask
,基本上與巢狀元素的 add 方法 相同。當您的任務的 execute
方法被呼叫時,任務執行個體將會被設定 (其屬性和巢狀元素已處理),但在此之前不會被設定。
當我們 說 execute
會被呼叫時,我們說謊了 ;-). 事實上,Ant 會在 org.apache.tools.ant.Task
中呼叫 perform
方法,而該方法會反過來呼叫 execute
。此方法可確保會觸發 建置事件。如果您執行巢狀在您的任務中的任務執行個體,您也應該對這些執行個體呼叫 perform
,而不是 execute
。
讓我們撰寫自己的任務,在 System.out
串流上列印訊息。此任務有一個屬性,稱為 message
。
package com.mydomain; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; public class MyVeryOwnTask extends Task { private String msg; // The method executing the task public void execute() throws BuildException { System.out.println(msg); } // The setter for the "message" attribute public void setMessage(String msg) { this.msg = msg; } }
它真的很簡單 ;-)
將您的任務新增到系統也很簡單
<taskdef>
元素新增到您的專案。這實際上會將您的任務新增到系統中。<?xml version="1.0"?> <project name="OwnTaskExample" default="main" basedir="."> <taskdef name="mytask" classname="com.mydomain.MyVeryOwnTask"/> <target name="main"> <mytask message="Hello World! MyVeryOwnTask works!"/> </target> </project>
若要直接從建立它的建置檔使用任務,請將 <taskdef>
宣告放在目標中,在編譯之後。使用 <taskdef>
的 classpath 屬性指向程式碼剛剛編譯的位置。
<?xml version="1.0"?> <project name="OwnTaskExample2" default="main" basedir="."> <target name="build" > <mkdir dir="build"/> <javac srcdir="source" destdir="build"/> </target> <target name="declare" depends="build"> <taskdef name="mytask" classname="com.mydomain.MyVeryOwnTask" classpath="build"/> </target> <target name="main" depends="declare"> <mytask message="Hello World! MyVeryOwnTask works!"/> </target> </project>
新增任務的另一種方式 (更永久的方式) 是將任務名稱和實作類別名稱新增到 org.apache.tools.ant.taskdefs
套件中的 default.properties 檔案。然後您可以使用它,就像它是內建任務一樣。
Ant 能在執行建置專案所需任務時產生建置事件。可以將監聽器附加到 Ant 以接收這些事件。例如,此功能可用於將 Ant 連線到 GUI 或將 Ant 整合到 IDE 中。
若要使用建置事件,您需要建立一個 ant Project
物件。然後,您可以呼叫 addBuildListener
方法將監聽器新增到專案中。您的監聽器必須實作 org.apache.tools.antBuildListener
介面。監聽器將收到下列事件的 BuildEvents
如果建置檔案透過 <ant>
或 <subant>
呼叫另一個建置檔案,或使用 <antcall>
,您將建立一個新的 Ant「專案」,它會傳送自己的目標和任務層級事件,但絕不會傳送建置開始/完成事件。自 Ant 1.6.2 起,BuildListener
介面有一個名為 SubBuildListener
的延伸,它會收到兩個新事件,如下所示
如果您有興趣了解這些事件,您只需實作新介面,而不是 BuildListener
(當然,還要註冊監聽器)。
如果您希望從命令列附加監聽器,可以使用 -listener 選項。例如
ant -listener org.apache.tools.ant.XmlLogger
將使用監聽器執行 Ant,該監聽器會產生建置進度的 XML 表示。此監聽器包含在 Ant 中,預設監聽器也是如此,它會將記錄產生到標準輸出。
注意:監聽器不得直接存取 System.out
和 System.err
,因為 Ant 的核心會將這些串流上的輸出重新導向到建置事件系統。存取這些串流可能會導致 Ant 中出現無限迴圈。根據 Ant 的版本,這將導致建置終止或 JVM 用完堆疊空間。記錄器也不能直接存取 System.out
和 System.err
。它必須使用已設定的串流。
注意:BuildListener
的所有方法(「建置已開始」和「建置已完成」事件除外)都可能同時在多個執行緒上發生,例如當 Ant 正在執行 <parallel>
任務時。
撰寫轉接器到您最愛的記錄程式庫非常容易。只要實作 BuildListener
介面,實例化您的記錄器,然後將訊息委派給該實例即可。
在開始建置時,將您的轉接器類別和記錄程式庫提供給建置類別路徑,並透過 -listener 選項啟用您的記錄器,如上所述。
public class MyLogAdapter implements BuildListener { private MyLogger getLogger() { final MyLogger log = MyLoggerFactory.getLogger(Project.class.getName()); return log; } @Override public void buildStarted(final BuildEvent event) { final MyLogger log = getLogger(); log.info("Build started."); } @Override public void buildFinished(final BuildEvent event) { final MyLogger logger = getLogger(); MyLogLevelEnum loglevel = ... // map event.getPriority() to enum via Project.MSG_* constants boolean allOK = event.getException() == null; String logmessage = ... // create log message using data of the event and the message invoked logger.log(loglevel, logmessage); } // implement all methods in that way }
透過 Java 延伸 Ant 的另一種方式是變更現有的任務,這絕對受到鼓勵。對現有來源和新任務的變更都可以納入 Ant 程式碼庫中,這對所有使用者都有利,並能分散維護負擔。
請參閱 Apache 網站上的 參與 頁面,以取得有關如何取得最新來源和如何提交變更以重新併入來源樹的詳細資訊。
Ant 也有提供一些 任務指南,為開發和測試任務的人員提供一些建議。即使您打算將任務保留給自己,您仍然應該閱讀這份指南,因為它應該具有參考價值。