编写您自己的任务非常容易
org.apache.tools.ant.Task
或 另一个设计为可扩展的类 的 Java 类。public void
方法。方法名称必须以 set
开头,后跟属性名称,名称的第一个字符大写,其余字符小写*。也就是说,要支持名为 file 的属性,您需要创建一个名为 setFile
的方法。根据参数的类型,Ant 会为您执行一些转换,请参阅 下方。parallel
),您的类必须实现接口 org.apache.tools.ant.TaskContainer
。如果您这样做,您的任务将不支持任何其他嵌套元素。请参阅 下方。public void addText(String)
方法。请注意,Ant 不会扩展传递给任务的文本中的属性。Object
类型的 public
方法。create 方法的名称必须以 create
开头,后跟元素名称。add(或 addConfigured)方法必须是一个接受单个参数的 public void
方法,该参数是具有无参数构造函数的 Object
类型。add(addConfigured)方法的名称必须以 add
(addConfigured
)开头,后跟元素名称。有关更完整的讨论,请参阅 下方。BuildException
的 public void execute()
方法。此方法实现任务本身。* 实际上,第一个字母之后的字母的大小写对 Ant 来说并不重要,使用全小写是一个很好的约定。
UnknownElement
。此 UnknownElement
被放置在目标对象内的列表中,或者递归地放置在另一个 UnknownElement
内。UnknownElement
都使用 perform()
方法调用。这将实例化任务。这意味着任务仅在运行时实例化。project
和 location
变量获取对构建文件中的项目和位置的引用。target
变量获取对它所属的目标的引用。init()
在运行时调用。createXXX()
方法创建,或者在运行时实例化并通过其 addXXX()
方法添加到该任务。与 addConfiguredXXX()
相对应的子元素在此处创建,但实际的 addConfigured 方法不会被调用。setXXX()
方法在运行时设置。addText()
方法添加到任务。setXXX()
方法在运行时设置。addConfiguredXXX()
方法创建,则这些方法现在将被调用。execute()
在运行时调用。如果 target1和
target2都依赖于
target3,则运行 ant target1 target2 将在
target3中运行所有任务两次。
Ant 始终在将属性的值传递给相应的 setter 方法之前扩展属性。从 Ant 1.8 开始,可以 扩展 Ant 的属性处理,以便非字符串对象可能是包含单个属性引用的字符串的评估结果。这些将通过匹配类型的 setter 方法直接分配。由于需要一些超出基本操作的干预才能启用此行为,因此最好标记旨在允许此使用模式的属性。
编写属性 setter 的最常见方法是使用 java.lang.String
参数。在这种情况下,Ant 将将字面值(在属性扩展之后)传递给您的任务。但还有更多!如果您的 setter 方法的参数是
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
参数的方法将始终输给更具体的方法。如果仍然存在 Ant 可以选择的 setter,则只会调用其中一个,但我们不知道是哪个,这取决于您的 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
无参数构造函数或一个接受 Project
类作为参数的 public
单参数构造函数。这是选项 1 和 2 之间的唯一区别。
2 和 3 之间的区别是 Ant 在将对象传递给方法之前对对象所做的操作。 addInner()
将在构造函数调用之后直接接收一个对象,而 addConfiguredInner()
将在处理此新对象的属性和嵌套子元素之后获取该对象。
如果您使用多个选项会发生什么?只会调用其中一个方法,但我们不知道是哪个,这取决于您的 JVM 的实现。
如果您的任务需要嵌套使用 <typedef>
定义的任意类型,您有两个选择。
public void add(Type type)
public void addConfigured(Type 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
接口。监听器将为以下事件接收构建事件
如果构建文件通过<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 还有一些任务指南,为开发和测试任务的人员提供了一些建议。即使您打算将任务保密,您也应该阅读它,因为它应该是有益的。