Ant任务编写全解析
1. Ant任务的生命周期
Ant任务的构建始于加载和解析构建文件,其生命周期包含以下几个关键步骤:
1.
任务实例创建
:在解析构建文件时,Ant会为文件中每个任务声明创建一个合适的
Task
子类实例,使用其空构造函数。
2.
任务信息告知
:Ant会告知任务其所属的项目、目标以及一些其他细节,例如该任务在构建文件中的行号。
3.
初始化方法调用
:Ant调用
Task
类的
init()
方法,不过大多数任务不会重写此方法。
4.
目标执行
:Ant按照其认为合适的顺序执行目标,可能不会执行所有目标,这取决于条件目标的条件是否满足。
5.
任务执行
:目标内的任务会逐个执行。对于每个任务,Ant会根据构建文件中的属性和元素值进行配置,然后调用其
execute()
方法。
对于不继承自
org.apache.tools.ant.Task
的类,Ant的API中有一个
TaskAdapter
,它继承自
Task
,包含一个对象实例并调用其
execute
方法。Ant内部使用
TaskAdapter
来处理不继承自
Task
的任务。
2. Ant API基础
在深入进行任务开发之前,了解一些Ant的API是很有帮助的。以下是一些在大多数Ant任务中常用的关键类及其重要方法:
-
Task类
:
org.apache.tools.ant.Task
抽象类是Ant任务的典型基类,是Ant构建过程中的主要工作单元。继承自
Task
的类至少应实现
execute
方法。该类通过受保护的成员变量
project
提供对
Project
对象的访问。
-
public void init() throws BuildException
:在解析构建文件时遇到任务时调用此方法,实际中很少重写,因为初步配置可在
execute
方法中完成。
-
public void execute() throws BuildException
:这是任务的核心方法,若出现问题,可抛出
org.apache.tools.ant.BuildException
。
-
log(String msg, int msgLevel)
和
log(String msg)
:用于调用
Project
的日志方法,有五个日志级别,按优先级降序排列为
MSG_ERR
、
MSG_WARN
、
MSG_INFO
、
MSG_VERBOSE
、
MSG_DEBUG
。
-
public Project getProject()
:允许任务访问项目范围的信息,如设置新属性或访问现有属性的值。
-
Project类
:
-
String getProperty (String name)
:返回Ant属性的值,若未定义则返回
null
。由于Ant会在将属性值传递给任务之前自动展开属性,因此该方法在任务中很少使用。
-
void setNewProperty (String name, String value)
:为属性赋值,Ant属性是不可变的,此方法确保遵循不可变规则,若属性已存在则不会更改。
-
void setProperty (String name, String value)
:这是Ant 1.4及之前版本中
setNewProperty
的前身,允许调用者覆盖属性,但每次这样做时会打印警告信息。若编写与旧版本Ant配合使用的任务,必须使用此方法设置属性。
-
String replaceProperties(String value)
:属性在XML属性中会自动展开,但在接收未自动展开的元素文本时,此方法很有用。
-
java.io.File getBaseDir()
:返回项目的基目录,用于解析相对路径,但由于Ant的自动文件和路径展开功能,实际中很少使用。
-
String getName()
:返回项目的名称,即
<project>
元素的
name
属性指定的值。
-
java.io.File resolveFile(String filename)
:返回一个
File
对象,其路径为指定文件名的绝对路径。若文件名是相对路径,则相对于项目的基目录进行解析。
-
Path类
:
-
String toString()
:重写
Object.toString
方法,提供完全解析且特定于平台的完整路径。
-
static String[] translatePath(Project project, String path)
:从包含由冒号
:
或分号
;
分隔元素的单个路径中提供路径元素数组。
-
int size()
:返回
Path
实例中的路径元素数量。
-
String[] list()
:返回
Path
实例中的路径元素数组。
-
FileSet类
:
-
DirectoryScanner getDirectoryScanner(Project project)
:要处理文件集对象中的文件,首先调用此方法获取
DirectoryScanner
对象,然后使用
DirectoryScanner
的API遍历文件。
-
java.io.File getDir(Project project)
:返回此
FileSet
实例指定的基目录。
-
DirectoryScanner类
:
String[] getIncludedFiles()
:返回所有包含的文件名,考虑了包含/排除模式,返回的文件名相对于指定的根目录。
-
EnumeratedAttribute类
:通过要求属性值为可能值列表中的一个,Ant可以轻松处理简单的验证问题。子类必须实现
getValues
方法,使用
getValue
方法从构建文件中检索设置的值。
-
abstract String[] getValues()
:由子类实现,返回允许的值集。
-
String getValue()
:返回设置的值,保证是
getValues
返回的值之一。
-
int getIndex()
:若需要值在
getValues
返回列表中的位置,可使用此方法获取。
-
FileUtils类
:
-
static FileUtils newFileUtils()
:大多数
FileUtils
方法是实例方法,使用此方法返回一个
FileUtils
实例。
-
copyFile (many overloaded signatures)
:使用这些方法复制文件可处理一些细节,如可选的令牌替换过滤和创建父目录。
-
java.io.File createTempFile(String prefix, String suffix, File parentDir)
:返回一个当前不存在的临时文件名,实际上并不创建文件,只是确保生成的名称不是现有文件。
-
java.io.File normalize(String path)
:清理绝对文件或目录路径,确保其在当前平台上是有效的绝对路径,会将驱动器字母大写(若有),删除多余的斜杠,并解析
.
和
..
引用。
-
java.io.File resolveFile(java.io.File file, String filename)
:若文件名不是绝对路径,则相对于另一个文件解析并规范化文件路径。
-
void setFileLastModified(File file, long time)
:这是基于反射的
File.setLastModified
包装器,可处理Java 1.1,使用此方法更改文件的时间戳。
3. 任务如何获取数据
在构建文件中,任务以包含属性、子元素甚至正文文本的XML元素形式指定。Ant提供了一种优雅且简单的方式,让任务以丰富的、特定于领域的方式获取这些信息。在构建文件执行期间,Ant会创建所使用任务的实例,并将属性和子元素信息传递给它们。Ant使用Java内省机制,查找特定命名的方法并使用构建文件中的数据调用它们。
3.1 设置属性
XML属性由名称和文本值组成,但将字符串值传递给任务实例远不止这么简单。以下是不同类型属性的设置方式:
-
字符串属性
:最简单的情况是任务使用属性,如
<sometask value="some value"/>
,任务有一个
setValue
方法:
private String value;
public void setValue (String value) {
this.value = value;
}
这类似于JavaBeans风格的命名约定,属性对应于以
set
为前缀的setter方法。
-
布尔属性
:许多任务只需要一个布尔类型的开关,如
true/false
、
on/off
或
yes/no
。通过让setter参数接受
boolean
或
java.lang.Boolean
类型,若值为
yes
、
on
或
true
,任务将得到
true
,否则为
false
。
private boolean toggle = false;
public void setToggle(boolean toggle) {
this.toggle = toggle;
}
在构建文件中使用:
<setter toggle="on"/>
由于属性的隐式展开,以下两种情况调用
setToggle
方法时传入的都是
true
:
<property name="toggle.state" value="on"/>
<setter toggle="${toggle.state}"/>
前提是
toggle.state
属性之前未设置为评估为
false
的值。
-
数字属性
:属性内省机制支持所有Java基本类型和包装类型,大多数基本类型和对应的包装类用于处理数字数据,如
byte
、
short
、
int
、
long
、
float
、
double
。若属性值可以转换为所需类型,则一切正常;若转换文本为相应数字类型时出错,将抛出
NumberFormatException
,导致构建停止。
-
单字符属性
:虽然单字符属性不太常用,但Ant允许使用接受
char
或
java.lang.Character
类型的setter,提供给setter的字符是属性值的第一个字符,忽略任何其他字符。
-
文件或目录属性
:任务通常需要将文件或目录作为属性传递。Ant通过实现接受
java.io.File
参数的setter提供对文件或目录属性的内置支持。使用
File
参数的好处是,当在构建文件中指定相对路径时,路径会被解析为绝对路径。例如:
<mytask destdir="output"/>
任务实现
setDestDir
方法:
private File destDir;
public void setDestDir(File destDir) {
this.destDir = destDir;
}
在
execute
方法中验证目录是否存在:
if (!destDir.isDirectory()) {
throw new BuildException(destDir + " is not a valid directory");
}
-
路径属性
:需要处理路径(如类路径)的任务可以使用接受
org.apache.tools.ant.types.Path参数的setter。允许Ant提供Path对象而不是简单的分隔字符串的好处在于Ant的跨平台能力。构建文件可以使用分号或冒号分隔路径元素,使用反斜杠或正斜杠分隔目录,Ant会自动将路径调整为适合当前平台的格式。例如:
<property environment="env"/>
<setter path="${env.TEMP}:build/output"/>
在Windows平台上运行时,路径会自动调整为适合Windows的格式。
-
枚举属性
:
EnumeratedAttribute
类是Ant简化任务编写的一个很好的例子。若只允许属性值为固定的可能值集合中的一个,使用Ant的
EnumeratedAttribute
类型可以节省一些验证编码时间。例如:
package org.example.antbook.tasks;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.EnumeratedAttribute;
public class EnumTask extends Task {
private String version = "2.3";
public void setVersion(ServletVersion ver) {
version = ver.getValue();
}
public void execute() {
log("Servlet version = " + version);
}
public static class ServletVersion extends EnumeratedAttribute {
public String[] getValues() {
return new String[] {"2.2", "2.3"};
}
}
}
-
类属性
:若任务的功能包含动态可交换的实现,使用
Class.forName,使用java.lang.Class类型的setter可以确保类存在,否则构建失败。但这种方式的实用性有限,因为它只在Ant的操作类路径中搜索类,而任务通常应足够灵活,允许构建文件指定自己的类路径。建议对指定类名的属性使用String类型,使用Class.forName从<taskdef>指定的任务类路径中检索类,或使用AntClassLoader.loadClass从不同的类路径中获取类。 -
用户自定义类型
:最后,对于属性setter类型,你可以定义自己的类型。任何具有公共
String构造函数的类类型都是允许的,这实际上与数字数据类型使用的机制相同,因为所有包装类都有接受单个String参数的构造函数。例如,定义一个Hex类:
package org.example.antbook.tasks;
public class Hex {
private Integer value;
public Hex(String hex) {
value = Integer.decode(hex);
}
public int intValue() {
return value.intValue();
}
public String toString() {
return "0x" + Integer.toHexString(value.intValue());
}
}
在任务中使用:
private Hex hex;
public void setHex(Hex hex) {
this.hex = hex;
}
public void execute() {
if (hex != null) {
log(hex + " = " + hex.intValue());
}
}
在构建文件中指定十六进制数:
<setter hex="0x1A"/>
任务的输出为:
[setter] 0x1a = 26
综上所述,Ant提供了丰富的机制来帮助开发者编写任务,通过合理利用这些机制,可以提高开发效率,减少不必要的编码工作。开发者可以根据任务的具体需求,选择合适的属性类型和API方法,实现高效、灵活的任务开发。
4. 任务获取数据的机制总结
为了更清晰地了解任务获取数据的方式,下面通过表格对不同属性类型的设置方式进行总结:
| 属性类型 | 设置方式 | 示例 |
| — | — | — |
| 字符串属性 | 任务有以
set
为前缀的方法,参数为
String
类型 |
<sometask value="some value"/>
,任务中
setValue
方法 |
| 布尔属性 | setter参数接受
boolean
或
java.lang.Boolean
类型 |
<setter toggle="on"/>
,任务中
setToggle
方法 |
| 数字属性 | 支持Java基本类型和包装类型的数字数据 | 如
byte
、
short
等类型的setter方法 |
| 单字符属性 | setter接受
char
或
java.lang.Character
类型 | 取属性值的第一个字符 |
| 文件或目录属性 | setter接受
java.io.File
参数 |
<mytask destdir="output"/>
,任务中
setDestDir
方法 |
| 路径属性 | setter接受
org.apache.tools.ant.types.Path
参数 |
<setter path="${env.TEMP}:build/output"/>
|
| 枚举属性 | 使用
EnumeratedAttribute
类型 | 示例中的
EnumTask
类 |
| 类属性 | 建议使用
String
类型,通过
Class.forName
检索类 | |
| 用户自定义类型 | 类有公共
String
构造函数 | 自定义的
Hex
类 |
5. 任务开发流程示例
下面通过一个简单的示例,展示如何开发一个完整的Ant任务。假设我们要开发一个任务,用于将指定目录下的所有文件复制到另一个目录。
5.1 定义任务类
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.FileUtils;
import java.io.File;
import java.io.IOException;
public class CopyFilesTask extends Task {
private File srcDir;
private File destDir;
private FileSet fileSet;
public void setSrcDir(File srcDir) {
this.srcDir = srcDir;
}
public void setDestDir(File destDir) {
this.destDir = destDir;
}
public void addFileset(FileSet fileSet) {
this.fileSet = fileSet;
}
@Override
public void execute() throws BuildException {
if (srcDir == null || destDir == null || fileSet == null) {
throw new BuildException("srcDir, destDir and fileSet are required");
}
FileUtils fileUtils = FileUtils.newFileUtils();
try {
fileUtils.copyDirectory(srcDir, destDir, fileSet, null);
log("Files copied successfully from " + srcDir + " to " + destDir);
} catch (IOException e) {
throw new BuildException("Error copying files: " + e.getMessage(), e);
}
}
}
5.2 配置构建文件
<project name="CopyFilesProject" default="copy-files">
<target name="copy-files">
<taskdef name="copy-files" classname="com.example.CopyFilesTask"/>
<copy-files srcDir="source" destDir="destination">
<fileset dir="source">
<include name="**/*.txt"/>
</fileset>
</copy-files>
</target>
</project>
5.3 执行任务
在命令行中执行以下命令:
ant copy-files
6. 任务开发的注意事项
-
属性验证
:在任务执行前,要对传入的属性进行验证,确保其合法性。例如,在上述
CopyFilesTask中,检查srcDir、destDir和fileSet是否为空。 -
异常处理
:在
execute方法中,要对可能出现的异常进行捕获和处理,避免任务执行过程中因异常而崩溃。如在复制文件时捕获IOException。 -
日志输出
:使用
log方法输出任务执行过程中的信息,方便调试和监控。例如,在文件复制成功后输出日志信息。 -
兼容性
:考虑不同版本的Ant的兼容性,如使用
setNewProperty和setProperty方法时要注意版本差异。
7. 总结与展望
Ant提供了强大而灵活的机制来开发自定义任务,通过了解Ant任务的生命周期、API基础以及任务获取数据的方式,开发者可以轻松地开发出满足各种需求的任务。在实际开发中,要根据具体需求选择合适的属性类型和API方法,同时注意属性验证、异常处理等问题,以确保任务的稳定性和可靠性。
未来,随着软件开发的不断发展,Ant可能会进一步优化其API,提供更多的功能和工具,帮助开发者更高效地开发任务。同时,开发者也可以结合其他技术和工具,如Maven、Gradle等,实现更复杂的构建和部署流程。
以下是Ant任务开发的流程图:
graph TD;
A[开始] --> B[加载和解析构建文件];
B --> C[创建任务实例];
C --> D[告知任务相关信息];
D --> E[调用init方法];
E --> F[执行目标];
F --> G[执行目标内的任务];
G --> H[配置任务属性];
H --> I[调用execute方法];
I --> J[任务执行结束];
J --> K[结束];
通过以上内容,我们对Ant任务开发有了全面的了解,希望这些知识能帮助你在实际开发中更好地运用Ant进行任务开发。
Ant自定义任务开发详解
超级会员免费看
7

被折叠的 条评论
为什么被折叠?



