到 Google 资讯主页   
EasyJF首页   资料   源码   软件    论坛   网站    
   使用帮助    
    该信息为本站MyRSS系统缓存内容,部分图片及附件有可能无法正常使用.easyjf.comwww.matrix.org.cn无关,不对该信息负责.通过http://www.matrix.org.cn//resource/article/43/43845_Ant_Spring.html访问该信息的原始内容.
页面功能  【加入收藏】 【推荐给朋友】 【字体:  】 【关闭】   
用ANT来享受SPRING
作者:xMatrix 来源:www.matrix.org.cn  发布时间:2006-02-22 17:51:10.877

用ANT来享受SPRING

通过轻量级的IoC容器来扩展ANT

作者:Josef Betancourt 2005年2月14日

翻译:xMatrix


版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:
Josef Betancourt ;xMatrix
原文地址:
http://www.javaworld.com/javaworld/jw-02-2005/jw-0214-antspring.html
中文地址:
http://www.matrix.org.cn/resource/article/43/43845_Ant_Spring.html
关键词: Ant Spring



摘要
这篇文章介绍了通过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的参数如下表所示:

image

例子
最简单的应用我们的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(&quot;${host.site.url}&quot;,&quot;${site.port}&quot;)" 
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实际上只是引用同样的类或对象。
现在需求已经确定而且用例也定义好了,那就让我们来看看细节吧。

实现
这是一个简单实现,并没有提供什么技巧,如ANT的IO子系统或者重用IoC容器来执行多任务。
UML图显示如下:

image
图1. UML 类图. 点击查看大图

列表1 显示了Task扩展的抽象父类。在运行时,ANT调用xecute()。如果任务中的beanLocations属性被定义,一个IoC应用上下文被创建并且Bean变成可访问的了。如果一个类被定义则执行普通的对象实例化。

下面是Project属性,文本被插入到对象中,只依赖是否存在相应的设置方法或者对象是否是ANT相关的。因此在非IoC的情况下,这个任务是一种设置注射的方式。

OGNL
当我实现我的任务扩展时,对于调用运行时参数的方法的支持还是一个问题,虽然不是很严重,但这是一个设计上的机会。一个方法是调用增加一个包含参数的XML段给任务。ANT支持动态元素,网上也有一些给这种任务使用的XML段的例子。但这种方式看起来过于复杂而且容易出错。

想起我先前在OGNL上的研究,我决定在这儿使用他。根据OGNL网站所说,ONGL是“对象图形导航语言;他是一种表达式语言用来获取和设置JAVA对象的属性。你可以使用相同的表达式获取和设置属性的值”。OGNL的一个优秀的特点是支持方法调用,这正是我所需要的。

OGNL支持很多表达式语言。例如,下面的代码计算一个目标对象的索引表达式作为调用的一部分:

<methodCall>  <![CDATA[    notifyDeveloper(names(${dev}) ]]>       </methodCall> 
  

另一个OGNL表达式计算的例子可以在包含的单元测试中找到,那里一个方法调用表达式被如下使用:
convertToString(employees[getNum()]).

虽然OGNL解决了我遇到的问题并且表达式提供了一种新的强大的ANT功能,但是定义属性的使用才是推荐的方法而且与IoC模式兼容。
为了演示抽象类如何使用,下一节我们会用SPRING来实现一个实际的Task扩展。

SPRING支持

列表2显示了SpringContextTask。这个类是如何定位特定IoC容器的例子。模板方法模式使这个类简单化;所有调整实际目标对象来支持需求的细节都被放在了父类中。如何找到容器在使用任何IoC容器中都是必要的,从容器中得到对象,并且给属性赋值。

列表 2. SpringContextTask类

/*
* SpringContextTask.java
* Created on Jan 9, 2005
* Creator: Josef Betancourt
* Project: SpringContextTask
* -----------------------------------------------------------------------------
*
* Copyright 2005 by Josef Betancourt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* -----------------------------------------------------------------------------
*
*
*/

package jbetancourt.ant.task.spring;

import java.util.Enumeration;
import java.util.Properties;

import jbetancourt.ant.task.AbstractContextTask;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.PropertyOverrideConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
*
* Ant task extension that invokes a POJO within a Spring Application
* Context, a "springdef".
*
* Tested with Ant 1.6.2, JDK 1.4.2, Spring 1.1.4, and OGNL 2.6.7.
*
* This task is added to Ant with a taskdef or one of the new Ant 1.6+
* approaches.
*
*
*
*  Example taskdef:  
*  <taskdef name="springTask"
*     classname="jbetancourt.SpringContextTask"
*     classpathref="taskdefclasspath">
*  
*  It is then used simply by accepting the defaults as:
*      <target name="test1">
*      <springTask beanLocations="applicationContext.xml">
*      </springTask></target>        
*  
*  A more complex use is:
*     <target name="publishBean">
*           <springTask
*                      beanLocations="applicationContext.xml"
*                beanName="siteGenerator"

*                      host="${host.site.url}"
*                      port="${site.port}"
*                      methodName="generateSite">
*                            In-line site post change text
*                </springTask>                    
*       </target>
*
*  
* Use of methodCall element with CDATA expression.
*
*   <target name="test8">
*      <springTask beanLocations="applicationContext.xml"
*                 beanName="antBean1">            
*           <methodCall><![CDATA[execute("Goodbye")]]>
*                 </methodCall>            
*       </springTask>                
*   </target>                  
*
*
*
*
*
* @author JBETANCOURT
* @since Jan 9, 2005  
*  
*/
public class SpringContextTask extends AbstractContextTask {

    /** runtime Spring context that that will be set or created */
    private ApplicationContext applicationContext;

    /**
     *
     * Get the container managed bean.
     *
     * @param beanName
     * @return the pojo, singleton or non-singleton.
     */
    public Object getBeanFromContainer(String beanName) throws BuildException {
        try {
            if(applicationContext == null){
                applicationContext = new FileSystemXmlApplicationContext(
                    getBeanLocations().list());
            }

            return applicationContext.getBean(getBeanName());
            
        } catch (BeansException e) {
            throw new BuildException("Failure: could not get bean '" +
                    getBeanName() + "' from context",e);
        }        
    }
    
    /**
     * Invoke target bean setters with Ant specified dynamic properties.
     *
     */
    public void insertManagedBeanProperties(){
        // How to programmatically post process a Spring bean?
        // See Spring Forum thread for source of this approach.
        // http://forum.springframework.org/viewtopic.php?p=11833#11833
        PropertyOverrideConfigurer poc = new PropertyOverrideConfigurer();
        
        // The keys must be of form 'beanName.key'.
        // Create a new Properties with this format.
        Properties props = addKeyPrefix(getDynamicProperties(), getBeanName());        

        poc.setProperties(props);

        ((ConfigurableApplicationContext)applicationContext).
            addBeanFactoryPostProcessor(poc);
        ((ConfigurableApplicationContext)applicationContext).refresh();        
    }
    
    /**
     * For each key in props, convert to 'beanname.key' format.
     *
     * @return with keys modified
     * @throws BuildException
     */
    private Properties addKeyPrefix(Properties initProps,
            String prefix) throws BuildException {
        
        // TODO:  This seems like a wrong approach here.  
        // Can the passed in props be manipulated instead?

        
        Properties props = new Properties();
        
        for (Enumeration en = initProps.propertyNames();
                  en.hasMoreElements();) {
            String key = (String) en.nextElement();
            
            // Let Ant resolve any property replacements.
            String resolvedText = getProject().replaceProperties(initProps.getProperty(key));
            props.put(prefix + "." + key, resolvedText);
        }
        
        return props;
    }

    /**
     * Get the Spring context that was created or set.
     * @return could be null
     */
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     *
     *
     * @param applicationContext non-null
     *            The applicationContext to set.
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        log("setting applicationContext: " + applicationContext,
                Project.MSG_DEBUG);
        this.applicationContext = applicationContext;
    }

    /* (non-Javadoc)
     * @see jbetancourt.ant.task.ExternalContainer#createContainer()
     */
    public void createContainer() throws BuildException {
        try {
            if(applicationContext == null){
                applicationContext = new FileSystemXmlApplicationContext(
                    getBeanLocations().list());
            }
            
        } catch (BeansException e) {
            throw new BuildException("Failure: could not create Spring container.",e);
        }        
    }
}


注意现在的SPRING实现是非常直接的。ANT的两个特性保证了这种简单性。首先,任何异常都被ANT的非强制异常BuildException所封装,这样就简化了更多的扩展。其次,使用ANT的日志系统可以隐藏记录实现的细节。

主要的复杂性在于方法insertManagedBeanProperties()。设置对象的属性是直接的,然而在容器框架中设置属性可能是很关键的。容器的语法必须被遵守:容器可能拥有或需要作用于属性设置操作的功能,如事件处理,自动绑定及转换。

AOP实现

既然大多数IoC框架都支持AOP,另一个可选的方法是利用IoC容器中的AOP使他更加灵活。例如,在SPRING中,你可以创建一个专一的ANT工厂Bean来提供在ANT属性中的绑定并且满足其他需求。但我没有在我的任务中使用他。

测试

ANT提供对使用JUnit Test子类的单元测试的支持,这使得Task的测试更易管理。这篇文章对应的源程序中包含这些测试。当他运行时(用Maven或Ant),部分输出如下:

test:test:
    [junit] Running jbetancourt.ant.task.AbstractContextTaskTest
    [junit] Tests run: 4, Failures: 0, Errors: 0, Time elapsed: 1.752 sec
    [junit] Running jbetancourt.ant.task.spring.SpringContextTaskTest
    [junit] Tests run: 21, Failures: 0, Errors: 0, Time elapsed: 4.256 sec
BUILD SUCCESSFUL
Total time: 11 seconds


SpringContextTaskTest类已经有ANT风格的任务测试。在这些测试中,JUnit子类的测试通过executeTarget()或类似的方法执行ANT文件中的目标:

   public void test2(){ 
      executeTarget("test2");
  }      
  public void test3(){
      expectBuildException("test3", "beanLocation or class must be specified");
  }    


任务测试在ANT文件SpringContextTaskTest.xml中,包含了实际的测试目标,如:

   <target name="test3">  <!-- No context path or class specified; should fail -->
     <springTask methodName="length"/></target>


并且在我们的扩展测试中,这个目标引用在applicationContext.xml中的SPRING Bean定义。这些Bean定义由一个重用为不同的Bean的JAVA类来组成。例如:

   <bean id="antBean1" class="jbetancourt.TestBean1"/>     
   <bean id="beanWithProps" class="jbetancourt.TestBean1"/>
   <bean id="main" class="org.springframework.beans.factory.
     config.MethodInvokingFactoryBean">
      <property name="targetObject"><ref local="antBean"/></property>
      <property name="targetMethod"><value>execute<
        /value></property>
   </bean>  
                  

一个有趣的测试是test18,他使用一个AOP代理返回的Bean:

  <target name="test18">
      <springTask beanLocations="etc/contexts/applicationContext.xml"
        beanName="postBugReport" methodName="doService"></springTask>
    </target>        
Bean定义如下:
    <bean id="securityInterceptor" class="jbetancourt.SecurityInterceptor">        
    </bean>
    
    <bean id="postBugReport" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target"><ref local="antBean"/></property>
        <property name="proxyInterfaces">
            <list>
                <value>jbetancourt.ISecurity</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>securityInterceptor</value>
            </list>

        </property>        
    </bean>


SecurityInterceptor可以是一个用于编译的安全advise,如登录过滤器(类似于servlet过滤器)。postBugReport目标与先前的antBean一致。

部署
IoC目标Bean及定义文件或者类可以通过发布的JAR文件来提供。我们可以在使用SRPING IoC时称这些为SPRING件。(如图2)

image
Figure 2. Springlet的用法。

使用有层次的外部文件可以重写在部署文档中的缺省值。因此用户只需要编辑外部的属性文件及任何子Bean定义文件来自定义缺省值。外部文件可以定义其他协作的IoC JAR。

总结
在这篇文章中,我介绍了一种ANT扩展来执行IoC容器管理的对象,从而增加程序的松耦合度。相同的任务也可以执行在非管理对象的方法上。通过使用OGNL,可以通过JAVA表达式来执行方法,而不需要更复杂的XML定义。这里并没有讨论优化和利用ANT及SPRING的高级特性。
虽然这里是通过ANT扩展来表现这个观点,但同样可以用Maven或其他构建系统来实现。同样的,这里使用的SRPING也可以用其他IoC框架来代替,如HiveMind—需要ANT任务必须定义一个模块发布描述文档,或者PicoContainer—虽然他更偏好通过编程来配置。

关于作者
Josef Betancourt,生活在罗得岛的一个高级软件工程师。

资源
●下载与这篇文章对应的源程序:
http://www.javaworld.com/javaworld/jw-02-2005/antspring/jw-0214-antspring.zip
●OGNL的好的介绍:“OGNL表达式及绑定语言”,Drew Davidson (2004年3月)
http://www.ognl.org/resources/OGNL_Presentation_20040309.pdf
●为什么及如何使用轻量级容器:精通不使用EJB的J2EE开, Rod Johnson, Juergen Hoeller (Wrox, 2004年6月; ISBN: 0764558315)
http://www.amazon.com/exec/obidos/ASIN/0764558315/javaworld
●“IoC简介,Sam Newman (java.net, 2004年2月)
http://today.java.net/pub/a/today/2004/02/10/ioc.html
●IoC分析:“控制反转容器和依赖注射模式”,Martin Fowler (martinfowler.com):
http://martinfowler.com/articles/injection.html
●IoC对设计的影响:“依赖注射和开放闭合设计”
http://jroller.com/page/rickard/20040814#dependency_injection_and_open_vs
●Spring主页
http://www.springframework.org
●Ant主页
http://ant.apache.org/
●OGNL主页
http://www.ognl.org/
●HiveMind主页
http://jakarta.apache.org/hivemind/index.html
●PicoContainer主页
http://picocontainer.org/
●PMD主页
http://pmd.sourceforge.net/
●JavaNCSS主页
http://www.kclee.de/clemens/java/javancss/
●FindBugs主页
http://findbugs.sourceforge.net/
●Maven主页
http://maven.apache.org/
●更多关于SPRING的知识,可阅读“Pro Spring: Spring and EJB”,(JavaWorld, 2005年2月):
http://www.javaworld.com/javaworld/jw-02-2005/jw-0214-springejb.html
●学习如何使用Spring和JSF,可阅读“让JSF开始工作”,Derek Yang Shen (JavaWorld, 2004年7月)
http://www.javaworld.com/javaworld/jw-07-2004/jw-0719-jsf.html
●更多关于设计模式的知识,可浏览设计JavaWorld的主题索引的模式部分
http://www.javaworld.com/channel_content/jw-patterns-index.shtml
●更多JAVA开发工具的文章,可以游览JavaWorld的主题索引的开发工具部分
http://www.javaworld.com/channel_content/jw-tools-index.shtml

 
相关文章
 
页面功能  【加入收藏】 【推荐给朋友】 【字体:  】 【关闭】   


EasyJF.com 2006 隐私政策 使用EasyJF前必读