Tutorial:

Solving the Constructor Ambiguity Problem with Spring 3 and Annotations

TheServerSide.com

In a previous tutorial, we stepped through the process of writing some code, and configuring the Spring container so that it would use it's formidable powers of IoC (Inversion of Control) and spit out an instance of a GameSummary class we created.


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 in the right place. 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 over XML in your Spring applications. A better 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 its setters.

If, say, we want to be initializing the clientChoice and serverChoice properties of the GameBean as soon as it 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 a @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



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[c];
}


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:


08 Jun 2010