Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Let your Ant enjoy Spring

Extend Ant using a lightweight Inversion of Control container

  • Print
  • Feedback

I needed to add a new task to an Ant-driven build and I implemented that task using Spring, a lightweight IoC (Inversion of Control) framework. I encountered virtually no issues since, because an IoC container is noninvasive, it is relatively easy to create a wrapper or just use the objects that implement the task. I then began to wonder if Ant could directly use Spring-configured objects, thus reusing the dependency graph and configuration already specified and tested. Why duplicate and introduce side effects or other problems? If IoC containers really do provide a benefit, a more direct use is warranted.

This article introduces this approach and presents a proof-of-concept implementation. Developers new to Ant extension coding should find this example interesting.

Ant extensions

To add a custom task to Ant, the Ant manual recommends using an Ant class intended for this purpose, like the Task class. But, even this recommendation is not mandatory; Ant can invoke any class that has an execute() method. (Of course, Ant can also invoke any program using the exec or java task, but that is a different type of extension.) Ant also supports aggregating these task extensions into various kinds of properties or XML files.

The best approach for adding a custom task to Ant is one that reuses the IoC framework via a Task extension. Thus, a Task that performs what a standalone application must do to set up and use the framework's hosted objects and resources builds upon the strength of Ant.

Inversion of control

The Inversion of Control (IoC) design pattern, also called Dependency Injection (DI), in the framework context, is concerned with the componentization of Java objects. The growing interest in IoC frameworks is due in large part to the Spring framework's developers showing the synergy available in an IoC/AOP/XML/JavaBeans lightweight framework, which offers capabilities beyond DI by allowing the creation of a powerful abstraction layer to other APIs or components. Spring is itself an example of the use of IoC. Also note that Ant seems conformant with this IoC container since it too is XML/JavaBeans-based and, in some ways, already uses IoC.

Requirements

Our Ant IoC task extension requirements can be specified in an actor/goal/requirements format (the requirements are not ordered):

  • Actor: Developer
  • Goal: Modify the IoC task
  • Requirements:
    • Run a regression test following any code changes or build
    • Easily add new tests to the regression set
    • Support a different IoC framework
    • Debug by changing Ant log level and/or IoC log configuration, and getting useful output
  • Actor: Build creator
  • Goal: Edit an Ant target and use the task to specify beans that are in or out of an IoC container
  • Requirements:
    • Set the IoC descriptor path
    • Optionally, if no container is required, specify FQCN (fully qualified class name) as target
    • If using IoC, set the POJO (plain old Java object) bean name, default is antBean
    • Specify the target method name, default is execute
    • Specify a method call expression that may have arguments
    • Specify properties to insert into the target bean, overriding container properties
    • Specify element text to push to target
    • No need to specify new classes to handle the Ant/IoC combination
    • Reuse existing property files for variable expansion
  • Actor: Task extension object
  • Goal: Invoke an object method
  • Requirements:
    • Invoke a POJO specified in the IoC bean definition
    • Invoke a specified class outside a container
    • Use default bean name antBean if none specified
    • Invoke a simple method, default is execute()
    • Invoke a method expression with optional arguments
    • If target is Ant-aware, insert project
    • Insert dynamic properties

Task

The task specification that supports these requirements is SpringContextTask.

Description

This task invokes a method on an object either managed by a Spring container or unmanaged and specified as a FQCN. Classpath Spring bean definition references are not supported yet.

SpringContextTask's parameters are shown in the table below.

Parameters

Attribute Description Required
beanLocations Path to IoC descriptors Optional, or classname, not both.
className FQCN of target class Optional, or beanLocations.
beanName The bean id in the IoC Optional. Default: antBean.
methodName Method to invoke on bean Optional. Default: execute.
call Method call expression Optional, or methodCall, not both.
     
* Dynamic attributes 0 or more.
methodCall   Optional. Expression. A nested element. Alternative to the call attribute.


Examples

The simplest example of applying our new Ant task extension is:

 <!-- create the task definition -->
<taskdef name="runBean" classpathref="testpath"
      classname="jbetancourt.ant.task.spring.SpringContextTask"/>

<target name="simpleAppContextUseWithDefaults"> <runBean beanLocations="applicationContext.xml"></runBean> </target>


The simpleAppContextUseWithDefaults target invokes execute() on the bean named antBean in the bean definition applicationContext.xml found on the file path. The path attribute name is plural to support future use of multiple bean definition files.

This bean invocation resembles how Ant already invokes an object; however, this time, an IoC container manages the bean. The container could have added transaction dependencies, wired in datastores, set up a Web services proxy, used remoting, or even supplied an AOP (aspect-oriented programming) proxy instead of an actual target bean. Our approach simplifies configuration since the Ant script does not need to know how to configure an object, especially a complex one. But, what if the Ant script must set some required properties for a service call on a bean:

  <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> 


Note that since the task name is defined in a taskdef, the name used depends on the Ant taskdef definition (not shown); here, the task name is spring. Now we specify the bean name and a method to call. The element text is also pushed to the target bean. In this example, the text is a publish comment.

With the use of Ant's dynamic attributes feature, we also push required properties to the target object. Usually when an attribute is parsed in an Ant file, a setter is called on the task class. With dynamic attributes, a non-object property or field is added to the object with the setDynamicAttribute() method. In essence, this property injection provides an override capability, since the container could have already wired in the properties in the hosted bean. But, won't this complicate configuration? Will we have to maintain properties for use by Ant tasks and also properties needed by the managed objects?

  • Print
  • Feedback

Resources