Ant权威指南部分翻译

类别:软件工程 点击:0 评论:0 推荐:

翻译Ant权威指南的第5,6章.

第五章 用户自定义任务(Task)
 通过用户定制化来扩展(extend)Ant的概念已经并将一直成为Ant最重要的和被其大力声称的特征.
Ant的建造者们给我们提供了一个足够健壮(robust enough)的系统来和当今可用的语言,工具协同工作,
并具备兼容未来出现的语言和工具的能力.举个例子,当Ant在2000年早些时候出现的时候,
并没有适合和C#配合的Ant任务(Task)存在,而现在却有了.大量的用户已经开发出大量的Ant任务(Task)
来配合各种第三方工具(third-party tools)的使用,从组件(groupware)产品如StarTeam(一项控制系统),
到应用服务器如Bea的Weblogic或是JBoss组织(JBoss Group)的JBoss.
这些改变和改进的发生对于Ant的核心处理引擎(core processing engine)并没有什么变化.
扩展Ant而不改变他的核心引擎是非常重要的,因为那意味着Ant核心引擎能通过扩展的开发(extension development)
被分离的改善和改进.这样,在两个领域可以并行开发,结果是改变的速度会比Ant作为一个独立封闭(monolithic)的系统快的多.

 所有的任务都是Java类(Java class),并且任何一个程序员都可以通过写一个新的Java任务类来扩展Ant的功能.
这些就是用户自写任务(User-Written Task),并且利用了随Ant一起发布的核心任务的相同接口(These are user-written tasks,
and take advantage of the same interface to Ant used by the core tasks shipped with an Ant distribution).
用户自写任务和核心任务的唯一的区别只是他们的作者(author)和包位置(package location)(有时候这也是相同的!).
另外,他们在系统的同一个水平位置发挥作用(function on the same level playing field).
这一章,我们给你展示一下如何通过写你自己的任务来扩展Ant.

5.1 用户自制任务的要求.
 Ant有两个任务,java和exec,他们能够执行任何Java类或系统中可执行命令行程序(command-line executable).
这种能力可能会使你怀疑定制任务还是否需要.技术上讲,你能使用这两个任务执行任何类会运行任何程序.
结果变得明显,某些自定义任务事实上并没有发明什么只不过是一个可执行的程序包装器(excution wrapper),
他运行Java类或程序就像java和exec任务执行的方式一模一样.差别是用户定制类可以和Ant引擎更紧密的工作.
定制类能够提供更加详细的(detailed)信息,处理错误也更加精确.
另外一方面,java和exec任务在处理未可预知的错误的能力,给用户产生详尽的通知的能力是有限的.
不管某个事件或错误的性质如何,对于他们处理都是一样,给你的控制权都非常有限.
 一个定制任务,多数情况下,比起直接用Java和exec来执行都是一个扩展Ant来解决某个问题的更好的解决方法.
工作过程(build)中的错误(errors),事件(events)和消息(messages)会被任务初始化并被Ant引擎管理着.
Ant对事件作出反应并在一种用户可控制的行为下进行,把他们发送(propagate)到他自己监听器(listeners),
或用户自定义的监听器上(更多用户自定义监听器参见第六章).
对任务如此细致(fine-grained)的管理对终端用户(end users)来说再好不过了
(软件开发者需要更多信息关于他们的工程工作过程是如何进行的).
同样对于其他用户编写新任务来扩展现有任务也有益处,继承他们的工作能力又通过一系列相关操作产生一致的行为.
单单这些特征就可以使得定制任务成为一件好事.更何况,这些定制任务还有更多的用途.
 任务善于抽象简单操作并使他们通过统一的接口加上额外的功能更加强大.
一些Ant任务甚至具备处理由于不同平台而产生的普遍使用的命令行功能间的差异的能力.
举个例子,不同平台上,不同Shell间的拷贝(copying)和删除(deleting)文件以及目录由于命令名和命令行参数的的改变
而变得非常痛苦.而通过使用任务来抽象文件操作,Ant消除了这个痛苦,并给用户提供了一个统一的接口.
在Ant中,只有一种方式来拷贝和删除一个文件,并且他不管Ant运行在哪个平台上都能一致的工作.
这并不是抽象(Abstracting)带来的唯一好处.没有了命令行工具的特征集(feature set)的限制,
一个抽象的任务可以给你提供一个提升的特征集.
一个Windows下的del命令不能实现删除所有的以.java结束的文件同时保留所有以Abstract开头的文件.
而Ant下的delete任务却能够做到,这表明了比起命令行来,他具备更大的灵活性.更好的是,他能在任何平台上完成此事.
任务设计关心的是工作需要,从不会限制他本身于工具的特征内,因为工具的设计注重于命令环境(shell)和系统需要.
 使用可用定制任务类中的强大功能,你可以改善几乎任何一项工具的使用.不要把定制任务想象成Ant缺陷的有益补充(Band-Aid).
Ant和他的任务模型更像"乐高玩具".增加Ant任务可以增加并加强Ant的属性集,但并不会是他臃肿.
Ant自始至终都保持着模块化和可扩展性.
5.2 Ant任务模型(Task Model)
 理解定制任务,首先应该理解任务模型.Ant,作为一个以Java为基础的程序,
使用了Java类的层次结构(hierarchy)和反射能力(reflection capabilities)来完成工作.
所有的Ant任务直接或间接的继承自抽象类org.apache.tools.ant.Task. 
Ant引擎在这一个层面上管理所有的任务,因为他仅仅操作Task对象.
对于这个引擎,每一个任务都和其他任何任务一样,继承自这同一个类并且拥有相同的核心方法与属性.
XML解析和方法名设计的绑定使得Ant可以使用Task所有的子类.
另外,Ant以一种固定的方式处理所有任务--也就是说,Ant在同一个循环中执行所有任务.
当然编写简单的任务并不需要理解这个模型和处理细节,但是对于复杂任务可能会表现出不可预期的行为(exhibit undesirable behaviors).
-----------------------------------------------------------------------------------------
-    编写定制数据类型     -
- 除了任务之外,Ant模型还要处理数据类型(DataType).数据类型的一个例子是path任务. -
-path任务没有处理直接的行为.        -
-相反,他建造了一个数据集,数据来源于(based on)XML中给予的规则和其他信息.   -
-作为Ant1.4,用户可以技术性的拥有定制用户数据类型的能力.     -
-但是,声明数据类型的方法(typedef任务)是一个bug,且不能工作.    -
-Ant Release1.5版本中解决了这个问题.       -
-----------------------------------------------------------------------------------------

5.2.1 任务的组成部分
 一个任务包括两面.一面面向Ant的终端用户,这时任务仅仅是一个工作文件(buildfile)中的XML,别无他物.
为这一面你需要解析(dissect)这个XML并识别出该任务的各个部分.对于任务的编程人员,任务却变得不一样.
虽然XML还存在,但是却仅仅作为要编写的Java的代码的指南.可Java代码仅仅是冰山一角.
技术上来说,对于一个任务还有许多层面.

5.2.1.1 公用超类
 对所有任务类,必须继承自超类(某种意义上说,就是Task).
Ant引擎严格对Task对象操作,忽视任何开发者附加在子类上的任何东西.但是,这并不意味着你可以忽略Task的类层次结构.
理解这个结构可以帮助你尽量摆脱他对你工作的阻力(ignoring it hampers your effort).
Task的子类不仅要表现工作文件中的任务,还要表现其他任务中的类中有用的功能.有时候,一个子类甚至不是一个任务.
例如,如果你的任务需要用到文件集合(file sets)和模式(patterns),你应该继承 org.apache.tools.ant.main.taskdef.MathchingTask.
这个类实现大部分文件集合和模式的操作,减除你自己来实现要付出的乏味的努力.
这使得你能够得到利用巨人般的功能强大的好处,就像利用这个任务和其他任务类.
(it does you good to stand on shoulders of powerful giants such as this and other task classes).
 
 你应该知道一些和你需求相似的任务设计.Ant中一个很好高效率重用的例子就是zip家族的任务.JARS扩展了zip的打包模型(zip-packaging model),
jar任务继承自zip,吸取(borrowing)了大部分zip的功能而仅仅实现Jar相关特征的操作.更进一步,WAR文件是一个拥有标准目录结构和一个附加的必备文件(部署描述文件web.xml)的JAR文件.
因此,war任务继承自jar.在war的例子中,保留了继承来的每一点功能,只是实现了创建标准目录结构和检验War中描述文件的合格性的功能.
在这章的末尾,我们将分析jar任务和他的层次结构作为定制任务的一个例子.
5.2.1.2 属性(Attributes)
 属性是名-值对形式描述的特殊XML标签.编程角度来说,Ant从XML中解析并加载属性的名值对,并把他们传递给各个任务对象.
Ant会把这些字符串重定义为原始数据类型对象,文件对象或者类对象.一个典型的应用,属性值表示布尔类型,扮演任务处理过程的标志.
例如,任务javac中的属性debug就是一个布尔类型,这个值为"on"时,javac编译类时会给出debug信息.为"off",则一般的编译.
5.2.1.3 内嵌元素(Nested Elements)
 内嵌元素或多或少都和属性有着相互的替代的能力.他们可以是任务或是任务或是数据类型.作为属性,任务可以很明确的处理他们的内嵌元素.
不幸的是,处理内嵌元素并没有处理名-值对那么简单.
 内嵌元素的复杂度可能变得非常令人迷惑,因为没有一种确定的模型使你能够设计你使用的内嵌元素.
理论上,你定制的任务可以把任何一个既有的任务作为他的内嵌元素.例如,你可以把javac看作一个内嵌元素.
但是,这样一个内嵌元素只有当你明确的处理javac相关类Javac的使用时才能正常工作.
你必须知道并处理javac实现时候的所有制约条件,弊端.不是那么简单(no small feat).
就算你这么作了,javac也可能执行的操作使得你不可能利用他作为一个内嵌元素.这是因为没有一种标准的方法来执行这些任务.
即使没有任何东西可以阻止你编程使用象javac一样的任务作为内嵌元素,你也将发现当整个工作中止时他并不不可用.
 任务使用内省的(introspective)调用来处理内嵌元素,就像处理属性一样.
区别在于内嵌元素有相应的类包含数据和功能在他里面.而属性只有名-值对.
一个元素需要他的类被实例化,他自己的属性被解析被处理,并且他的主要的功能将被执行.在这个过程中,错误随时会发生.
 通过对比和对照一个任务对他属性的使用和对内嵌元素的使用,可以证明属性和内嵌元素的差别.考虑copy任务:
 
 <copy destdir="newdir/subdir">
  <fileset dir="olddir">
   <include name="**/*.java"/>
  </fileset>
 </copy>

copy任务带有属性destdir和内嵌元素<fileset>.copy任务处理destdir很简单.Ant给任务类传递一个文件对象指向该目录.
一次调用,这个属性就被设定.对比一下Ant是如何处理<fileset>元素的.有三种方式可以让Ant把改元素传递给任务类.
在每一中方法中,Ant必须保存fileset数据类型贯穿整个任务的生命过程.
(因此,在这个层次上,数据类型和任务对于Ant引擎来说都是唯一的).Ant对这些任务和数据类型的处理是一个递归的过程.
我们试图阐明的关键点是:Ant对数据类型的处理大都包含在对元素属性的处理过程中.
 属性虽然比数据类型更易使用和理解,但是可读性和灵活性却不够.例如,Paths变成了难看有难维护的属性.
Path的值可能很长,并且如果path结构变化的话要每次都随着改变.内嵌的path元素就更可读且更易维护了.
他们由于能使用复杂的文件模式串来表现路径而显然更强大.(例如,*.*可以在path数据类型中工作,在path作为属性时却不能).
 象生活中的每一件事,决定在实现属性还是内嵌元素之间有一个衡量的问题.
虽然我们在使用数据类型的时候得到了易维护性和易读性,比起使用属性却丢失了初始化开发阶段.有许多方法使用内嵌元素(准确的说是三种调用方式),
并且每一种方式都可能导致错误或是难以调试的异常行为(odd behavior).因为这个缘故,一些任务的作者提供同时支持两者的方法,
例如有classpath属性又有classpath内嵌类型.
 记住这点可能使用户的迷糊,因此清分别给予文档化.你需要明确定义如果用户同时定义相同值的属性和内嵌数据类型时该如何处理.
Ant并不知道如何区别他们的不同并在未定义的顺序下操作两者.

5.2.2 Ant和任务间的通信
 既然你已经理解了一个任务所包含的各个部分,我们现在转而注意Ant工作引擎和任务间的通信机制.
编写定制任务时,你需要理解三种通信机制.

Project类
 project是在每个任务中都可以访问的公共实例.这个类表现了整个工作文件和在那包含的一切事物.
通过他,你可以访问所有的任务(tasks),目标(targets),属性(properties)和工作文件的其他部分.

工作异常(Build Exceptions)
 工作异常,通过BuildException类来实现,为任务触发错误条件给Ant工作引擎提供一种途径.
日志系统(The logging system)
 日志系统,通过project类可以访问,提供给任务一种方式来展示过程的信息给用户查看.
以下三节分别细致的描述每一种机制.

5.2.2 Ant和任务间的通信
 既然你已经理解了一个任务所包含的各个部分,我们现在转而注意Ant工作引擎和任务间的通信机制.
编写定制任务时,你需要理解三种通信机制.

Project类
 project是在每个任务中都可以访问的公共实例.这个类表现了整个工作文件和在那包含的一切事物.
通过他,你可以访问所有的任务(tasks),目标(targets),属性(properties)和工作文件的其他部分.

工作异常(Build Exceptions)
 工作异常,通过BuildException类来实现,为任务触发错误条件给Ant工作引擎提供一种途径.
日志系统(The logging system)
 日志系统,通过project类可以访问,提供给任务一种方式来展示过程的信息给用户查看.
以下三节分别细致的描述每一种机制.
5.2.2.1 Project类
 一个提供任务和Ant引擎之间通信的最大※便利的类:Project类.由于父类Task中包含一个Project类的实例,所以使得这种通信成为可能.
可以在任何任务中象使用任何变量一样使用他.Project类提供了许多强大的功能,请关注他都能作些什么,但也请注意你意外的滥用这种力量的地方.
(你不会有意的滥用,是吗?).同样,请注意你能用Project所作的聪明的事情在下一个发行的Ant中可能不被支持.
请保留一个可选的设计计划或者准备维护你自己的Ant版本.
 Project类表示了整个工作文件.这个类允许你访问工作文件中的每一个任务,目标,属性,甚至一些定义工作文件如何执行的核心设置.
开发者很少使用这种访问便利,即便使用他的功能和能力都已经具备了.首先,任务开发者使用Project来提供通过log方法的调用来访问引擎的核心审核系统(auditing system).
 另外,Project为所有任务定义系统范围内的常量和全程方法.这些常量可以作为系统呼叫的参数,比如logging的参数.
全局方法提供各色功能从转换路径为本地化形式到提供布尔转换器来转换任务的布尔属性值.
 在一个任务内,Project类的字段名相当明显,就是project.以下是任务中一些公共的方法调用和一些可用的常量:

project.getGlobalFilterSet()
 返回一个关于本次工作的全局FilterSet对象.可以定义一个全局的过滤集合,对每个任务排除或包含一系列文件使得可以在上面进行文件和目录操作.
如果你的任务需要遵守这个全局的过滤器,你可以通过调用project.getGlobalFilterSet()得到.更多关于FileterSet的信息请查看Ant API JavaDoc

project.getBaseDir()
 返回<project>节点中的basedir属性值.如果你的任务需要从工程目录下进行文件操作,这是一种获得该目录路径的最好的方式.
project.translatePath()
 把路径转换成当前操作系统的本地化路径格式.工作文件的作者可能就是一般的写文件路径和文件的名,而忽略诸如目录分割字符的不同点.
当你的任务需要在一个实际的文件上进行操作,你需要本地格式文件和目录串从而避免错误.Project类中的translatePath()方法把原始的路径转换成符合当前操作系统的路径.
Project类能识别当前的操作平台,并且把文件名和目录转换成正确的形式.例如:
 File f = new File(dir,project.translatePath(filePath));
这个例子展示了如何产生一个文件.这个任务建立一个文件,并不需要任何进行平台识别的代码来产生一个有效的路径(如:Windows或Unix).
相反,任务的编程者调用translatePath(),因为知道不管什么平台下,他都能在JVM下正常工作.
project.toBoolean()
 检验一个布尔值.带有布尔属性的任务携带着诸如yes|no,true|false,on|off的值.通过toBoolean()函数可以解析.
这样消除了重写简单的从字符串值到布尔值转换的需要,也不用花费额外的精力为所有任务提供一致的接口.
所有带有标志类的属性的任务能使用三种布尔值的组合.例如,project.toBoolean("yes")和project.toBoolean("on")都返回true.

除了本节中展示的使用project类从工作引擎中获得信息之外,你还可以使用他把信息传递给工作引擎.但是这种应用通常是破坏性的,使用他比较危险.
Project类保存了许多工作引擎操作的设置值,这就意味着你可以在合适的地方做点改变.但仅仅是及其特别的例子,否则最好还是什么都别作.
我们提到这种能力仅仅是让我们的知识更加全面,而不是推荐你去实现时运用他.与工作引擎通信的最安全的最好方式是使用工作异常和日志信息.
这是因为一个任务要做出的通信类型是那些通知信息型的,而不是任何可能造成破坏性的东西.这就意味着如果错误发生了,应该提供状态消息作为运行时反馈或者安全稳妥的失败.

5.2.2.2 工作异常
 工作异常通过BuildException类抛出,并为任务提供一种机制去发送错误情形给Ant工作引擎.在一个任务中的任何地方你都可以抛出BuildException.
引擎对于他加在某个任务上的方法调用都预期有BuildException发生.看这个例子,他展示了一个BuildException的抛出过程:
 if(!manifestFile.exists()){
  throw new BuildException("Manifest file: "+manifestFile+"does not exist.",getLocation());
 }
如果在任务试图使用指定的特定的文件不存在的时候,任务进入错误状态并宣告失败.他通知Ant引擎此次失败通过抛出一个包含错误消息和一个位置(Location)对象(使用getLocation()方法来提取)的BuildException.
Location类包含工作文件的名称和引擎当前正在解释的行号.在某种程度上,他同样是一个类似于Project的类通过他任务可以从引擎那得到通信.但是大部分开发者限制使用来自Location类的信息去构建BuildException中的消息.
 抛出一个BuildException会马上中止这个任务.只有所有的任务成功,包含这些任务的目标才算成功.有了BuildException,Ant就知道什么时候失败一个任务,他的目标,以及整项工程.
 对于这样一个规则:只有所有任务成功一个目标才算成功.一个异常通常是任务中的failOnError的属性偶然使用而出现.任务使用这个属性能避免抛出一个BuildException,因此工作能继续进行.当然,没有什么能象这样的自动化,
并且你作为任务作者,对实现这项特征负有责任.以下是从Cvs类中抽取的部分代码表现如何实现failOnError.
XML:
 <cvs failOnError="true"
 cvsroot=":pserver:[email protected]:/usr/phpwiki"
 dest="${src.dir}"/>
实现:(从Cvs.java源码中摘录(excerpt)出来)
 /**
 * The task's instance variable, representing the failOnError flag
 * If true it will stop the build if cvs exits with error.
 * Default is false.
 */
 private boolean failOnError = false;
 ...
 // Sets the instance variable through the attribute
 public void setFailOnError(boolean failOnError) {
  this.failOnError = failOnError;
 }
 // some method code, blah blah blah
 // Throw a build exception from this method, only
 // if the task is supposed to fail
 public void execute( ) throws BuildException {
  // more code...
  // Handle an error, but only throw an exception when
  // failOnError is true
  if(failOnError && retCode != 0) {
   throw new BuildException("cvs exited with error code "+ retCode);
  }
  // more code...
 }

 简单而言,如果failOnError属性值为false,Cvs类将不会抛出BuildException并为包含该任务的目标构建一个错误状态.
另外,这样有个益处是,不是什么都不作,错误的条件至少可以产生一些日志信息使得终端用户知道什么东西除了问题.
举个例子,更好的实现:
 //some method code,blah blah blah
 //Throw a build exception only if the task is supposed to fail
 if(failOnError && retCode != 0){
  throw new BuildException("cvs exited with error code "+retCode);
 }
 if(!failOnError && retCode != 0){
  log("cvs existed with error code "+retCode);
 }

5.2.2.3 日志系统
 Project类允许任务获得系统范围的关于工作文件的信息.他同样提供方法访问工作引擎的审核系统.这些方法是log()形式的各种变种.
所有的信息是否显示决定于叫做消息级别的引擎范围的设置.

 消息在下面的五种级别上显示,按照详尽程度(verbosity)排序:
 错误级别 (Error)
 警告级别 (Warning)
 通知级别 (Info)
 详细级别 (Verbose)
 调试级别 (Debug)

这些级别指导Ant以什么样的状态来展示消息.例如,如果你告诉Ant仅仅显示通知级别的消息,那么发给Ant的所有错误级别,警告级别和通知级别的消息将会记录到日志中.
消息级别的值可以通过以下的Project类中公共的静态域取道:
 
 Project.MSG_ERR
 Project.MSG_WARN
 Project.MSG_INFO
 Project.MSG_VERBOSE
 Project.MSG_DEBUG

VERBOSE和DEBUG级别看起来很相似,但他们事实上并不相似.当你运行Ant时,你能通过单独的参数指定VERBOSE和DEBUG级别的消息.指定DEBUG级别的消息结果会导致不会显示VERBOSE消息,相反也一样.

log()函数把消息发送到工作中已注册的监听器.该监听器然后根据他的设计处理消息串.默认的日志监听器把所有的日志打印到控制台.
log()函数有三种形式可用:

log( message)
 在任务中,消息通过Project类的log方法传递.默认情况,对log()的调用是通知级别(通过MSG_INFO变量指定).
下面的例子发送同样的通知消息到工作引擎在默认级别MSG_INFO上.
 project.log("This build step has completed successfully with "+numfiles+" processed");
 
 log("This build step has completed successfully with "+numfiles+" processed");
例子显示,有一个默认的log()方法(定义在Task类中)可用而任务不用使用他们的Project实例变量.使用默认的log()是种好的想法,因为将来的Ant发行版本中任务层次对Project类的的访问会被取消.

log(message,level)
 log()方法的另外一个形式有第二个消息级别的参数.在发送DEBUG和VERBOSE消息时很有用.例如:
 
 //Use the project variable's log method to log messages
 project.log("For loop to process files begins",Project.MSG_DEBUG);

 //Use the log method from Task to log messages
 log("For loop to process files begins",Project.MSG_DEBUG);
注意到,有两种方法调用log().除了Project类,Task类也有两个参数的log()方法的实现.你应该尽量使用Task中的两个参数的方法log(message,level).

log(message,level,task)
 Project类中的第三种形式的log()方法有第三个参数,一个Task对象.有应该不要在自写的任务中调用这个方法.他是给工作引擎用的;我们在此提到仅仅是为了介绍的完整性.

5.3 任务的生命周期(The Task Life Cycle)

 复杂任务,操作多个文件,依赖内嵌任务,使用多重库(例如,可选的ejbjar任务),要求深入理解任务和Ant的关系.
把这看作是一次警告.这一节将深入探讨一个任务的生命周期的完整细节.如果你觉得你的定制任务没有这样复杂,那么请跳过这一节直接看我们的例子.
你之后什么时候都可以回过头来看这节.理解引擎和任务生命周期对你成为一个专家级的任务编写者是相当重要的,那对于编写相对简单的任务这并不要求.

 Ant处理所有的任务都是一样的.Ant在固定的阶段设定属性并处理内嵌元素.我们能预测一个任务是如何操作的并进而设计他.
任务的生命周期可被划分成两个主要阶段:解析时和运行时.
解析时阶段开始于Ant从XML文件中读取任务(把引擎想象成一个一个元素地解析XML文件).运行时阶段开始于解析时阶段成功完成之时.

5.3.1 解析时阶段(The Parse Phase)
 Ant在他地XML解析期读入一个元素节点时解析一个任务.
任务名,属性,内嵌元素都被包装成单个XML元素对象并存储在Ant的存储空间DOM.在解析时阶段,如果任务的XML描述不合规范或者任务构造时有操作抛出异常,这步操作就会失败.
下面是Ant在解析时阶段会产生的动作的完整清单:
 1.实例化任务类
 Ant,使用XML元素的名称,来实例化一个任务的相应类.记住,这个时候属性并不会被设置且不会和工作系统产生联系.
 2.建立引用指向project对象和父目标对象.
 任务使用引擎为他们准备好的可用的对象和引擎进行通信.在这个阶段,Ant建立对这些对象的引用,并使他们可用.
 3.增加id引用
 Ant在一个内部的表中存储带有id属性的任务列表.如果任务带有id属性,那么这个阶段仅仅对别的任务和数据类型重要.
 尤其是对于那些执行一些并行处理(parallel processing)的任务和数据类型很重要.关于并行处理,参考第7章.
 4.调用init()
 任务对象中的init()方法这个时候被调用.记住,任务属性此时还是不可用的.另外,任务所需其内嵌元素的信息也是不可用的.
 附注,许多已经发行的任务不实现这个方法.
 5.内嵌元素被解析并被处理,使用addXXX(),addConfiguredXXX(),和createXXX()方法等.

 在理解整个生命周期过程中这或许是最重要的一步(也是最难的一步).凭直觉,你也许会认为Ant定义和处理任务属性是在解析时阶段,但事实并非如此.
Ant知道运行时阶段才会关心任务属性.这也意味着非法的属性定义直到运行时才会被发现.但是,Ant却在解析时处理内嵌元素.
因此他会捕捉到非法的内嵌元素在捕捉到非法属性之前.
 那么,内嵌元素如何处理?他在你的任务中调用createXXX(),addConfiguredXXX(),或addXXX(),其中XXX被定义为内嵌元素名称.
那么这三个方法之间有什么区别?这主要取决于你计划怎么使用内嵌元素以及内嵌元素相关对象本身的性质特征.
如果你的任务需要自己来实例化这个对象,或者这个对象本身没有构造函数,那么请使用create;把它看作"你的任务创建的内嵌对象".
如果你的任务需要引用一个已经实例化的对象,那么使用add;把他看作"Ant把这个对象的引用加到你的任务对象中".
如果你需要Ant在传递这个对象的引用之前完整的处理这个元素节点,请使用addConfigured;把它看作"Ant把已配置的对象引用加到你的任务对象中".
如果还不清楚,请查看已有的任务实现吧.顺便说一句,Ant最先调用createXXX().如果你有对某特定元素的多个函数实现.Ant会统统调用的.
这样的结果将很可怕,所以请尽量不要这么作.

5.3.2 运行时阶段(The Runtime Phase)
 运行时阶段实际是对一个任务的总结结束.他开始于解析阶段的成功完成.当你的任务进入运行时阶段时,别的目标和任务可能已经成功运行了.
可能你想确保你的任务运行前所期待预想的行为和应有状态设置都应该已经完成了,但这是一种奢望!你的任务应该自动进行,并能够作为工作的第一个或最后一个任务进行.
以下是任务运行时发生的事情清单:
 1.任务的所有属性被设置
 把这些属性想象成任务的特征.Ant传递这些值给任务通过对每个属性调用方法setXXX(),其中XXX是属性的名称.
 如果少了某个set方法,Ant的错误会抛出并且任务和工作宣告失败.
 2.处理XML文件中的CDATA文本
 XML给予你存放原始文本的能力,使用<![CDATA[]]>构造.你可以发送原始文本给你的任务.Ant调用方法addText(String msg),传递一个String对象代表XML中的字符数据.
 下面是一个CDATA标记的例子:
 <taskname>
  <![CDATA[Naturalized language to be displayed by an Ant task]]>
 </taskname>
 当Ant读到CDATA标记时,它就会在你的任务中调用addText("Naturalized language to be displayed by an Ant task").
 如果你的任务类以及它的父类都没有实现addText方法,而你又包含了CDATA的标记,那么工作会失败.没有默认的方法来处理字符数据.
 许多作者并不使用CDATA属性.原始字符数据典型的被应用于需要处理消息任务或需要处理没有控制字符的文本.
 例如,script任务使用CDATA表示实际的脚本.因为如果不使用CDATA的话,象<,[等字符都是典型的编程语言操作符,这肯定会在XML中造成问题.
 3.所有的内嵌元素的属性被设定
 Ant解析所有元素的属性,当读到这些XML时.但知道运行时才作这些工作.这对所有的元素都是一样的,包括任务的内嵌元素.
 你几乎不需要担心内嵌元素的属性状态,因为你只有直到任务执行(此阶段的下一步)时才会用到,到那时,所有属性都是可用的.
 4.调用execute()
 到此,所有的数据收集和数据校验都已经完成.使用execute(),你的任务执行他所设计的行为.从这个时候开始,你必须处理或抛出所有的错误条件.
 Ant不会期望会有错误码返回,从而一旦发生不会在调用你任务中的其他方法.
 
 再说一遍,为了写一个任务你并不需要完全理解任务的生命流程.理解它可以最大程度的帮助你发现为什么有些你想做的事情在你任务中没有发生.
很少的情况下,你必须精通整个生命流程才能让某些事情发生.尽量避免它,因为任务执行的过程细节以后可能悄无声息的发生变化.除非你维护自己的Ant版本.
你会发现你自己坚持一个发行的版本可以运行,而另一个就不行.

 生命周期很重要,因为它允许Ant和所有的任务一致的工作.引用别的任务的想法和代码变得简单和那么普通.

5.4 分析一个例子:jar任务
 讲了那么多理论之后,让我们看看实际运用会发生什么(When rubber meets the road).要开发你自己的Ant任务,只需要写一个Java类来实现你的设计.
任务复杂还是简单那要看你了,唯一重要的是你的Java类必须和Ant对象模型中阐述(set forth)的协定(convention)一致.

 作为编写任务的一个例子,我们分析已有的任务:jar.jar任务已经覆盖了所有我们需要的主题.jar任务类是深层次的一部分,同时展示(demonstrating)了通过继承实现的重用(re-use).
它继承自zip,而zip继承自MatchingTask.jar任务没有自己的execute()方法实现,而是依赖zip类中的实现.
这表示了关于你自己的实现中,某些要求是那么的松.jar任务同样使用了大量的属性和内嵌元素,成为提供给我们的一个关于如何处理所有这些特征的好的例子.
使用已有的任务作为例子可以加强这个概念:用户编写任务和Ant发行中包含的任务之间没有什么区别.
 分析jar可以给我们一些设计任务眼光.它有着唯一且容易理解的设计目标.我们有的这个对象重用的任务设计对于未来的扩展也是开放的.
War和Ear继承自Jar,得到了同样的益处.但我们并不能涉及到jar任务的各个特征和各个方面.需要更多的信息,就花点时间看发行的源码吧.
学习更多的任务实现,而不仅是jar任务,可以帮助你成为一个强大的Ant任务开发者.
------------------------------------------------------------------------------------------------
 Where to look: The source for Jar, Zip, and MatchingTask is found in
 the source distribution of Ant (http://jakarta.apache.org/builds/jakartaant/
 release/v1.4.1/src). We analyze the jar task with code snippets from
 these source files. If you fail to follow some of our decisions or don't
 understand how a code snippet fits in with the descriptions, feel free to
 follow along with the full source code at hand.
------------------------------------------------------------------------------------------------
 这样说吧,我们的分析在创建一个工作任务方面并不全面(comprehensive).我们简单涉及并解释一下设计和编写jar任务的主要点(major points),
但是对于诸如处理Jars的输入流等实现细节并没有涉及.他只是一份分析而不是教程.如果你根据此分析来尝试编写并编译代码,你会发现最后有些东西并不工作.
在简洁(concise)和完整(complete)的矛盾上,我们选择了简洁,不完整(sacrificing),当然也是一份成熟的用户自写任务指导.
不过,我们的分析精确的描述了写好jar和其他任务所必备的工作(effort),如果你认为我们提供的不够,没有比你去学习任务的源码是更好的推荐.

 开始,我们假设Ant没有jar任务.没有它,我们第二章的例子工程就没有任务来创建这些类的Jar包了.
使用java或exec来运行命令行jar工具真是太讨厌了,也容易犯错(在本章的导论中有讨论).

5.4.1 设计jar任务
 对于一个要创建Jars的任务有什么要求?比较好的开始是从命令行工具jar起步.最小的情况下,我们的任务应该拥有此工具在创建JAR方面的全部特征(而不是此工具的所有特征).
这个区别是很重要的.我们不用重复实现jar工具,我们只是为我们的工作而创建一种可用操作,仅仅符合我们工作的需要罢了.
命令行工具仅仅能达到这样目标.我们的工作要求我们创建JARS,因此我们应该关心JAR的创建,而不是别的.
我们之后应该定义一种需求,例如,解包JARS文件,我们应该需要这些特征的实现.
 命令行工具可以生成一个zip兼容的文件加上一个特殊的文件夹叫做META-INF.它存放作一个特殊的文件叫做MANIFEST.MF.
不用深究更多细节,我们把JARS称作智能zip文件:此文档不仅可以把一组文件打包成一个文件,并且有一个包描述类型.
最小情况,我们的任务应该创建JARS并带有(allow)用户自写的清单文件,如果有的话.
 从整个工作的观点看,我们的设计应该允许我们使用多重目录和文件类型的多组文件来创建JARS.
既然一个JAR包含了类文件路径的目录结果,我们可能需要改变有多少组文件被存储到一个JAR文件中.经验丰富的Ant用户用文件集合和文件模式来识别这件事情.(这章后,相信你也能做到.)
对现有的任务的匆忙研究也揭示出了一些相似的有关文件集合的设计,例如copy和zip.
 简单而言,以下是我们的jar任务的要求:
 拥有(duplicate)命令行工具的JAR创建能力
 给定一个命名,一个清单文件名(manifest),一组文件或目录,命令行工具就可以生成JARS.我们的任务也要做到这一点.
 可以对一系列文件,目录和文件模式(Pattern)进行操作
 许多任务都有能力处理用户自定义文件集合信息,也能处理用户定义的文件模式.我们应该准备让我们的任务具备这项功能.
 通过XML描述,添加/修改清单文件
 这是一个可扩展其对应工具功能的任务的例子.不仅是维护一个单独的清单文件,我们允许清单设置成为工作文件内部的设定,当然也就是XML节点.
从我们的需求分析中,我们应该有了XML形式的任务语法的大概模样.当你为你自己的任务定义语法时,因为你的继续前进而使得设计变化,那请不要对此感到惊讶.
我们的任务的XML设计:
 <jar jarfile="somefile.jar" manifest="somemanifest.mf" basedir="somedir">
  <fileset dir="somedir">
   <include name="**/*.class"/>
  </fileset>
  <manifest>
   <attribute name="SomeAttribute" value="SomeValue"/>
  </manifest>
        </jar>
5.4.2 权衡优先工作
 假设除了使用Jar任务,我们已经没有了别的办法来完成这项工作,现在我们知道,我们需要编写一个定制任务.
 .......
(未完待续.....)
[附注:我第一次作翻译,由于水平有限,翻译的不成样子,真正理解的翻译的困难,也知晓了自己的水平的低下,对自己以前骂前人翻译的烂而感到惭愧,翻译工作是辛苦的,我几次欲放弃,都凭意志坚持下来,可如今我真的有点不知所措,我希望自己能坚持下来,纵然这样的翻译对读者可能是一种‘伤害’...........]

本文地址:http://com.8s8s.com/it/it33196.htm