Saturday, May 29, 2010

Wiring Spring Beans

Spring in Action (3rd edition) by Craig Walls is superb. (Spring's documentation is also eminently practical.) In this article, I take Craig's example of a knight, a minstrel and a quest for the holy grail and extend it in order to get a little practice wiring beans together.

I took the example from the first chapter of Spring in Action and added the class QuestExpedition to separate the behavior of how the quest is achieved from the quest as an objective or goal. One design being better than the other would depend on the ubiquitous language of the domain driven design. As I envision it, the personnel participating in the quest and the itinerary belong to a separate concern. I created QuestExpedition to encapsulate such concerns.

In Craig's code, the knight knows upon which quest he is embarked. In real life a knight would know the details of a quest on which he might embark but, in software, we only model the required behavior of the application, not real life. I removed the Knight.embarkOnQuest() method because it only calls Quest.embark(). In my application, I have the QuestExpedition embark rather than have the Knight embark.

QuestApp.java

import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class QuestApp { 
  public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    QuestExpedition expedition = (QuestExpedition)ctx.getBean("expedition");
    expedition.embark();
    expedition.reportStatus();
  }
}

QuestExpedition.java

import java.util.*; 

public class QuestExpedition {
  Quest quest;
  List knights;
  List minstrels;
  List destinations;
 
  public void embark() {
    System.out.println(toString());
  }
 
  public void setQuest(Quest quest) {this.quest = quest;}
  public void setKnights(List knights) {this.knights = knights;}
  public void setMinstrels(List minstrels) {this.minstrels = minstrels;}
  public void setDestinations(List destinations) {this.destinations = destinations;}
 
  public void reportStatus() {
    for (Minstrel m : minstrels) 
      for (Knight k : knights) 
        m.praiseKnight(k, quest); 
  }
 
  @Override
  public String toString() { 
    String comma = "";
    StringBuilder sb = new StringBuilder(); 
    sb.append("Embarking on quest to " + quest.toString());
    sb.append("\n with Knights: ");
    for (Knight k : knights) {  
      sb.append(comma + k.toString());
      comma = ", ";
    }
    sb.append("\n and with Minstrels: ");
    comma = "";
    for (Minstrel m : minstrels) {  
      sb.append(comma + m.toString());
      comma = ", ";
    }
    sb.append("\n Itinerary: ");
    comma = "";
    for (String dest : destinations) {
      sb.append(comma + dest);
      comma = ", ";
    }
    return sb.toString();
  }
}

beans.xml

<?xml version="1.0" encoding="UTF-8"?> 

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"> 

  <bean id="expedition" class="QuestExpedition">
   <property name="quest" ref="quest"/>
   <property name="knights">
    <list>
     <ref bean="count"/>
     <ref bean="duke"/>
     <ref bean="earl"/>
    </list>
   </property>
   <property name="minstrels">
    <list>
     <ref bean="troubadour"/>
    </list>
   </property>
   <property name="destinations">
    <list>
     <value>Rome</value>
     <value>Crete</value>
     <value>Malta</value>
    </list>
   </property>
  </bean>
  
  <bean id="quest" class="SlayDragonQuest">
   <constructor-arg value="Puff" />
  </bean>
  <bean id="count" class="Knight">
   <constructor-arg value="Count" />
   <constructor-arg value="Chuck" />
  </bean>
  <bean id="duke" class="Knight">
   <constructor-arg value="Duke" />
   <constructor-arg value="Danny" />
  </bean>
  <bean id="earl" class="Knight">
   <constructor-arg value="Earl" />
   <constructor-arg value="Earle" />
  </bean>
  <bean id="troubadour" class="Minstrel">
   <constructor-arg value="Troubadour" />
   <constructor-arg value="Steve" />
  </bean>
</beans>

Quest.java

public abstract class Quest {
  String name;
  String description;
  @Override
  public String toString() {
    return description + " " + name;
  }
}

HolyGrailQuest.java

public class HolyGrailQuest extends Quest {
  HolyGrailQuest(String name) {
    this.name = name;
    this.description = "find and acquire the grail"; 
  }
}

RescueDamselQuest.java

public class RescueDamselQuest extends Quest {
  RescueDamselQuest(String name) {
    this.name = name;
    this.description = "rescue the damsel";
  }
}

SlayDragonQuest.java

public class SlayDragonQuest extends Quest {
  SlayDragonQuest(String name) {
    this.name = name;
    this.description = "slay the dragon";
  }
}

Person.java

public class Person {
  final private String name;
  final private String honorific;
  Person(String honorific, String name) {
    this.name = name;
    this.honorific = honorific;
  }
  @Override
  public String toString() {
    return honorific + " " + name;
  }
}

Minstrel.java

public class Minstrel extends Person {
  Minstrel(String honorific, String name) {
    super(honorific, name); 
  }
  public void praiseKnight(Knight knight, Quest quest) {
    System.out.println("Bold " + knight.toString() + " pledged himself to " + quest.toString());
  }
}

Knight.java

public class Knight extends Person { 
  Knight(String honorific, String name) {
    super(honorific, name);
  }
}

ANT build.xml

<project name="GrailsQuest" default="run" basedir=".">
 <property name="src"       location="src/main/java"/> 
 <property name="test.src"  location="src/test/java"/>
 <property name="build"     location="build/classes"/>
 <property name="tests"     location="build/tests"/>
 <property name="spring"    location="${user.home}/dev/lib/spring-framework-3"/>
 
  <path id="classpath">
    <pathelement location="${build}"/>
    <fileset dir="${spring}" includes="**/*.jar"/>
  </path>

  <path id="test.classpath">
    <pathelement path="${classpath}"/>
    <pathelement location="${user.home}/dev/lib/junit/junit-4.6.jar"/>
    <pathelement path="${build}"/>
    <pathelement path="${tests}"/>
  </path>

  <target name="clean">
    <delete dir="${build}"/>
  </target>
 
  <target name="init" depends="clean">
    <mkdir dir="${build}"/>
    <mkdir dir="${tests}"/>
  </target>

  <target name="compile" depends="init">
      <javac srcdir="${src}" 
             destdir="${build}" classpathref="classpath"/>
    <javac srcdir="${test.src}" 
            destdir="${tests}" classpathref="test.classpath"/>
  </target>

  <target name="run" depends="compile">
    <java classname="QuestApp">
      <classpath><path refid="classpath"/></classpath>
    </java>
  </target>
</project>

Running the ANT Command for the Project

[greg:Quest] ant
. . .
     [java] Embarking on quest to slay the dragon Puff
     [java]   with Knights: Count Chuck, Duke Danny, Earl Earle
     [java]   and with Minstrels: Troubadour Steve
     [java]   Itinerary: Rome, Crete, Malta
     [java] Bold Count Chuck pledged himself to slay the dragon Puff
     [java] Bold Duke Danny pledged himself to slay the dragon Puff
     [java] Bold Earl Earle pledged himself to slay the dragon Puff

BUILD SUCCESSFUL
Total time: 5 seconds