摘要
这篇文章介绍了通过ant任务的扩展来实现ioc管理对象或非管理对象的执行。同时也介绍了ognl(对象图形导航语言)如何被用来使ant执行任何方法表达式,包括带有运行时参数的。也介绍了如何使用junit来测试ant的扩展。此外,还包含一个使用spring框架的实现。ant-ioc的组合为创建松耦合的软件开发支持任务开创了新的天地。
我需要增加一个基于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" classpathref="testpath" classname="jbetancourt.ant.task.spring.springcontexttask"/>&target name="simpleappcontextusewithdefaults"> &runbean beanlocations="applicationcontext.xml">&/runbean>&/target>
simpleappcontextusewithdefaults目标执行在文件路径中找到的bean定义文件applicationcontext.xml中的bean名为antbean的execute()方法。路径属性名是复数的以便将来支持多个bean定义文件。
bean的执行类似ant执行对象的方法;然而,这里是ioc容器来管理bean。容器可以增加事务依赖,包装数据库,设置网络服务代理,使用远程甚至提供aop代理来代替实际目标bean。我们的方法简化了配置,因为ant脚本不再需要知道如何配置对象,特别是复杂的对象。但是如果ant脚本确实需要为服务调用设置特定的属性时会怎么样呢:
&target name="publish"> &spring beanlocations="applicationcontext.xml" beanname="sitegenerator" methodname="generatesite" host="${host.site.url}" port="${site.port}"> made a few tweaks. removed 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("${host.site.url}","${site.port}")" or better: &methodcall>&![cdata[ generatesite("${host.site.url}","${site.port}") ]]>&/methodcall>
因此先前的例子可以如下写法:
&target name="publish"> &spring beanlocations="applicationcontext.xml" beanname="sitegenerator"> &methodcall> generatesite("${host.site.url}","${site.port}") &/methodcall> made a few tweaks. removed some sentence fragments. &/spring> &/target>
当然,目标对象必须包含需要的方法和参数标识符。
上面的例子简单介绍了springcontexttask方法。可能他们可以有其他或更好的实现。
有人可能会对这个task扩展的特性有疑问,如调用任何方法的功能。这个功能甚至可以被移除,因为任何不包含execute()方法的目标bean可以被包装,一个任务在ioc框架中可能更容易完成。但既然通过ognl(后面会讨论)支持方法表达式很容易,那么方法参数的支持也不是个问题了。
有趣的是,既然任何方法可以被调用,那么同一对象可以在同一个构建文件中被重用来提供不同的服务,这样就可以在执行需要很多属性的任务中减少过度的ant脚本混乱了。如果任务实例可以通过id来引用的话这个功能就会有实际意义了。我们可以象下面这样写:
&spring id="metrics" beanlocations="metricscontext.xml" beanname="main" exampleattribute="a value" and 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="metricscontext.xml" beanname="computencss"/> &spring beanlocations="metricscontext.xml" beanname="computeccm"/> &spring beanlocations="metricscontext.xml" beanname="findbugs"/>&/target>
但在这个例子中的每一个bean必须有一个execute()方法来启动服务。而且每一个bean实际上只是引用同样的类或对象。
现在需求已经确定而
闽公网安备 35060202000074号