57、Ant 扩展深入探究

Ant 扩展深入探究

1. 日志记录相关

Ant 提供了多种日志记录的方式,下面将详细介绍使用 Log4j 进行日志记录以及自定义日志记录器的相关内容。

1.1 使用 Log4j 日志记录功能

Ant 包含了 Log4jListener CommonsLoggingListener ,二者都能将日志输出到 Log4j。 CommonsLoggingListener 适用于需要灵活选择日志记录 API 的环境,但除了 Log4j 依赖外,还需要 commons - logging.jar

当应用程序记录事件时,事件会被记录到特定的类别中,类别是基于点分隔的文本表示的层次结构。Ant 的 CommonsLoggingListener 类使用 BuildEvent 提供的上下文的完全限定类名作为类别名称。每个事件会被记录到 DEBUG WARN INFO ERROR FATAL 优先级级别(注意: CommonsLoggingListener 不使用 FATAL 级别)。

Log4j 从 log4j.properties 文件(或使用 ANT_OPTS=-Dlog4j.configuration=<filename> )进行初始化。默认情况下,配置文件从当前目录加载。 CommonsLoggingListener 通过 Log4j 触发每个事件,包括调试信息,这使得我们可以对记录哪些事件、记录位置以及使用的格式进行配置控制。

以下是一个 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!                          

下面是一个简单的 build.xml 文件示例,用于触发构建失败:

<project default="fail">
  <target name="fail">
    <fail message="Example build failure"/>
  </target>
</project>

使用以下命令行运行:

ant -listener org.apache.tools.ant.listener.CommonsLoggingListener

控制台输出如下:

Buildfile: build.xml
fail:
BUILD FAILED
C:\AntBook\Sections\Extending\listeners\build.xml:3: Example build failure
Total time: 1 second

同时, build.log 文件会被创建或追加内容,示例输出如下:

[main] INFO org.apache.tools.ant.Project - Build started.
[main] INFO org.apache.tools.ant.Project - Build started.
[main] INFO org.apache.tools.ant.Target - Target "fail" started.
[main] INFO org.apache.tools.ant.taskdefs.Exit - Task "fail" started.
[main] ERROR org.apache.tools.ant.taskdefs.Exit - Task "fail" finished with error.
C:\AntBook\Sections\Extending\listeners\build.xml:3: Example build failure
        at org.apache.tools.ant.taskdefs.Exit.execute(Exit.java:90)
        at org.apache.tools.ant.Task.perform(Task.java:317)
        at org.apache.tools.ant.Target.execute(Target.java:309)
        at org.apache.tools.ant.Target.performTasks(Target.java:334)
        at org.apache.tools.ant.Project.executeTarget(Project.java:1216)
        at org.apache.tools.ant.Project.executeTargets(Project.java:1160)
        at org.apache.tools.ant.Main.runBuild(Main.java:605)
        at org.apache.tools.ant.Main.start(Main.java:195)
        at org.apache.tools.ant.Main.main(Main.java:234)
[main] ERROR org.apache.tools.ant.Target - Target "fail" finished with error.
C:\AntBook\Sections\Extending\listeners\build.xml:3: Example build failure
        at org.apache.tools.ant.taskdefs.Exit.execute(Exit.java:90)
        at org.apache.tools.ant.Task.perform(Task.java:317)
        at org.apache.tools.ant.Target.execute(Target.java:309)
        at org.apache.tools.ant.Target.performTasks(Target.java:334)
        at org.apache.tools.ant.Project.executeTarget(Project.java:1216)
        at org.apache.tools.ant.Project.executeTargets(Project.java:1160)
        at org.apache.tools.ant.Main.runBuild(Main.java:605)
        at org.apache.tools.ant.Main.start(Main.java:195)
        at org.apache.tools.ant.Main.main(Main.java:234)
[main] ERROR org.apache.tools.ant.Project - Build finished with error.
C:\AntBook\Sections\Extending\listeners\build.xml:3: Example build failure
        at org.apache.tools.ant.taskdefs.Exit.execute(Exit.java:90)
        at org.apache.tools.ant.Task.perform(Task.java:317)
        at org.apache.tools.ant.Target.execute(Target.java:309)
        at org.apache.tools.ant.Target.performTasks(Target.java:334)
        at org.apache.tools.ant.Project.executeTarget(Project.java:1216)
        at org.apache.tools.ant.Project.executeTargets(Project.java:1160)
        at org.apache.tools.ant.Main.runBuild(Main.java:605)
        at org.apache.tools.ant.Main.start(Main.java:195)
        at org.apache.tools.ant.Main.main(Main.java:234)
[main] ERROR org.apache.tools.ant.Project - Build finished with error.
C:\AntBook\Sections\Extending\listeners\build.xml:3: Example build failure
        at org.apache.tools.ant.taskdefs.Exit.execute(Exit.java:90)
        at org.apache.tools.ant.Task.perform(Task.java:317)
        at org.apache.tools.ant.Target.execute(Target.java:309)
        at org.apache.tools.ant.Target.performTasks(Target.java:334)
        at org.apache.tools.ant.Project.executeTarget(Project.java:1216)
        at org.apache.tools.ant.Project.executeTargets(Project.java:1160)
        at org.apache.tools.ant.Main.runBuild(Main.java:605)
        at org.apache.tools.ant.Main.start(Main.java:195)
        at org.apache.tools.ant.Main.main(Main.java:234)

上述输出由 TTCCAppender 生成,它会显示线程名称、事件优先级和事件消息,包括记录的任何异常的堆栈跟踪。当 build.log 文件达到 100KB 大小时,会生成备份文件(如 backup.log.1 backup.log.2 等),最多生成三个备份文件。

配置总结如下:
| 配置项 | 说明 |
| ---- | ---- |
| log4j.rootCategory | 根类别配置,指定日志级别和输出目标 |
| log4j.category.org.apache.tools.ant.Project | 特定类别的配置,可覆盖根类别配置 |
| log4j.appender.file | 文件输出配置,包括布局和文件大小限制 |
| log4j.appender.mail | 邮件输出配置,包括阈值、SMTP 主机等 |

1.2 编写自定义日志记录器

自定义 BuildLogger 是一个带有四个额外必需方法的 BuildListener 。这里我们开发了 MailLogger 类,用于发送完整构建日志的电子邮件。

MailLogger 类继承自 DefaultLogger ,通过重写 log 方法来捕获输出,并将其缓冲用于邮件正文。以下是 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();
    }
}

buildFinished 方法首先调用 DefaultLogger buildFinished 实现,以确保在发送邮件之前将最终输出生成到控制台或日志文件。创建一个健壮的邮件日志记录器需要几个可配置的参数,如发件人邮箱地址、收件人邮箱地址和主题。此外,还可以分别启用/禁用失败和成功消息,为失败和成功邮件设置不同的邮箱地址列表,以及根据构建的成功或失败设置不同的主题。

参数可以通过属性文件和 Ant 属性进行配置,Ant 属性优先于属性文件中指定的设置。我们可以使用特殊的项目属性 MailLogger.properties.file 来定义配置文件的位置,然后加载它并覆盖项目属性。构建的成功状态基于 BuildEvent 是否包含异常。

2. 自定义映射器开发

Ant 的一些任务支持 <mapper> 数据类型,用于将文件名映射到一个或多个相应的文件。大多数情况下,内置的映射器就足够了,但有时可能需要编写自定义映射器。

这里开发了 PackageNameMapper 类来解决单元测试结果与 Java 源文件时间戳比较的问题。Java 源文件基于包名的目录结构,而单元测试结果存储在扁平目录结构中,XML 文件名使用点分隔的包名。

以下是 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 类来继承星号( * )模式匹配功能,并重写 extractVariablePart 方法,将文件分隔符替换为点。

FileNameMapper 接口的主要方法签名为:

String[] mapFileName(String sourceFileName)

在构建文件中使用自定义映射器时,只需为 <mapper> 指定类名,并可选择指定类路径、类路径引用或嵌套的 <classpath> 元素。例如:

<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>

如果自定义映射器已经包含在 Ant 发行版中,可以直接通过名称引用:

<uptodate property="is.uptodate">
  <srcfiles dir="${some.dir}"/>
  <mapper type="package"
        from="*Test.java" to="${test.data.dir}/TEST-*Test.xml"/>
</uptodate>
3. 创建自定义选择器

Ant 的优势之一是能够在高级别上表示特定领域的需求,如 filesets 表示基于基目录的文件集合。 Patternsets 可根据文件名模式包含或排除文件,内置选择器提供了更多选择功能。

这里我们开发了 ReadOnlySelector 类,用于选择只读文件。以下是 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 方法。自定义选择器还可以使用嵌套的 <param> 标签来接受参数。例如,可以将选择器编写为通用的文件属性选择器,允许使用 <param name="attribute" value="readonly"/> (或 value="writable" )。

综上所述,通过使用 Log4j 进行日志记录、开发自定义日志记录器、映射器和选择器,我们可以根据具体需求对 Ant 进行扩展,提高构建过程的灵活性和效率。

Ant 扩展深入探究

4. 使用自定义组件的操作步骤
4.1 使用 Log4j 日志记录的操作步骤
  1. 准备依赖 :确保 commons - logging.jar log4j.jar 在 Ant 的 lib 目录中。
  2. 配置 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!                          
  1. 编写 build.xml 文件 :编写 Ant 构建文件,示例如下:
<project default="fail">
  <target name="fail">
    <fail message="Example build failure"/>
  </target>
</project>
  1. 运行 Ant 任务 :使用以下命令行运行 Ant 任务,并指定 CommonsLoggingListener
ant -listener org.apache.tools.ant.listener.CommonsLoggingListener
  1. 查看结果 :查看控制台输出和 build.log 文件,以及是否收到邮件通知。
4.2 使用 MailLogger 的操作步骤
  1. 准备配置文件 :创建 maillogger.properties 文件,示例如下:
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
  1. 编写 build.xml 文件 :编写 Ant 构建文件,示例如下:
<project name="MailLogger example" default="test">
  <target name="test">
    <echo message="hello out there"/>
  </target>
  <target name="fail"><fail/></target>
</project>
  1. 运行 Ant 任务 :使用以下命令行运行 Ant 任务,并指定 MailLogger 和配置文件:
ant -f buildmail.xml -logger org.apache.tools.ant.listener.MailLogger -DMailLogger.properties.file=maillogger.properties fail
  1. 查看结果 :查看控制台输出和是否收到邮件通知。
4.3 使用自定义映射器的操作步骤
  1. 编写自定义映射器类 :实现 FileNameMapper 接口,示例如下:
public class PackageNameMapper extends GlobPatternMapper {
    protected String extractVariablePart(String name) {
        String var = name.substring(prefixLength,
                name.length() - postfixLength);
        return var.replace(File.separatorChar, '.');
    }
}
  1. build.xml 文件中使用 :在构建文件中使用自定义映射器,示例如下:
<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>
  1. 运行 Ant 任务 :运行 Ant 任务,查看映射结果。
4.4 使用自定义选择器的操作步骤
  1. 编写自定义选择器类 :继承 BaseExtendSelector 并实现 isSelected 方法,示例如下:
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());
    }
}
  1. build.xml 文件中使用 :在构建文件中使用自定义选择器,示例如下:
<fileset dir=".">
  <customselector classname="org.example.antbook.ReadOnlySelector"/>
</fileset>
  1. 运行 Ant 任务 :运行 Ant 任务,查看选择结果。
5. 总结与展望
5.1 总结

通过以上内容,我们深入了解了 Ant 的扩展功能,包括使用 Log4j 进行日志记录、编写自定义日志记录器、开发自定义映射器和选择器。这些扩展功能可以帮助我们更好地控制构建过程,提高构建效率和灵活性。

扩展类型 实现方式 优点
日志记录 使用 CommonsLoggingListener MailLogger 灵活配置日志输出,可发送邮件通知
映射器 实现 FileNameMapper 接口 解决特定的文件名映射问题
选择器 继承 BaseExtendSelector 并实现 isSelected 方法 实现自定义的文件选择逻辑
5.2 展望

未来,我们可以进一步探索 Ant 的扩展功能,例如开发更多的自定义任务、监听器等。同时,结合其他工具和技术,如持续集成工具、自动化测试框架等,构建更加高效和稳定的软件开发流程。

以下是一个简单的 Ant 扩展开发流程图:

graph TD;
    A[需求分析] --> B[选择扩展类型];
    B --> C[编写代码];
    C --> D[测试与调试];
    D --> E[集成到项目中];
    E --> F[持续优化];

通过不断地学习和实践,我们可以更好地利用 Ant 的扩展功能,为软件开发带来更多的便利和价值。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值