Monday, January 14, 2008

Better Application Configuration using Spring Meta-Annotations

One of the common allegations against Spring is that it promotes XML abuse. Since 2.0, Spring has started putting in features in the DI container that provide developers options to minimize XML configuration and promote use of Java 5 annotations. Configuration parameters that are truly external and needs to change across deployments viz. driver pathname, URL base, file names etc. are definite candidates for property files or XML. While those mappings which deal with wiring of application components are better handled through a mechanism that enforces more type-safety than string literals in XML. With the recent releases of Spring, we are getting more and more options towards better configuration of the DI container.

This post talks about my experience in evolution of better configuration options with Spring 2.5 in one of the real life applications. Spring folks have now realized that programmatic configuration options through proper type-safety of the Java language has definite advantages in encouraging code modularity, unit testability and refactorability. Modularization is one of the key focal areas in application construction and integration today, and modules based on a type safe programming language yield better end result than one based on XML files.

In one of my applications, I have the following implementation of a Strategy pattern that models the interest calculation algorithm for various types of accounts. The algorithm varies depending upon some of the attributes of the account. Here is a simplified view of things ..


public interface InterestCalculation {
  BigDecimal calculate(final Account account);
}

public class NormalCheckingInterestCalculation
    implements InterestCalculation {
  //..
}

public class PriorityCheckingInterestCalculation
    implements InterestCalculation {
  //..
}

public class NormalSavingsInterestCalculation
    implements InterestCalculation {
  //..
}

public class PrioritySavingsInterestCalculation
    implements InterestCalculation {
  //..
}



One of the issues with earlier versions of Spring was that the best (or the most recommended) practices espoused writing XML code not only for declaring the beans for every concrete implementation, but also for every collaboration that it takes part in. Hence my XML code also goes on increasing in addition to the Java code. One available option to reduce the growing chunks of XML is to use autowiring, which, being implemented at a very coarse level of granularity in earlier versions of Spring seemed too magical to me to use in production code.

Spring offers options for modularizing XML configurations and implementing hierarchical application contexts. But still I think the recent thoughts towards modularization using typesafe metadata holds more promise. It is good that initiatives like Spring JavaConfig are gaining in importance. When we speak of modularization of application components, one of the very important aspects in it is composition of modules. And XML cannot be a viable option towards scalable module composition. Guice has been doing some promising stuff with modules and composition, but that is another story for another post.

Meanwhile in the Spring land ..

Spring 2.5 implements autowiring at a much finer level of granularity that allows developers to provide explicit controls on the matching closure. Using annotations like @Qualifier, I can control the selection of candidates amongst the multiple matches. However, the default usage of @Qualifier allows only specification of bean names, which once again is in the form of string literals and hence susceptible to typos, usual type-unsafety kludges and refactoring unfriendliness. The real treat, however, is the feature that enables you to use @Qualifier as a meta annotation to implement your own custom qualifiers.

Consider how the new autowiring features in Spring 2.5 improve upon the above example and allow explicit declarative configuration by using fully type-safe annotations in place of XML.

Autowiring based on Annotations

Spring 2.5 allows you to define your own annotations that can act as classifiers for matching candidates in autowiring. For the example above, the various strategies for interest calculation are based on 2 axes of variation - the account type and the customer type. With the earlier variant of using XML as the configuration metadata, these business rules were buried deep within the implementing classes only, and dependency injection was done using explicit bean names as string literals. Here is an annotation that defines the classifiers in Spring 2.5 ..


@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface AccountQualifier {
  AccountType accountType() default AccountType.CHECKING;
  CustomerType customerType() default CustomerType.NORMAL;
}



Note the usage of @Qualifier as the meta-annotation to define custom domain specific annotations.

Now annotate all injected instances of the strategy interface with the annotation @AccountQualifier. The wiring will be done by Spring based on the matching qualifiers.


public class InterestCalculator {
  @Autowired
  @AccountQualifier(
      accountType=AccountType.CHECKING,
      customerType=CustomerType.NORMAL
  )
  private InterestCalculation normalChecking;

  @Autowired
  @AccountQualifier(
      accountType=AccountType.SAVINGS,
      customerType=CustomerType.PRIORITY
  )
  private InterestCalculation prioritySavings;

  //..
}



In the above snippet, the instance normalChecking will be wired with an instance of NormalCheckingInterestCalculation, while prioritySavings will get an instance of PrioritySavingsInterestCalculation. Implement fine grained autowiring with Spring 2.5 and get explicit declarative business rule based configuration as aprt of metadata free!

Now the configuration XML does not have to specify the collaborators. The size goes down, though still we need to specify each of the custom qualifiers associated with the concrete implementation classes of the strategy.


<beans>
    <context:annotation-config/>

    <bean class="org.dg.biz.NormalCheckingInterestCalculation">
        <qualifier type="AccountQualifier">
            <attribute key="accountType" value="CHECKING"/>
            <attribute key="customerType" value="NORMAL"/>
        </qualifier>
    </bean>

    <bean class="org.dg.biz.NormalSavingsInterestCalculation">
        <qualifier type="AccountQualifier">
            <attribute key="accountType" value="SAVINGS"/>
            <attribute key="customerType" value="NORMAL"/>
        </qualifier>
    </bean>

    <bean class="org.dg.biz.PriorityCheckingInterestCalculation">
        <qualifier type="AccountQualifier">
            <attribute key="accountType" value="CHECKING"/>
            <attribute key="customerType" value="PRIORITY"/>
        </qualifier>
    </bean>

    <bean class="org.dg.biz.PrioritySavingsInterestCalculation">
        <qualifier type="AccountQualifier">
            <attribute key="accountType" value="SAVINGS"/>
            <attribute key="customerType" value="PRIORITY"/>
        </qualifier>
    </bean>
    
    <bean id="interestCalculator" class="org.dg.biz.InterestCalculator" />
</beans>



Classpath AutoScan for Managed Components - Even less XML

Spring 2.5 makes it possible to do away with explicit declarations of the concrete implementation classes in the XML file by using the auto-scan feature. This allows the DI container to auto-scan the classpath and detect all wiring candidates based on user specified filters. In our case, we already have the metadata as the selection criteria - hence we can use the same annotation @AccountQualifier as the basis for autoscanning of managed components. So annotate the implementation classes with the appropriate metadata ..


@AccountQualifier(accountType=AccountType.CHECKING, customerType=CustomerType.NORMAL)
public class NormalCheckingInterestCalculation
    implements InterestCalculation {
  //..
}

@AccountQualifier(accountType=AccountType.CHECKING, customerType=CustomerType.PRIORITY)
public class PriorityCheckingInterestCalculation
    implements InterestCalculation {
  //..
}

// similarly the other classes



and include the final snippet in XML that does the last bit of trick to auto-scan the classpath to find out matching entries based on the annotations. The entire erstwhile blob of XML turns into the following snippet of a few lines ..


<context:annotation-config/>

<bean id="interestCalculator" 
      class="org.dg.biz.InterestCalculator"/>

<context:component-scan base-package="org.dg.biz" use-default-filters="false">
  <context:include-filter type="annotation" expression="org.dg.biz.AccountQualifier"/>
</context:component-scan>



That's it! We do not need to declare any of the above beans explicitly for autowiring.

One last point .. in the above example we have used @Qualifier as a meta-annotation. Hence our custom domain specific annotation @AccountQualifier class still has a Spring import of @Qualifier. In keeping with Spring's usual promise of non-intrusiveness, there is a way out of it. If you do not want a Spring dependency in your custom annotation @AccountQualifier, you can do that as well, by adding the following snippet to the above 5 lines of XML ..


<bean id="customAutowireConfigurer" 
      class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
  <property name="customQualifierTypes">
    <set>
      <value>org.dg.biz.AccountQualifier</value>
    </set>
  </property>
</bean>






P.S. Using the same annotation for autowiring and autodetection of managed components does not work in Spring 2.5 because of a bug. Mark Fisher of SpringSource pointed this out to me and verified that it works in Spring 2.5.1. I upgraded my application and things worked fine.

1 comment:

Ivan Memruk said...

A nice article. Thanks.

Just one little thing -- using an annotation from Spring does not create a static dependency on Spring since unknown annotations are ignored by JVM (JSR-175). Therefore, your classes with Spring annotations will be usable even if Spring is not in the classpath. You can use Spring and other frameworks' annotations without any worries.