Ant扩展:日志记录、自定义映射器与选择器的深入探索
在软件开发过程中,Ant作为一个强大的构建工具,为我们提供了许多便利。然而,为了满足特定的需求,我们常常需要对Ant进行进一步的扩展。本文将详细介绍如何扩展Ant的日志记录功能、开发自定义映射器和选择器。
1. 日志记录扩展
Ant提供了多种日志记录方式,其中Log4j和自定义日志记录器是常用的扩展方式。
1.1 使用Log4j进行日志记录
Ant包含了
Log4jListener
和
CommonsLoggingListener
,它们都可以将日志输出到Log4j。
CommonsLoggingListener
在需要灵活选择日志记录API的环境中更为推荐,但需要额外依赖
commons-logging.jar
。
日志记录的配置通过
log4j.properties
文件完成,以下是一个示例配置:
log4j.rootCategory=INFO,file
log4j.category.org.apache.tools.ant.Project=INFO,file,mail
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.layout=org.apache.log4j.TTCCLayout
log4j.appender.file.file=build.log
log4j.appender.file.maxBackupIndex=3
log4j.appender.file.maxFileSize=100KB
log4j.appender.mail=org.apache.log4j.net.SMTPAppender
log4j.appender.mail.layout=org.apache.log4j.HTMLLayout
log4j.appender.mail.Threshold=ERROR
log4j.appender.mail.SMTPHost=localhost
log4j.appender.mail.bufferSize=1
log4j.appender.mail.to=erik@example.org
log4j.appender.mail.from=erik@example.org
log4j.appender.mail.subject=Build Failure!
这个配置文件的作用如下:
| 配置项 | 作用 |
| — | — |
|
log4j.rootCategory
| 设置根日志级别和输出目标 |
|
log4j.category.org.apache.tools.ant.Project
| 设置特定类别的日志级别和输出目标 |
|
log4j.appender.file
| 配置文件输出的日志追加器 |
|
log4j.appender.mail
| 配置邮件输出的日志追加器 |
以下是使用
CommonsLoggingListener
的示例:
<project default="fail">
<target name="fail">
<fail message="Example build failure"/>
</target>
</project>
命令行运行:
ant -listener org.apache.tools.ant.listener.CommonsLoggingListener
运行过程中,
build.log
文件会记录详细的日志信息,当构建失败时,还会发送一封包含错误信息的HTML格式邮件。
1.2 开发自定义日志记录器
自定义日志记录器可以满足特定的日志记录需求,例如发送完整构建日志的邮件。以下是
MailLogger
的实现代码:
package org.apache.tools.ant.listener;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.util.StringUtils;
import org.apache.tools.mail.MailMessage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
public class MailLogger extends DefaultLogger {
private StringBuffer buffer = new StringBuffer();
public void buildFinished(BuildEvent event) {
super.buildFinished(event);
Project project = event.getProject();
Hashtable properties = project.getProperties();
Properties fileProperties = new Properties();
String filename = (String)
properties.get("MailLogger.properties.file");
if (filename != null) {
InputStream is = null;
try {
is = new FileInputStream(filename);
fileProperties.load(is);
} catch (IOException ioe) {
// ignore because properties file is not required
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
for (Enumeration e = fileProperties.keys();
e.hasMoreElements();) {
String key = (String) e.nextElement();
String value = fileProperties.getProperty(key);
properties.put(key, project.replaceProperties(value));
}
boolean success = (event.getException() == null);
String prefix = success ? "success" : "failure";
try {
boolean notify = Project.toBoolean(getValue(properties,
prefix + ".notify", "on"));
if (!notify) {
return;
}
String mailhost = getValue(properties, "mailhost",
"localhost");
String from = getValue(properties, "from", null);
String toList = getValue(properties, prefix + ".to",
null);
String subject = getValue(properties,
prefix + ".subject",
(success) ? "Build Success" : "Build Failure");
sendMail(mailhost, from, toList, subject,
buffer.toString());
} catch (Exception e) {
System.out.println("MailLogger failed to send e-mail!");
e.printStackTrace(System.err);
}
}
protected void log(String message) {
buffer.append(message).append(StringUtils.LINE_SEP);
}
private String getValue(Hashtable properties, String name,
String defaultValue) throws Exception {
String propertyName = "MailLogger." + name;
String value = (String) properties.get(propertyName);
if (value == null) {
value = defaultValue;
}
if (value == null) {
throw new Exception("Missing required parameter: " +
propertyName);
}
return value;
}
private void sendMail (String mailhost, String from,
String toList, String subject,
String message) throws IOException {
MailMessage mailMessage = new MailMessage(mailhost);
mailMessage.from(from);
StringTokenizer t = new StringTokenizer(toList, ", ", false);
while (t.hasMoreTokens()) {
mailMessage.to(t.nextToken());
}
mailMessage.setSubject(subject);
PrintStream ps = mailMessage.getPrintStream();
ps.println(message);
mailMessage.sendAndClose();
}
}
这个自定义日志记录器的工作流程如下:
1. 在构建完成时,首先调用父类的
buildFinished
方法。
2. 加载配置文件中的属性,并与项目属性合并。
3. 根据构建结果判断是否需要发送邮件。
4. 如果需要发送邮件,获取邮件相关的配置信息,如邮件服务器、发件人、收件人、主题等。
5. 调用
sendMail
方法发送邮件。
使用
MailLogger
时,需要提供必要的配置参数,推荐使用外部属性文件:
MailLogger.from = erik@example.org
MailLogger.failure.to = erik@example.org
MailLogger.mailhost = localhost
MailLogger.success.to = erik@example.org
MailLogger.success.subject = ${ant.project.name} - Build success
MailLogger.failure.subject = FAILURE - ${ant.project.name}
MailLogger.success.notify = off
命令行运行:
ant -f buildmail.xml -logger org.apache.tools.ant.listener.MailLogger -DMailLogger.properties.file=maillogger.properties fail
2. 开发自定义映射器
Ant的一些任务支持
<mapper>
数据类型,用于将文件名映射到一个或多个对应的文件。在大多数情况下,内置的映射器已经足够,但有时需要开发自定义映射器。
例如,为了加速包含单元测试的构建,我们开发了
PackageNameMapper
:
public class PackageNameMapper extends GlobPatternMapper {
protected String extractVariablePart(String name) {
String var = name.substring(prefixLength,
name.length() - postfixLength);
return var.replace(File.separatorChar, '.');
}
}
自定义映射器需要实现
org.apache.tools.ant.util.FileNameMapper
接口,这里我们继承了
GlobPatternMapper
以利用其通配符匹配功能。
在构建文件中使用自定义映射器的示例:
<uptodate property="is.uptodate">
<srcfiles dir="${some.dir}"/>
<mapper classname="org.example.antbook.MyCustomMapper"
classpathref="mapper.classpath"
from="*Test.java" to="${test.data.dir}/TEST-*Test.xml"/>
</uptodate>
3. 开发自定义选择器
Ant的文件集和模式集提供了强大的文件选择功能,但有时需要开发自定义选择器来满足特定需求。例如,我们开发了
ReadOnlySelector
来选择只读文件:
package org.example.antbook;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.selectors.BaseExtendSelector;
import java.io.File;
public class ReadOnlySelector extends BaseExtendSelector {
public boolean isSelected(File basedir, String filename, File file)
throws BuildException {
return (!file.canWrite());
}
}
自定义选择器需要继承
BaseExtendSelector
并实现
isSelected
方法。
通过以上扩展,我们可以根据具体需求定制Ant的日志记录、文件映射和文件选择功能,提高构建过程的效率和灵活性。
Ant扩展:日志记录、自定义映射器与选择器的深入探索
4. 自定义映射器与选择器的应用场景分析
在实际开发中,自定义映射器和选择器能够解决许多特定的问题,下面我们详细分析它们的应用场景。
4.1 自定义映射器的应用场景
自定义映射器在处理文件路径和命名规则不一致的情况时非常有用。例如,在软件开发中,Java源文件通常按照包名的目录结构存放,而单元测试结果可能以扁平的目录结构存储,文件名中包含点分的包名。使用
PackageNameMapper
可以将Java源文件的路径映射到对应的单元测试结果文件。
以下是一个更详细的流程图,展示了使用自定义映射器加速单元测试的流程:
graph TD;
A[开始构建] --> B[检查单元测试结果与源文件时间戳];
B --> C{是否需要运行测试};
C -- 是 --> D[运行单元测试];
C -- 否 --> E[跳过测试];
D --> F[记录测试结果];
E --> F;
F --> G[完成构建];
B --> H[使用自定义映射器进行路径映射];
H --> C;
这个流程图清晰地展示了自定义映射器在整个构建过程中的作用,通过比较时间戳和路径映射,避免了不必要的单元测试,从而提高了构建速度。
4.2 自定义选择器的应用场景
自定义选择器可以根据特定的文件属性进行文件筛选。例如,在项目中,我们可能只需要处理只读文件,或者只处理特定日期之后修改的文件。
ReadOnlySelector
就是一个很好的例子,它可以筛选出只读文件。
以下是一个使用自定义选择器的操作步骤:
1. 在项目中创建
ReadOnlySelector
类,代码如下:
package org.example.antbook;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.selectors.BaseExtendSelector;
import java.io.File;
public class ReadOnlySelector extends BaseExtendSelector {
public boolean isSelected(File basedir, String filename, File file)
throws BuildException {
return (!file.canWrite());
}
}
- 在构建文件中使用自定义选择器:
<fileset dir="your_directory">
<custom selectorclass="org.example.antbook.ReadOnlySelector"/>
</fileset>
- 执行构建任务,Ant会根据自定义选择器筛选出只读文件进行后续处理。
5. 扩展Ant的注意事项
在扩展Ant时,需要注意以下几点:
| 注意事项 | 说明 |
| — | — |
| 依赖管理 | 对于使用的第三方库,如
commons-logging.jar
和
log4j.jar
,要确保正确配置类路径,避免出现类找不到的错误。 |
| 配置文件 | 对于日志记录和自定义日志记录器,配置文件的路径和格式要正确,否则可能导致配置不生效。 |
| 兼容性 | 自定义的映射器和选择器要考虑与Ant的版本兼容性,避免在不同版本的Ant中出现问题。 |
| 性能影响 | 在开发自定义组件时,要考虑其对构建性能的影响,避免引入不必要的开销。 |
6. 总结
通过对Ant的日志记录、自定义映射器和选择器的扩展,我们可以根据项目的具体需求定制构建过程,提高构建的效率和灵活性。在日志记录方面,我们可以使用Log4j进行灵活的日志配置,也可以开发自定义日志记录器满足特定的日志需求,如发送完整构建日志的邮件。在文件处理方面,自定义映射器可以解决文件路径和命名规则不一致的问题,自定义选择器可以根据特定的文件属性进行文件筛选。
在实际应用中,我们要根据项目的具体情况选择合适的扩展方式,并注意扩展过程中的注意事项,确保扩展的稳定性和性能。通过不断地探索和实践,我们可以充分发挥Ant的强大功能,为软件开发提供更好的支持。
超级会员免费看
1153

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



