Tutorial:

Solving the Constructor Ambiguity Problem with Spring 3 & Annotations

By Jason Tee & Cameron McKenzie

TheServerSide.com

Previous Spring 3 Track Tutorials:

Tutorial I: Setting Up & Configuring The Spring 3 Environment

Tutorial II: SpringWithout XML: The Basics of Spring IoC using Annotations & XML Files


 

In a previous tutorial, we stepped through the process of configuring a Spring container and hacking out some Java code that would leverage the Spring container's formidable powers of IoC (Inversion of Control). The final result of our hard work in the previous tutorials was to get the Spring container to spit out an instance of the GameSummary class we created. It's a simple class that represented the results of playing a Rock-Paper-Scissors game. Here's what it looks like, if you're coming into this particular tutorial without having seen the previous ones:


package com.mcnz.spring;
 public class GameSummary {

  private String clientChoice, serverChoice, result;
  private java.util.Date date = null;

  String[] choices = {"rock", "paper", "scissors"};
  String[] results = {"win", "lose", "tie"};

  public GameSummary(){}

  public String getClientChoice() { return clientChoice; }
  public void setClientChoice(String clientChoice) {
    this.clientChoice = clientChoice;
  }
  public String getServerChoice() { return serverChoice; }
  public void setServerChoice(String serverChoice) {
    this.serverChoice = serverChoice;
  }
  public String getResult() { return result; }
  public void setResult(String result) {
    this.result = result;
  }
  public java.util.Date getDate() { return date; }
  public void setDate(java.util.Date date) {
    this.date = date;
  }
  public String toString() {
    return clientChoice +
        ":" + serverChoice +
            ":" + result + ":" + date;
  }
}


To spit out an instance of this class using Spring without XML, all we needed to do was write a little configuration file, and sprinkle on a few @Bean and @Configuration annotations. We also decided that we wanted the GameSummary bean, which represents the results of a "Rock Paper Scissors" game, to be initialized with properties that indicated that the client had won by playing "paper", so we wrote our little configuration class like so:


package com.mcnz.spring;
import org.springframework.context.annotation.*;
@Configuration
public class SummaryConfig {
  @Bean
  public GameSummary clientWinsWithPaper() {
    GameSummary gs = new GameSummary();

    gs.setClientChoice("paper");
    gs.setServerChoice("rock");
    gs.setResult("win");

    return gs;
  }
}


To me, I like that. I think it looks nice. I can see a GameSummay instance being created in the clientWinsWithPaper() method, and I can see the properties getting initialized. Any sane Java programmer would appreciate this. Insane programmers take a different approach, and use the Spring configuration file, and pollute their applications with XML. Here's how you'd achieve the same result using an Spring configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="clientWinsWithPaper" class="com.mcnz.spring.GameSummary" >

    <property name="clientChoice" value="paper"/>
    <property name="serverChoice" value="rock"/>
    <property name="result" value="win"/>

</bean>
</beans>

If your environment is set up properly, you can run both examples, that is, Spring with XML, and Spring without XML, in the same stand-alone application. It would look something like this:


package com.mcnz.spring;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.io.*;
public class SumRunner {
  public static void main(String args[]){
    /* Spring IoC Without XML */
    AnnotationConfigApplicationContext context
      = new AnnotationConfigApplicationContext(SummaryConfig.class);
    GameSummary gsA
      = context.getBean("clientWinsWithPaper", GameSummary.class);
    /* Spring IoC with XML */
    Resource resource = new ClassPathResource("summary.xml");
    BeanFactory beanFactory = new XmlBeanFactory(resource);
    GameSummary gsX
      = (GameSummary)beanFactory.getBean("clientWinsWithPaper");
    System.out.println(gsA);
    System.out.println(gsX);
  }
}


To be honest, this example doesn't really demonstrate an overwhelming divide between the benefits of using annotations, as compared to XML, in your Spring applications. A more convincing example might be to look at using a constructor to set initial properties of a given JavaBean, as opposed to simply configuring bean properties through setters. Spring initializations through constructors really tend to highlight some of the major disadvantages of using an XML file for Spring configuration.

Initializing Bean Properties Through A Constructor

If we need to initialize the clientChoice and serverChoice properties of the GameBean as soon as the bean is created, we should be doing all of this through a constructor; So, I'm going to add a constructor to my GameSummary class that does just that. Some multivariable calculus will be needed to figure out the result based on the data coming in. I'll allow you to tackle that linear algebra on your own, if you're so inclined.

public GameSummary(String c, String s){
  clientChoice = c;
  serverChoice = s;
  /* result = ???; */
}

Using this constructor to initialize the clientChoice and serverChoice properties by using the @Configuration bean is a lead pipe cinch. You just call the constructor in your Java code. Here's how it looks, with the previous calls to the setter methods all commented out:

package com.mcnz.spring;
import org.springframework.context.annotation.*;
@Configuration
public class SummaryConfig {
  @Bean
  public GameSummary clientWinsWithPaper() {
    GameSummary gs = new GameSummary("rock", "paper");
    //gs.setClientChoice("paper");
    //gs.setServerChoice("rock");
    //gs.setResult("win");
    return gs ;
  }
}

To make the equivalent constructor call using XML, you need to use the constructor-arg element, and pass in your initialization data through the value attribute. You don't actually need to specify the argument type, as Spring will try and find a constructor that best matches the arguments provided. Here's how it looks:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="clientWinsWithPaper" class="com.mcnz.spring.GameSummary" >
  
 <constructor-arg value="rock"/>
  <constructor-arg value="paper"/>

</bean>
</beans>

Although the configuration details in the Spring container have changed, the client code that references does not, so you can just go ahead and re-run the SumRunner class. This time, the output will reflect the fact that both the clientGesture and the serverGesture have been initialized to non-null values.

rock:paper:null:null
paper:rock:null:null


Introducing Constructor Ambiguity Problems with Spring

Now, just for arguments sake, lets say we added a constructor that initialized the clientGesture and the result property. The serverGesture property will remain null. We can't have two constructors that have the same argument signature, so instead of passing in a String and a String, we will use the index of the choices and results array that has been hanging around unused in the GameSummary class:

String[] choices = { "rock", "paper", "scissors" };
String[] results = { "win", "lose", "tie" };

So, to initialize the clientGesture and result properties using a constructor, and by leveraging the appropriate index of these two arrays, the following constructor needs to be added to the GameSummary class:

public GameSummary(int c, int r) {
  clientChoice = choices[c];
  result = results[r];
}

With the constructor coded, I'm going to add a new method to the @Configuration class that spits out a GameSummary instance that indicates that the client has won with the choice of "rock." Really, the code for this new clientWinsWithRock method couldn't be more simple.


@Bean
public GameSummary clientWinsWithRock() {
  GameSummary gs = new GameSummary(0, 1);
  return gs ;
}


However, things get a bit more complicated with the XML file. If someone was to just add the following entry to their Spring configuration file, you'd end up with some very buggy code:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="clientWinsWithPaper" class="com.mcnz.spring.GameSummary" >
  <constructor-arg value="rock"/>
  <constructor-arg value="paper"/>
</bean>

<bean id="clientWinsWithRock" class="com.mcnz.spring.GameSummary" >
  <constructor-arg value="0"/>
  <constructor-arg value="1"/>
</bean>

</beans>


If you adjust your SumRunner to pull the clientWithRock instances out of both the annotation and XML configured Spring containers, and then ran your code, you'd get the following erroneous results:

rock:null:win:null
0:1:null:null

Now, there is a simple solution to this problem. All you need to do is go into your XML file and add type attributes to the constructor-arg elements. Of course, this is simple if you completely understand the problem, are an expert in Spring, and always know exactly where to go in that XML configuration file that is pushing over a thousand bean entries. And note, this is not an error, either at compilation time or at runtime. This is just a plain old bug, and it's not a bug in Spring. It's a bug that has entered into our code because we didn't know any better. Here's the fix.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="clientWinsWithPaper" class="com.mcnz.spring.GameSummary" >
  <constructor-arg type="java.lang.String" value="rock"/>
  <constructor-arg type="java.lang.String" value="paper"/>
</bean>

<bean id="clientWinsWithRock" class="com.mcnz.spring.GameSummary" >
  <constructor-arg type="int" value="0" />
  <constructor-arg type="int" value="1"/>
</bean>

</beans>

Notice that we now have type attributes for both the clientWinsWithRock and clientWinsWithPaper beans. When we run the SumRunner class, we now get the expected, bug free results:

rock:null:win:null
rock:null:win:null


Now, one thing to emphasize here is how simple it was to write some Java code that calls the appropriate constructor, as opposed to the bug-laden path of using XML. Sure, our bug was due to a lack of understanding of both the problem domain, and how Spring works, so we really have nobody to blame but ourselves. And of course, we solved the problem, but still, imagine this was a big project. How long would it take to recompile your workspace, rerun your test suites, document the bug, and then restage your application. Sure, I'm being dramatic. But compare the trials and tribulations of debugging a Spring XML problem to a similar type of error in the @Configuration file:

Notice how when we write some bad code in our Configuration class we get a design-time error. This code will never create a runtime bug, becuase it will never even make it into production. You won't have to redeploy, or restage, or even troubleshoot a running application because the error gets flagged right here, right away. And smart IDEs like NetBeans and Eclipse will actually tell you what the problem is and how to fix it. Your mileage may vary, but for my money, I'd much rather be correcting errors earlier in the development cycle, as opposed to trying to figure what went wrong later on. And that's just one of the many reasons sane developers much prefer using @Configuration classes, and always try to develop their applications that use Spring without XML.

Even More Constructor Ambiguity

So far, we have demonstrated that we can initialize the various properties of the GameSummary bean by either passing in a String, or by passing in the appropriate index to either the choices or results array that is defined in the GameSummary class. What if we wanted to add a couple of constructors that combined the two? Why would we want to do this? Well, for no good reason other than to demonstrate how badly XML configurations suck, but it's a good example, so watch. Here we add two new constructors to the GameSummary class:


public GameSummary(int c, String r){
  clientChoice = choices[c];
  result = r;
}

public GameSummary(String c, int r) {
  clientChoice = c;
  result = results[r];
}


So, to create a new clientWinsWithScissors scenario in the SummaryConfig class, we could simply add a method that returns a GameSummary instance that is created by calling the constructor that takes a String and an int. Here's how it looks:

@Bean
public GameSummary clientWinsWithScissors() {
  GameSummary gs = new GameSummary(2,"win");
  return gs;
}

A clever developer who knows a bit about Spring might go off and make the following entry in the configuration file to achieve the same results as invoking the

<bean id="clientWinsWithScissors" class="com.mcnz.spring.GameSummary" >
  <constructor-arg type="int" value="2" />
  <constructor-arg type="String" value="win"/>
</bean>

Of course, a clever developer that wrote the above XML would have just introduced a bug into the application, because running their application would have triggered the following output:

scissors:null:win:null
win:null:tie:null

The first line, which is the output from the bean generated by the annotation configuration file, is correct. However, the second line that is generated from using the XML configuration file is incorrect.

You see, the clientWinsWithScissors entry in the XML file indicates that a constructor with an int and a String should be invoked. However, it says nothign about which order the arguments should be matched in the constructor, despite the fact that the int value is first, and the String value comes second. As a result, at runtime, the Spring container may actually pick the constructor that takes a String and an int, in that order, rather than the other way around. When this happens, the clientGesture gets initialized to the value intended for the result, an the result gets initialized to the index of the results array that corresponds to the index of the choices array that was originally intended. As a result, you get the erroneous result of win:null:tie:null.

Of course, there is a way to fix this. We want to call the GameSummary constructor where the int is in the first position, and the String is in the second position of the method signature. To do this, we add the extra attribute of index to the constructor-arg element, and use zero based counting to specify where the argument is situated. So, the int would be in index zero, and the String would be at index one.

<bean id="clientWinsWithScissors" class="com.mcnz.spring.GameSummary" >
  <constructor-arg type="int" value="2" index="0" />
  <constructor-arg type="String" value="win" index="1" />
</bean>

Sure, it's a simple fix, and if you know what you're doing, and you're paying attention, you should get it right the first time around. But then again, if you're using an annotation based Spring configuration file, this type of a bug isn't even a possibility, and I much prefer bugs that aren't possible to the ones that are.

 

Previous Spring 3 Track Tutorials:

Tutorial I: Setting Up & Configuring The Spring 3 Environment

Tutorial II: SpringWithout XML: The Basics of Spring IoC using Annotations & XML Files
Tutorial III: Solving the Spring Constructor Ambiguity Problem

 

Recommended Books for Learning Spring

Spring in Action  ~ Craig Walls
Spring Recipes: A Problem-Solution Approach  ~ Gary Mak
Professional Java Development with the Spring Framework ~ Rod Johnson PhD
Pro Java EE Spring Patterns: Best Practices and Design Strategies ~ Dhrubojyoti Kayal

14 Jun 2010