56、Ant开发:动态配置、任务库构建与扩展方法

Ant开发:动态配置、任务库构建与扩展方法

1. 支持任意命名的元素和属性

1.1 Ant 1.5 之前的情况

在 Ant 1.5 之前,任务在运行时无法动态添加新的属性或元素。例如,若不修改 Ant 的源代码,就无法在 <ejbjar> 中添加新的 <condition> 测试或其他元素。对于需要支持用户定义参数的任务,通常会在构建文件中这样指定:

<paramtask>
  <param name="username" value="erik"/>
  <param name="hostname" value="localhost"/>
</paramtask>

对应的 Java 代码如下:

package org.example.antbook.tasks;
import org.apache.tools.ant.Task;
import java.util.Vector;
import java.util.Iterator;
public class ParamTask extends Task {
    private Vector params = new Vector();
    public Param createParam() {
        Param p = new Param();
        params.add(p);
        return p;
    }
    public void execute() {
        Iterator iter  = params.iterator();
        while (iter.hasNext()) {
            Param p = (Param) iter.next();
            log(p.getName() + " = " + p.getValue());
        }
    }
    public static class Param {
        private String name;
        private String value;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
}

1.2 Ant 1.5 的动态配置机制

Ant 1.5 引入了动态配置机制,允许将用户定义的参数指定为任务的属性,例如:

<dynatask username="erik" hostname="localhost"/>

通过实现 DynamicConfigurator 接口,任务可以支持新的属性和元素。示例代码如下:

package org.example.antbook.tasks;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DynamicConfigurator;
import org.apache.tools.ant.Task;
import java.util.Enumeration;
import java.util.Properties;
public class DynaTask extends Task implements DynamicConfigurator {
    private Properties params = new Properties();
    public void setDynamicAttribute(String name, String value)          
            throws BuildException {                          
        params.setProperty(name, value);                     
    }                                                        
    public Object createDynamicElement(String name)                    
            throws BuildException {                            
        throw new                                              
          BuildException("Element " + name + " is not supported");   
    }                                                          
    public void execute() {
        Enumeration enum = params.keys();
        while (enum.hasMoreElements()) {
            String name = (String) enum.nextElement();
            log(name + " = " + params.get(name));
        }
    }
}

在这个示例中, DynaTask 只支持动态属性,通过 createDynamicElement 方法抛出 BuildException 来拒绝动态元素。Ant 的内省机制会特殊处理实现了 DynamicConfigurator 接口的类,将属性名和值传递给 setDynamicAttribute 方法,将元素名传递给 createDynamicElement 方法。

1.3 未来应用展望

虽然 Ant 1.5 引入了 DynamicConfigurator 能力,但 Ant 自身的任务尚未使用该功能。未来,像 <ejbjar> <serverdeploy> 这样的容器任务可能会利用这种动态能力,实现更灵活的供应商扩展,而无需修改 Ant 的核心任务。例如,XDoclet 已经在使用 DynamicConfigurator 动态查询支持的子任务的实现细节。

2. 构建任务库

2.1 任务库的构建方法

构建可重用任务库能让构建文件编写者更方便地使用任务。将任务整合到库中很简单,只需将它们打包成 JAR 文件。同时,建议在库中包含一个属性文件,用于快速将构建文件中的任务名映射到实际的 Java 类名。示例属性文件内容如下:

simpletask=org.example.antbook.tasks.SimpleTask
message=org.example.antbook.tasks.MessageTask
message2=org.example.antbook.tasks.MessageTask2
enum=org.example.antbook.tasks.EnumTask
fileproc=org.example.antbook.tasks.FileProcTask
nested=org.example.antbook.tasks.NestedTask
run=org.example.antbook.tasks.RunTask

构建 JAR 文件的目标如下:

<target name="jar" depends="compile">
  <copy file="${meta.dir}/taskdef.properties"
        todir="${build.dir}"/>
  <jar destfile="${dist.dir}/tasks.jar"
       basedir="${build.dir}"/>
</target>

2.2 使用任务库

构建文件可以轻松使用任务库,而无需知道每个任务的类名。示例构建文件如下:

<?xml version="1.0" ?>
<project name="library" default="main">
  <property name="tasks.jar" location="dist/tasks.jar"/>
  <taskdef resource="taskdef.properties" classpath="${tasks.jar}"/>      
  <target name="usetasks">
    <simpletask/>
    <property name="the.message" value="blue scooter"/>
    <message message="${the.message}"/>
    <property name="another.message" value="light up ahead"/>
    <message2>${another.message}</message2>
    <enum version="2.3"/>
    <fileproc dir="${basedir}">
      <include name="**/*.java"/>
    </fileproc>
    <nested>
      <fileset dir="images">
        <include name="**/*.gif"/>
      </fileset>
      <fileset refid="non.java.files"/>
    </nested>

    <run flag="off">
      <fileset dir="." excludes="**/*.class"/>
    </run>
  </target>

  <target name="main" depends="usetasks"/>
</project>

在这个示例中,只使用了一个 <taskdef> 就定义了 taskdef.properties 中的所有任务,并在 usetasks 目标中使用了其中的几个任务。

3. 支持多个版本的 Ant

为早期版本的 Ant 编写的任务通常可以在未来的 Ant 1.x 版本(甚至可能是 Ant 2.0)中正常工作,但反之则不一定。Ant 的 API 会不断发展,新版本的特性在早期版本中可能不被支持。为了编写能在多个版本的 Ant 中工作的任务,例如 Ant 1.4.1 和 Ant 1.5,必须避免使用较新的 API 功能,如 Project.setNewProperty DynamicConfigurator 。确保兼容性的最佳方法是使用要支持的最旧版本的 Ant 来编译任务,并在每个版本上进行测试。

4. Ant 的其他扩展方式

4.1 脚本编写

4.1.1 <script> 任务概述

<script> 任务支持多种脚本语言命令,可用于调用任务和操作其他 Ant 对象。脚本可以用 JavaScript(具体是 Mozilla 的 Rhino 实现)或其他多种语言编写,是在构建文件内部扩展 Ant 的简单方法,无需分发额外的库或添加新的源文件。

4.1.2 脚本任务的基础框架

<script> 任务基于 IBM 的 Bean Scripting Framework,需要下载 bsf.jar 并放入 ANT_HOME/lib 目录。同时,还需要提供所使用脚本语言的实现。例如,Mozilla Rhino 版本的 JavaScript 是常用选择,Python 等也是可行的。这些库的下载 URL 在 Ant 文档的库依赖部分有列出。

4.1.3 示例:生成随机数并赋值给属性
<project name="script_example" default="test-random">
  <description>
    Use a script task to generate a random number, then
    print it
  </description>
  <target name="random">
    <script language="javascript"><![CDATA[
      //NB: an unqualified Math is the JavaScript object
      var r=java.lang.Math.random();
      var num = Math.round(r*10);
      project.setNewProperty("random", num);
      self.log("Generated random number " + num, project.MSG_DEBUG);
    ]]>
    </script>
  </target>
  <target name="test-random" depends="random">
    <echo>Random number is ${random}</echo>
  </target>
</project>

运行 test-random 目标会打印出 random 属性的值,该值在 0 到 10 之间(包含 0 和 10)。

4.1.4 隐式对象

<script> 任务为脚本上下文提供了两个固定名称的隐式对象: project self project 对象是 org.apache.tools.ant.Project 实例的引用,可用于通过 Project 对象的 API 设置和访问属性; self 对象是 org.apache.tools.ant.taskdefs.optional.Script 实例的引用,可用于使用 Task 提供的日志方法记录消息。

除了 self project <script> 任务还会将 Ant 属性、目标和引用提供给脚本上下文。属性和目标通过名称提供,引用通过其 id 或名称提供。引用包括数据类型、任务和项目本身。需要注意的是,名称不是有效 Java 标识符的隐式对象会被忽略,不会放入脚本上下文。示例如下:

<?xml version="1.0" ?>
<project name="script_context" default="main">
  <property name="legalName" value="accessible"/>
  <property name="illegal name" value="inaccessible?"/>
  <target name="main">
    <script language="javascript"><![CDATA[
      self.log("legalName = " + legalName);
      self.log("illegal name = " + project.getProperty("illegal name"));
      self.log("test = " + test);
      self.log("echo_task = " + echo_task);
      self.log("script_context = " + script_context);
      self.log("project = " + project);
      echo_task.setMessage("invoked via <script>");
      echo_task.execute();
    ]]>
    </script>
  </target>
  <target name="test">
    <echo id="echo_task"/>
  </target>
</project>

输出结果如下:

main:
   [script] legalName = accessible
   [script] illegal name = inaccessible?
   [script] test = test
   [script] echo_task = org.apache.tools.ant.taskdefs.Echo@7a8a02
   [script] script_context = org.apache.tools.ant.Project@7ebe1
   [script] project = org.apache.tools.ant.Project@7ebe1
     [echo] invoked via <script>
4.1.5 脚本编写总结

脚本框架功能强大,可以通过完整的包名使用任何 Java 类,通过名称或 id 值引用构建文件中的对象。但通常不建议在 Ant 文件中大量使用 <script> 任务,因为它会使构建文件变得复杂,限制代码的复用性。如果项目确实需要使用脚本任务,最好将脚本提取到单独的文件中,使用 <script file="random.js"> 选项直接引用文件,这样可以将脚本与构建文件分离,便于复用。此外,脚本框架在出现问题时可能无法提供详细的错误信息,例如在将浮点数随机数转换为整数时使用 (int) 进行强制类型转换可能会导致错误。

4.2 监听器和记录器

4.2.1 监听器和记录器概述

Ant 提供了在执行过程中监控其进度的功能,涉及两个紧密相关的概念:监听器和记录器。为了开发自定义监听器和记录器,需要了解 Ant 使用的底层架构。

4.2.2 BuildListener 接口

附加的 BuildListener 会在构建生命周期中的七个事件发生时收到通知,这些事件包括:构建开始/结束、目标开始/结束、任务开始/结束以及消息记录。任何数量的构建监听器都可以附加到项目上,Ant 内部也会附加一些监听器来通知自己事件的发生,特别是构建结束事件,用于清理操作。每个事件都会传递一个 BuildEvent 对象,该对象根据触发的事件封装了字符串消息、项目引用、目标引用或任务引用。具体信息如下表所示:
| BuildListener 方法 | BuildEvent 内容 |
| — | — |
| buildStarted 和 buildFinished | 通过 getProject() 获取项目,通过 getException() 获取异常 |
| targetStarted 和 targetFinished | 通过 getTarget() 获取目标,通过 getException() 获取异常,也可访问项目对象 |
| taskStarted 和 taskFinished | 通过 getTask() 获取任务,通过 getException() 获取异常,也可访问项目和目标对象 |
| messageLogged | 通过 getMessage() 获取消息,通过 getPriority() 获取消息优先级,根据消息记录的位置,还可使用 getTask getTarget getProject |

使用监听器时,需要在命令行使用 -listener 开关指定,例如:

ant -listener org.apache.tools.ant.listener.CommonsLoggingListener
4.2.3 BuildLogger 接口

BuildLogger 接口在其父接口 BuildListener 的基础上,增加了对输出和错误打印流的访问。此外,还提供了设置 emacs 模式和消息输出级别的方法。 DefaultLogger 使用 emacs 模式为 IDE 集成提供格式化输出,因为大多数 IDE 可以解析 emacs 表示的文件错误位置。消息输出级别用于根据日志级别过滤输出。

一个 Ant 项目只能有一个关联的 BuildLogger ,因为记录器可以访问错误和输出打印流,所以只附加一个记录器是合理的。可以通过命令行的 -logger 开关指定 BuildLogger ,使用 -emacs 启用 emacs 模式, -quiet -verbose -debug 开关指定日志级别。默认的输出级别是信息级别,介于安静和详细选项之间。 BuildLogger 会作为监听器内部附加到项目上,因此它会像其他附加的监听器一样接收事件。

需要注意的是,Ant 在记录器或监听器介入之前会有一些控制台输出,这些输出无法通过它们捕获。如果在项目范围之外发生错误(例如缺少 build.xml ),这些输出只能在控制台或日志文件中看到。

4.2.4 编写自定义监听器

以下是一个自定义监听器的示例,它是 Jakarta Commons Logging API 的包装器,可通过多种流行的日志记录 API 进行自定义日志记录:

package org.apache.tools.ant.listener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogConfigurationException;
import org.apache.commons.logging.LogFactory;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildListener;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;
/**
 * Jakarta Commons Logging listener.
 * Note: do not use the SimpleLog as your logger implementation as it
 * causes an infinite loop since it writes to System.err, which Ant traps
 * and reroutes to the logger/listener layer.
 *
 * @author Erik Hatcher
 * @since Ant 1.5
 */
public class CommonsLoggingListener implements BuildListener {
    private boolean initialized = false;
    private LogFactory logFactory;
    public CommonsLoggingListener() {
        try {
            logFactory = LogFactory.getFactory();
        } catch (LogConfigurationException e) {
            e.printStackTrace(System.err);
            return;
        }
        initialized = true;
    }
    public void buildStarted(BuildEvent event) {
        if (initialized) {
            Log log = logFactory.getInstance(Project.class);
            log.info("Build started.");
        }
    }
    public void buildFinished(BuildEvent event) {
        if (initialized) {
            Log log = logFactory.getInstance(Project.class);
            if (event.getException() == null) {
                log.info("Build finished.");
            } else {
                log.error("Build finished with error.", event.getException());
            }
        }
    }
    public void targetStarted(BuildEvent event) {
        if (initialized) {
            Log log = logFactory.getInstance(Target.class);
            log.info("Target \"" + event.getTarget().getName() + "\" started.");
        }
    }
    public void targetFinished(BuildEvent event) {
        if (initialized) {
            String targetName = event.getTarget().getName();
            Log log = logFactory.getInstance(Target.class);
            if (event.getException() == null) {
                log.info("Target \"" + targetName + "\" finished.");
            } else {
                log.error("Target \"" + targetName + "\" finished with error.", event.getException());
            }
        }
    }
    public void taskStarted(BuildEvent event) {
        if (initialized) {
            Task task = event.getTask();
            Log log = logFactory.getInstance(task.getClass().getName());
            log.info("Task \"" + task.getTaskName() + "\" started.");
        }
    }
    public void taskFinished(BuildEvent event) {
        if (initialized) {
            Task task = event.getTask();
            Log log = logFactory.getInstance(task.getClass().getName());
            if (event.getException() == null) {
                log.info("Task \"" + task.getTaskName() + "\" finished.");
            } else {
                log.error("Task \"" + task.getTaskName() + "\" finished with error.", event.getException());
            }
        }
    }
    public void messageLogged(BuildEvent event) {
        if (initialized) {
            Object categoryObject = event.getTask();
            if (categoryObject == null) {
                categoryObject = event.getTarget();
                if (categoryObject == null) {
                    categoryObject = event.getProject();
                }
            }
            Log log = logFactory.getInstance(categoryObject.getClass().getName());
            switch (event.getPriority()) {
                case Project.MSG_ERR:
                    log.error(event.getMessage());
                    break;
                case Project.MSG_WARN:
                    log.warn(event.getMessage());
                    break;
                case Project.MSG_INFO:
                    log.info(event.getMessage());
                    break;
                case Project.MSG_VERBOSE:
                    // 原文档此处未完成,可根据实际情况补充
                    break;
            }
        }
    }
}

4.3 其他扩展方式

除了脚本编写、监听器和记录器,Ant 还支持以下扩展方式:
- 自定义 <mapper> 实现
- 使用专门的选择器进行强大的文件集过滤
- 使用过滤器转换文本流

了解这些扩展方式在遇到继承的构建或需要特定功能时会很有用。

综上所述,Ant 提供了丰富的扩展机制,包括动态配置、任务库构建、脚本编写、监听器和记录器等,开发者可以根据具体需求选择合适的扩展方式,以实现更灵活、高效的项目构建。

5. 自定义映射器开发

5.1 自定义映射器概述

自定义 <mapper> 实现允许开发者根据特定需求对文件进行映射转换。在 Ant 构建过程中,可能需要对文件的名称、路径等进行修改,自定义映射器可以满足这些个性化的需求。

5.2 开发步骤

虽然文档未给出具体示例代码,但开发自定义映射器的一般步骤如下:
1. 定义映射逻辑 :确定需要对文件进行何种映射转换,例如修改文件名的前缀、后缀,改变文件路径等。
2. 实现映射接口 :创建一个类实现 Ant 提供的映射接口,并重写相应的映射方法。
3. 在构建文件中使用 :将自定义映射器应用到需要的任务中,如 <copy> <move> 等任务。

以下是一个简单的流程说明:

graph LR
    A[定义映射逻辑] --> B[实现映射接口]
    B --> C[在构建文件中使用]

6. 创建自定义选择器

6.1 自定义选择器的作用

自定义选择器用于对文件集进行强大的过滤。在 Ant 构建中,可能需要根据特定的条件筛选文件,例如文件的修改时间、文件大小、文件类型等,自定义选择器可以实现这些复杂的筛选逻辑。

6.2 开发步骤

创建自定义选择器的步骤如下:
1. 确定筛选条件 :明确需要根据哪些条件筛选文件。
2. 实现选择器接口 :创建一个类实现 Ant 提供的选择器接口,并重写选择方法,根据筛选条件判断文件是否符合要求。
3. 在构建文件中使用 :将自定义选择器应用到 <fileset> 中,实现文件的筛选。

示例代码虽然未在文档中给出,但以下是一个简单的流程说明:

graph LR
    A[确定筛选条件] --> B[实现选择器接口]
    B --> C[在构建文件中使用]

7. 实现自定义过滤器

7.1 自定义过滤器的功能

自定义过滤器用于转换文本流。在 Ant 构建过程中,可能需要对文件的内容进行修改,例如替换某些字符串、添加特定的文本等,自定义过滤器可以实现这些文本转换功能。

7.2 开发步骤

实现自定义过滤器的步骤如下:
1. 确定转换规则 :明确需要对文本进行何种转换,例如替换字符串、添加前缀后缀等。
2. 实现过滤器接口 :创建一个类实现 Ant 提供的过滤器接口,并重写过滤方法,根据转换规则对文本进行处理。
3. 在构建文件中使用 :将自定义过滤器应用到需要的任务中,如 <copy> <move> 等任务。

以下是一个简单的流程说明:

graph LR
    A[确定转换规则] --> B[实现过滤器接口]
    B --> C[在构建文件中使用]

8. 总结

Ant 具有丰富的可扩展性,为开发者提供了多种扩展和定制的方式:
- 脚本编写 :通过 <script> 任务支持多种脚本语言,可在构建文件内部扩展 Ant,但使用时需注意代码的复用性和错误处理。
- 监听器和记录器 :可监控和记录 Ant 的构建过程,开发者可以开发自定义监听器和记录器满足特定的监控和日志需求。
- 自定义映射器 :允许根据特定需求对文件进行映射转换。
- 自定义选择器 :实现强大的文件集过滤功能,根据复杂条件筛选文件。
- 自定义过滤器 :用于转换文本流,对文件内容进行修改。

开发者可以根据具体的项目需求,灵活运用这些扩展方式,实现更高效、灵活的项目构建。同时,在使用过程中要注意遵循 Ant 的规范和最佳实践,确保构建的稳定性和可维护性。

以下是 Ant 扩展方式的总结表格:
| 扩展方式 | 作用 | 使用场景 |
| — | — | — |
| 脚本编写 | 在构建文件内部扩展 Ant,调用任务和操作对象 | 临时需求、快速实现功能 |
| 监听器和记录器 | 监控和记录构建过程 | 调试、记录构建信息 |
| 自定义映射器 | 对文件进行映射转换 | 修改文件名、路径等 |
| 自定义选择器 | 筛选文件集 | 根据复杂条件筛选文件 |
| 自定义过滤器 | 转换文本流 | 修改文件内容 |

通过合理利用这些扩展方式,开发者可以充分发挥 Ant 的优势,提高项目构建的效率和质量。

内容概要:本文介绍了一个基于MATLAB实现的无人机三维路径规划项目,采用蚁群算法(ACO)多层感知机(MLP)相结合的混合模型(ACO-MLP)。该模型通过三维环境离散化建模,利用ACO进行全局路径搜索,并引入MLP对环境特征进行自适应学习启发因子优化,实现路径的动态调整多目标优化。项目解决了高维空间建模、动态障碍规避、局部最优陷阱、算法实时性及多目标权衡等关键技术难题,结合并行计算参数自适应机制,提升了路径规划的智能性、安全性和工程适用性。文中提供了详细的模型架构、核心算法流程及MATLAB代码示例,涵盖空间建模、信息素更新、MLP训练融合优化等关键步骤。; 适合人群:具备一定MATLAB编程基础,熟悉智能优化算法神经网络的高校学生、科研人员及从事无人机路径规划相关工作的工程师;适合从事智能无人系统、自动驾驶、机器人导航等领域的研究人员; 使用场景及目标:①应用于复杂三维环境下的无人机路径规划,如城市物流、灾害救援、军事侦察等场景;②实现飞行安全、能耗优化、路径平滑实时避障等多目标协同优化;③为智能无人系统的自主决策环境适应能力提供算法支持; 阅读建议:此资源结合理论模型MATLAB实践,建议读者在理解ACOMLP基本原理的基础上,结合代码示例进行仿真调试,重点关注ACO-MLP融合机制、多目标优化函数设计及参数自适应策略的实现,以深入掌握混合智能算法在工程中的应用方法
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值