摘要
这篇文章介绍了通过ANT任务的扩展来实现IoC管理对象或非管理对象的执行同时也介绍了OGNL(对象图形导航语言)如何被用来使ANT执行任何方法表达式包括带有运行时参数的也介绍了如何使用JUNIT来测试ANT的扩展此外还包含一个使用SPRING框架的实现AntIoC的组合为创建松耦合的软件开发支持任务开创了新的天地
我需要增加一个基于ANT驱动的新任务并且我用SPRING(一个轻量级的IoC框架)来实现这个任务我几乎没有碰到什么问题因为IoC容器是非侵入式的这很容易创建一个包装或者直接使用对象来实现任务于是我开始想知道是否ANT可以直接使用SPRING配置的对象然后重用已经定义和测试的依赖图和配置那为什么还要重复和引入波纹效应或其他问题呢?如果IoC容器果真能提供这样的便利就可以保证更直接的使用
这篇文章介绍了这种方法并且演示了一种概念上的实现刚接触ANT扩展的开发者会发现这个例子十分有趣
ANT扩展
为了给ANT增加自定义任务ANT手册建议使用为这个目的而提供的类如Task类但是这个建议不是强制的ANT可以执行任何拥有execute()方法的类(当然ANT也可以通过使用exec或java任务来执行任何程序但那是另一种扩展方式)ANT也支持集成这些任务扩展到各种类型的属性或XML文件中
给ANT增加一个自定义任务的最佳方法是通过Task扩展来重用IoC框架因此执行独立应用的Task必须设置和使用建立在ANT基础上的框架内置的对象和资源
控制反转
IoC设计模式也称作DI(依赖注射)在框架的上下文中这与JAVA对象的组成有关在IoC框架上增加的投资很大一部分是由于SPRING框架的开发人员演示了在一个IoC/AOP/XML/JavaBeans轻量框架中的协同作用而这正是通过允许为其他API或组件创建强大的抽象层来提供超越DI能力的原因SPRING本身就是一个使用IoC的例子ANT看起来与IoC容器相适应因为他也是基于XML或者JavaBean的从某方面来说他也使用了IoC
需求
我们的ANT IoC任务扩展需求可以通过角色/目标/需求的格式来定义(这里的需求不分顺序)
●角色开发人员
●目标修改IoC任务
●需求
在任何代码改变或构建后执行回归测试
很容易在回归测试中增加新的测试用例
支持不同的IoC框架
通过修改ANT日志的级别或IoC日志的配置使调试时可以得到更有效的输出
●角色构建创建人
●目标编辑ANT目标并使用任务来定义IoC容器的输入或输出Bean
●需求
设置IoC描述符的位置
在不需要容器时定义FQCN(完全限定类名)作为目标
使用IoC时设置POJO(普通JAVA对象)Bean名缺省为antBean
定义目标方法名缺省为execute
定义一个调用可以带参数的表达式的方法
定义可以插入目标Bean的属性用来复写容器属性
定义目标的元素文本
没有必要定义用来处理Ant/IoC组合的新类
为了各种扩展需要重用现存的属性文件
●角色任务扩展对象
●目标执行对象方法
●需求
执行在IoC Bean定义中定义的POJO
执行容器外的定义类
如果没有定义使用缺省的Bean名antBean
执行简单的方法缺省为execute()
执行带可选参数的方法表达式
如果目标是ANT相关的则插入工程
插入动态属性
任务
支持这些需求的任务定义是SpringContextTask
描述
这个任务执行由SPRING容器管理的或者是未管理的FQCN的对象的方法目前还不支持SRPING Bean定义引用的Classpath
SpringContextTask的参数如下表所示
例子
最简单的应用我们的ANT任务扩展的例子如下
&! create the task definition >&taskdef name=runBean classpatclassname=jbetancourtanttaskspringSpringContextTask/>&target name=simpleAppContextUseWithDefaults> &runBean beanLocations=applicationContextxml>&/runBean>&/target>
simpleAppContextUseWithDefaults目标执行在文件路径中找到的Bean定义文件applicationContextxml中的Bean名为antBean的execute()方法路径属性名是复数的以便将来支持多个Bean定义文件
Bean的执行类似ANT执行对象的方法然而这里是IoC容器来管理Bean容器可以增加事务依赖包装数据库设置网络服务代理使用远程甚至提供AOP代理来代替实际目标Bean我们的方法简化了配置因为ANT脚本不再需要知道如何配置对象特别是复杂的对象但是如果ANT脚本确实需要为服务调用设置特定的属性时会怎么样呢
&target name=publish>&springbeanLocations=applicationContextxml beanName=siteGeneratormethodName=generateSite host=${hostsiteurl}port=${siteport}> Made a few tweaksRemoved some sentence fragments &/spring> &/target>
注意因为任务名已经在taskdef中定义了使用的名字将依赖于ANT的taskdef定义这儿任务名是spring现在我们定义Bean名字和调用的方法元素文本也会被放到目标Bena中在这个例子中文本是一个发布的注释
通过使用ANT的动态属性功能我们也可以将需要的属性放到目标对象中通常在ANT文件中一个属性被解析时对应的set方法会被调用使用动态属性非对象属性或字段会通过setDynamicAttribute()方法被增加到对象中通常因为容器已经包装了其中的Bean的属性这种属性注入提供了一种重写的能力但是是否这样会将配置复杂化?我们将不得不维护ANT任务使用的属性及管理对象所需要的属性
当然这不是必须的如例子中的SPRING用法相同的属性文件被ANT和SPRING同时使用— 即使使用了ANT的占位符语法(${})SPRING提供了这种目的的类如PropertyPlaceHolderConfigurer因此这种方法不会引入新的配置恶梦可参考旁注属性中的属性获得更多的帮助
另一种放置属性的方法是通过使用call属性来调用带运行时参数的目标方法或者嵌套的methodCall元素他的内容是java表达式这个元素很容易使用因为XML需要的符号如实体转义符可以用CDATA来避免
call=generateSite("${hostsiteurl}""${siteport}") Or better:&methodCall>&![CDATA[generateSite(${hostsiteurl}${siteport}) ]]>&/methodCall>
因此先前的例子可以如下写法
&target name=publish>&spring beanLocations=applicationContextxml beanName=siteGenerator> &methodCall>generateSite(${hostsiteurl}${siteport})&/methodCall> Made a few tweaksRemoved some sentence fragments &/spring> &/target>
当然目标对象必须包含需要的方法和参数标识符
上面的例子简单介绍了SpringContextTask方法可能他们可以有其他或更好的实现
有人可能会对这个Task扩展的特性有疑问如调用任何方法的功能这个功能甚至可以被移除因为任何不包含execute()方法的目标Bean可以被包装一个任务在IoC框架中可能更容易完成但既然通过OGNL(后面会讨论)支持方法表达式很容易那么方法参数的支持也不是个问题了
有趣的是既然任何方法可以被调用那么同一对象可以在同一个构建文件中被重用来提供不同的服务这样就可以在执行需要很多属性的任务中减少过度的ANT脚本混乱了如果任务实例可以通过ID来引用的话这个功能就会有实际意义了我们可以象下面这样写
&spring id=metrics beanLocations=metricsContextxml beanName=main exampleAttribute=a valueand so forth /> &target name=ComputeMetrics>&spring refid=metrics call=computeNCSS/>&spring refid=metrics call=computeCCM/>&spring refid=metrics call=findBugs/> &/target> &target name=genDocs>&! here are calls to other types of docs />&! now call the metric docs />&spring refid=metrics call=createDocs/> &/target>
现在我们拥有更易读的格式而隐藏了更多的信息我们不再关心容器中有什么只要那儿有一个入口点—main那个Bean可以是实际的Bean或者通过依赖注射代理给其他工具如PMD JavaNCSS 或者FindBugs
我没有选择通过ID引用重用SpringContextTask的开发方式另一种完成重用的方式是在上下文中使用不同的Bean如
&target name=ComputeMetrics> &spring beanLocations=metricsContextxml beanName=computeNCSS/> &spring beanLocations=metricsContextxml beanName=computeCCM/> &spring beanLocations=metricsContextxml beanName=findBugs/>&/target>
但在这个例子中的每一个Bean必须有一个execute()方法来启动服务而且每一个Bean实际上只是引用同样的类或对象
现在需求已经确定而