Skip to content

2. Article 1 - Spring IoC

Objectives of this document:

  • to explore the configuration and integration possibilities of the Spring framework (http://www.springframework.org)
  • define and use the concept of IoC (Inversion of Control), also known as Dependency Injection

2.1. Configuring a 3-tier application with Spring

Consider a classic 3-tier application:

We will assume that access to the business and DAO layers is controlled by Java interfaces:

  1. the [IArticlesDao] interface for the data access layer
  1. the [IArticlesManager] interface for the business layer

In the data access layer, or DAO (Data Access Object) layer, it is common to work with a DBMS and therefore with a JDBC driver. Consider the skeleton of a class accessing an articles table in a DBMS:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

    // connection to the data source
    private String driverClassName=null;
    private Connection connection = null;
    private String url = null;
    private String user = null;
    private String pwd = null;
 ....

    public List getAllArticles() {
        // request the list of articles
        try {
            // Load the JDBC driver
            Class.forName(driverClassName);
            // create a connection to the database
            connection = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

To perform an operation on the DBMS, every method requires a [Connection] object that represents the connection to the database, through which data will be exchanged between the database and the Java code. To create this object, four pieces of information are required:

String driverClassName
the name of the DBMS JDBC driver class
String url
the JDBC URL of the database to be used
String user
the credentials used to establish the connection
String pwd
The password for this username

How can our previous [ArticlesDaoPlainJdbc] class obtain this information? There are several possibilities:

Solution 1 - the information is hard-coded in the class:

1
2
3
4
5
6
7
8
public class ArticlesDaoPlainJdbc implements IArticlesDao {

    // connection to the data source
    private final String driverClassName = "org.firebirdsql.jdbc.FBDriver";
    private String url = "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb";
    private String user = "someone";
    private String pwd = "somepassword";
 ....

The drawback of this solution is that you have to modify the Java code whenever any of this information changes, such as when the password is changed.

Solution 2 - the information is passed to the object during its construction:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

    // connection to the data source
    private final String driverClassName;
    private String url;
    private String user;
    private String pwd;
 ....
    public ArticlesDaoPlainJdbc(String driverClassName, String url, String user, String pwd) {
      this.driverClassName = driverClassName;
    this.url = url;
    this.user = user;
    this.pwd = pwd;
    ...
    }

Here, the object receives the information it needs to function when it is constructed. The problem is then shifted to the code that passed it the four pieces of information. How did it obtain them? The following class [ArticlesManagerWithDataBase] in the business layer could construct an object [ArticlesDaoPlainJdbc] from the data access layer:

public class ArticlesManagerWithDataBase implements IArticlesManager {

    // a data access instance
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
        // creation of the data access service
        articlesDao = (IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName, url, user, pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

We can see that, once again, the information needed to construct the [ArticlesDaoPlainJdbc] object is provided to the constructor of the [ArticlesManagerWithDataBase] object. We can imagine that this information is passed to it by a higher layer, such as the user interface layer. We thus gradually reach the highest layer of the application. Due to its position, this layer is not called by a layer that could pass it the configuration information it needs. We must therefore find an alternative to constructor-based configuration. The standard approach for configuring an application at its highest layer is to use a file containing all information likely to change over time. There may be multiple such files. Upon application startup, an initialization layer will then create all or part of the objects required by the application’s various layers.

There is a wide variety of configuration files. The current trend is to use XML files. This is the approach taken by Spring. The file configuring an [ArticlesDaoPlainJdbc] object might look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- the data access class -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
</beans>

An application is a set of objects that Spring calls beans, because they follow the JavaBean standard for naming accessors and initializers (getters/setters) of an object’s private fields. Objects in an application that serve a specific purpose are often created as a single instance. These are called singletons. Thus, in our multi-tier application example discussed here, access to the article database will be handled by a single instance of the [ArticlesDaoPlainJdbc] class. For a web application, these service objects serve multiple clients at once. A service object is not created for each client.

The Spring configuration file above allows for the creation of a single service object of type [ArticlesDaoPlainJdbc] in a package named [istia.st.articles.dao]. The four pieces of information required by the constructor of this object are defined within a <bean>...</bean> tag. There will be as many such <bean> tags as there are singletons to be created.

When will the objects defined in the Spring file be constructed? Application initialization can be handled by the application’s main method, if it has one. For a web application, this might be the [init] method of the main servlet. Every application has a method that is guaranteed to be the first to execute. It is generally within this method that the construction of singletons takes place.

Let’s take an example. Suppose we want to test the previous [ArticlesDaoPlainJdbc] class using a JUnit test. A JUnit test class has a [setUp] method that is executed before any other method. This is where we will create the [ArticlesDaoPlainJdbc] singleton.

If we follow the approach of passing configuration information via the constructor, we will have the following test class:

public class TestArticlesPlainJdbc extends TestCase {
    // tests the ArticlesDaoPlainJdbc class
    // the data source is defined in sprintest

    // an instance of the class under test
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception{
        // retrieves a data access instance
        articlesDao =
            (IArticlesDao) new ArticlesDaoPlainJdbc("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

The calling class [TestArticlesPlainJdbc] must know the four pieces of information required to initialize the [ArticlesDaoPlainJdbc] singleton to be constructed.

If we follow the approach of passing configuration information via a configuration file, we could have the following test class using the Spring file described above.

public class TestSpringArticlesPlainJdbc extends TestCase {
    // tests the ArticlesDaoJdbc class for accessing articles
    // the data source is defined in sprintest

    // an instance of the class under test
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
      // retrieves a data access instance
      articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesPlainJdbc.xml"))).getBean("articlesDao");
    }

Here, the calling class [TestSpringArticlesPlainJdbc] does not need to know the information required to initialize the singleton to be constructed. It simply needs to know:

  1. [springArticlesPlainJdbc.xml]: the name of the Spring configuration file described above
  2. [articlesDao]: the name of the singleton to be created

A modification to the configuration file, outside of these two entities, has no impact on the Java code. This method of configuring an application’s objects is very flexible. To configure itself, the application needs to know only two things:

  • the name of the Spring file containing the definitions of the singletons to be created
  • the names of these singletons, which the Java code uses to obtain a reference to the objects they have been associated with via the configuration file

2.2. Dependency Injection and Inversion of Control

Let’s now introduce the concept of Dependency Injection used by Spring to configure applications. The term Inversion of Control (IoC) is also used. Consider the construction of the [ArticlesManagerWithDataBase] singleton in the business layer of our application:

To access data from the DBMS, the business layer must use the services of an object implementing the [IArticlesDao] interface, for example, an object of type [ArticlesDaoPlainJdbc]. The code for the [ArticlesManagerWithDataBase] class might look like the following:

public class ArticlesManagerWithDataBase implements IArticlesManager {

    // a data access instance
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
        // creation of the data access service
        articlesDao = (IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName, url, user, pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

The [ArticlesDaoPlainJdbc] class is supposed to implement the [IArticlesDao] interface here:

public class ArticlesDaoPlainJdbc implements IArticlesDao {...}

To create the [IArticlesDao] singleton required for the class to function, its constructor explicitly uses the name of the class that implements the [IArticlesDao] interface:

articlesDao = (IArticlesDao) new ArticlesDaoPlainJdbc(...);

We therefore have a hard-coded dependency on the class name in the code. If the class implementing the [IArticlesDao] interface were to change, the code in the previous constructor would need to be modified. We have the following relationships between the objects:

The [ArticlesManagerWithDataBase] class itself takes the initiative to create the [ArticlesDaoPlainJdbc] object it needs. Returning to the term "inversion of control," we can say that it is the one that has the "control" to create the object it needs.

If we were to write a JUnit test class for the [ArticlesManagerWithDataBase] class, it might look something like this:

public class TestArticlesManagerWithDataBase extends TestCase {
    // an instance of the business class under test
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
        // creates an instance of the business class being tested
        articlesManager =
            (IArticlesManager) new ArticlesManagerWithDataBase("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

The test class creates an instance of the business class [ArticlesManagerWithDataBase], which in turn creates an instance of the data access class [ArticlesDaoPlainJdbc] in its constructor.

The Spring solution eliminates the need for the business class [ArticlesManagerWithDataBase] to know the name [ArticlesDaoPlainJdbc] of the data access class it requires. This allows the class to be changed without modifying the Java code of the business class. Spring enables the simultaneous creation of both singletons: one for the data access layer and one for the business layer. The Spring configuration file will define a new bean:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- the data access class -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
</beans>

The new feature is the bean defining the singleton of the business class to be created:

    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
  1. The class implementing the [articlesManager] bean is defined: [ArticlesManagerWithDataBase]
  2. The [articlesDao] field of the bean is assigned a value via the <property name="articlesDao"> tag. This is the field defined in the [ArticlesManagerWithDataBase] class:
public class ArticlesManagerWithDataBase implements IArticlesManager {

  // data access interface
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

For the [articlesDao] field to be initialized by Spring and its <property> tag, the field must follow the JavaBean standard and there must be a [setArticlesDao] method to initialize the [articlesDao] field. Note that the method name is derived precisely from the field name. Similarly, there is often a [get...] method to retrieve the field’s value. Here, it is the [getArticlesDao] method. In this new version, the [ArticlesManagerWithDataBase] class no longer has a constructor. It no longer needs one.

  • The value that Spring will assign to the [articlesDao] field is that of the [articlesDao] bean defined in its configuration file:
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
    .............
    </bean>
  • When Spring constructs the [ArticlesManagerWithDataBase] singleton, it will also create the [ArticlesDaoPlainJdbc] singleton:
    • Spring will establish a dependency graph of the beans and see that the [articlesManager] bean depends on the [articlesDao] bean
    • it will construct the [articlesDao] bean, i.e., an object of type [ArticlesDaoPlainJdbc]
    • then it will construct the [articlesManager] bean of type [ArticlesManagerWithDataBase]

Now let’s imagine a JUnit test for the [ArticlesManagerWithDataBase] class. It might look like the following:

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // tests the business class [ArticlesManagerWithDataBase]

    // an instance of the business class being tested
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
      // retrieves a data access instance
      articlesManager = (IArticlesManager) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesManagerWithDataBase.xml"))).getBean("articlesManager");
    }

Let’s follow the creation process of the two singletons defined in the Spring file named [springArticlesManagerWithDataBase.xml].

  • The [setUp] method above requests a reference to the bean named [articlesManager]
  • Spring consults its configuration file, finds the [articlesManager] bean. If it has already been created, it simply returns a reference to the object (singleton); otherwise, it creates it.
  • Spring detects the dependency of the [articlesManager] bean on the [articlesDao] bean. It therefore creates the [articlesDao] singleton of type [ArticlesDaoPlainJdbc] if it has not already been created (as a singleton).
  • It creates the [articlesManager] singleton of type [ArticlesManagerWithDataBase]

This mechanism could be diagrammed as follows:

Let’s recall the skeleton of the [ArticlesManagerWithDataBase] class:

public class ArticlesManagerWithDataBase implements IArticlesManager {

  // data access interface
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

Once Spring has finished constructing the singletons, we have an object of type [ArticlesManagerWithDataBase] whose [articlesDao] field is initialized without it knowing how. We say that we have injected a dependency into the [ArticlesManagerWithDataBase] object. We also say that we have inverted control: it is no longer the [ArticlesManagerWithDataBase] object that takes the initiative to create the object implementing the [IArticlesDao] interface that it needs; rather, it is the top-level application (when it initializes) that takes care of creating all the objects it needs by managing their interdependencies.

The main benefit of configuring the [ArticlesManagerWithDataBase] singleton via a Spring file is that we can now change the implementation class corresponding to the [articlesDao] field of the [ArticlesManagerWithDataBase] class without modifying its code. All we need to do is change the class name in the definition of the [articlesDao] bean in the Spring file:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
...
    </bean>

will become, for example:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoIbatisSqlMap">
...
    </bean>

The [ArticlesManagerWithDataBase] bean will work with this new data access class without even knowing it.

2.3. Spring IoC in Practice

2.3.1. Example 1

Consider the following class:

package istia.st.springioc.domain;

public class Person {
  private String name;
  private int age;

  // display Person
  public String toString() {
    return "name=[" + this.name + "], age=[" + this.age + "]";
  }

  // init-close
  public void init() {
    System.out.println("initializing person [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("Destroy person [" + this.toString() + "]");
  }

  // getters and setters
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

The class has:

  • two private fields, name and age
  • getter and setter methods for these two fields
  • a toString method to retrieve the value of the [Person] object as a string
  • an init method that will be called by Spring when the object is created, and a close method that will be called when the object is destroyed

To create objects of type [Person], we will use the following Spring file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="person1" class="istia.st.springioc.domain.Person" 
        init-method="init" destroy-method="close">
        <property name="name">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="person2" class="istia.st.springioc.domain.Person" 
        init-method="init" destroy-method="close">
        <property name="name">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
</beans>

This file will be named config.xml.

  • It defines two beans with the respective keys "person1" and "person2" of type [Person]
  • It initializes the [name, age] fields for each person
  • It defines the methods to be called during the initial construction of the object [init-method] and during the destruction of the object [destroy-method]

For our tests, we will use a single JUnit test class to which we will successively add methods. The first version of this class will be as follows:

package istia.st.springioc.tests;

import istia.st.springioc.domain.Person;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

public class Tests extends TestCase {

  // bean factory
  private ListableBeanFactory bf;

  // Initialize tests
  public void setUp() {
    bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
  }

  public void test1() {
    // Retrieve [Person] beans from the Spring file using their keys
    Person person1 = (Person) bf.getBean("person1");
    System.out.println("person1=" + person1.toString());
    Person person2 = (Person) bf.getBean("person2");
    System.out.println("person2=" + person2.toString());
    person2 = (Person) bf.getBean("person2");
    System.out.println("person2=" + person2.toString());
  }
}

Comments:

  • To retrieve the beans defined in the [config.xml] file, we use an object of type [ListableBeanFactory]. There are other types of objects that allow access to beans. The [ListableBeanFactory] object is obtained in the [setUp] method of the test class and stored in a private variable. It will thus be available to all test methods.
  • The [config.xml] file will be placed in the application’s [ClassPath], i.e., in one of the directories searched by the Java Virtual Machine when it looks for a class referenced by the application. The [ClassPathResource] object is used to search for a resource in an application’s [ClassPath], in this case the [config.xml] file.
  • Spring can use configuration files in various formats. The [XmlBeanFactory] object is used to parse a configuration file in XML format.
  • Processing a Spring file returns an object of type [ListableBeanFactory], here the object bf. With this object, a bean identified by the key C is obtained via bf.getBean(C).
  • The [test1] method retrieves and displays the values of the beans with keys "person1" and "person2".

The structure of our application’s Eclipse project is as follows:

Image

Comments:

  • The [src] folder contains the source code. The compiled code will go into a [bin] folder not shown here.
  • The [config.xml] file is located at the root of the [src] folder. The project build automatically copies it to the [bin] folder, which is part of the application’s [ClassPath]. This is where it is looked up by the [ClassPathResource] object.
  • The [lib] folder contains three Java libraries required by the application:
      • commons-logging.jar and spring-core.jar for the Spring classes
      • junit.jar for the JUnit classes
  • The [lib] folder is also part of the application's [ClassPath]

Executing the [test1] method of the JUnit test yields the following results:

Sep 18, 2004 11:28:53 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
Sep 18, 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person1'
init person [name=[Simon], age=[40]]
person1=name=[Simon], age=[40]
Sep 18, 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person2'
init person [name=[Brigitte], age=[20]]
person2=name=[Brigitte], age=[20]
person2=name=[Brigitte], age=[20]

Comments:

  • Spring logs a number of events using the [commons-logging.jar] library. These logs help us better understand how Spring works.
  • The [config.xml] file was loaded and then processed
  • The operation*
Person person1 = (Person) bf.getBean("person1");

forced the creation of the [person1] bean. We can see the Spring log regarding this. Because we had written [init-method="init"] in the definition of the [person1] bean, the [init] method of the created [Person] object was executed. The corresponding message is displayed.

  • The operation
System.out.println("person1=" + person1.toString());

displayed the value of the created [Person] object.

  • The same phenomenon occurs for the [person2] key bean.
  • The last operation
    person2 = (Person) bf.getBean("person2");
    System.out.println("person2=" + person2.toString());

did not result in the creation of a new object of type [Person]. If that had been the case, the [init] method would have been displayed, which is not the case here. This is the principle of the singleton. By default, Spring creates only a single instance of the beans in its configuration file. It is an object reference service. If asked for the reference to an object that has not yet been created, it creates it and returns a reference. If the object has already been created, Spring simply returns a reference to it.

  • Note that there is no trace of the [close] method of the [Person] object, even though we had written [destroy-method=close] in the bean definition. It is possible that this method is only executed when the memory occupied by the object is reclaimed by the garbage collector. By the time this happens, the application has already finished, and writing to the screen has no effect. To be verified.

Now that we have covered the basics of a Spring configuration, we will be able to move through our explanations a bit more quickly.

2.3.2. Example 2

Consider the following new [Car] class:

package istia.st.springioc.domain;

public class Car {
  private String brand;
  private String model;
  private Person owner;

  // constructors

  public Car() {
  }

  public Car(String make, String type, Person owner) {
    this.make = make;
    this.model = model;
    this.owner = owner;
  }

  // toString
  public String toString() {
    return "Car: make=[" + this.make + "] type=[" + this.type
        + "] owner=[" + this.owner + "]";
  }

    // getters-setters
  public String getBrand() {
    return brand;
  }

  public void setBrand(String brand) {
    this.brand = brand;
  }

  public Person getOwner() {
    return owner;
  }

  public void setOwner(Person owner) {
    this.owner = owner;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

  // init-close
  public void init() {
    System.out.println("init car [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy car [" + this.toString() + "]");
  }

}

The class has:

  • three private fields: type, make, and owner. These fields can be initialized and read using the public get and set methods. They can also be initialized using the Car(String, String, Person) constructor. The class also has a no-argument constructor to comply with the JavaBean standard.
  • a toString method to retrieve the value of the [Car] object as a string
  • an init method that will be called by Spring immediately after the object is created, a close method that will be called when the object is destroyed

To create objects of type [Car], we will use the following Spring [config.xml] file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="person1" class="istia.st.springioc.domain.Person" 
        init-method="init" destroy-method="close">
        <property name="name">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="person2" class="istia.st.springioc.domain.Person" 
        init-method="init" destroy-method="close">
        <property name="name">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
    <bean id="car1" class="istia.st.springioc.domain.Car" 
        init-method="init" destroy-method="close">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref bean="person2"></ref>
        </constructor-arg>
    </bean>
</beans>

This file adds a bean with the key "car1" of type [Car] to the previous definitions. To initialize this bean, we could have written:

    <bean id="car1" class="istia.st.springioc.domain.Car" 
        init-method="init" destroy-method="close">
        <property name="brand">
            <value>Peugeot</value>
        </property>
        <property name="type">
            <value>307</value>
        </property>
        <property name="owner">
            <ref bean="person2"/>
        </property>
    </bean>

Rather than choosing the method already presented, we have chosen here to use the class’s Car(String, String, Person) constructor. Additionally, the [car1] bean defines the method to be called during the object’s initial construction [init-method] and the method to be called during the object’s destruction [destroy-method].

For our tests, we will use the JUnit test class already presented, adding the following [test2] method to it:

1
2
3
4
5
  public void test2() {
    // retrieve the [car1] bean
    Car Car1 = (Car) bf.getBean("car1");
    System.out.println("Car1=" + Car1.toString());
  }

The [test2] method retrieves the [car1] bean and displays it.

The structure of the Eclipse project remains the same as in the previous test. Executing the [test2] method of the JUnit test yields the following results:

Sep 18, 2004 2:56:10 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from the classpath resource [config.xml]
Sept. 18, 2004 2:56:10 PM org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'car1'
Sep 18, 2004 2:56:10 PM org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person2'
init person [name=[Brigitte], age=[20]]
Sep 18, 2004 2:56:10 PM org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'car1' instantiated via constructor [public istia.st.springioc.domain.Car(java.lang.String,java.lang.String,istia.st.springioc.domain.Person)]
init car [Car: make=[Peugeot] model=[307] owner=[name=[Brigitte], age=[20]]]
Car1=Car : make=[Peugeot] model=[307] owner=[name=[Brigitte], age=[20]]

Comments:

  1. The [test2] method requests a reference to the [car1] bean
  2. Line 4: Spring begins creating the [car1] bean because this bean has not yet been created (singleton)
  3. line 6: because the bean [car1] references the bean [person2], the latter bean is in turn constructed
  4. line 7: the bean [person2] has been created. Its [init] method is then executed.
  5. Line 9: Spring indicates that it will use a constructor to create the bean [car1]
  6. line 10: the bean [car1] has been created. Its [init] method is then executed.
  7. line 11: the [test2] method displays the value of the [car1] bean

2.3.3. Example 3

We introduce the following new class [PersonGroup]:

package istia.st.springioc.domain;

import java.util.Map;

public class PeopleGroup {
  private Person[] members;
  private Map workingGroups;

  // getters - setters
  public Person[] getMembers() {
    return members;
  }

  public void setMembers(Person[] members) {
    this.members = members;
  }

  public Map getWorkGroups() {
    return workGroups;
  }

  public void setWorkGroups(Map workGroups) {
    this.workGroups = workGroups;
  }

  // display
  public String toString() {
    String list = "members: ";
    for (int i = 0; i < this.members.length; i++) {
      list += "[" + this.members[i].toString() + "]";
    }
    return list + ", working groups = " + this.workingGroups.toString();
  }

  // init-close
  public void init() {
    System.out.println("init PeopleGroup [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy PeopleGroup [" + this.toString() + "]");
  }
}

Its two private members are:

members: an array of people who are members of the group

workGroups: a dictionary mapping a person to a work group

Note here that the [PeopleGroup] class does not define a no-argument constructor to comply with the JavaBean standard. Recall that in the absence of any constructor, there is a "default" constructor, which is the no-argument constructor that does nothing.

The goal here is to demonstrate how Spring allows for the initialization of complex objects, such as those with array or dictionary fields. We add a new bean to the previous Spring [config.xml] file:

    <bean id="groupe1" class="istia.st.springioc.domain.GroupePersonnes" 
        init-method="init" destroy-method="close">
        <property name="members">
            <list>
                <ref bean="person1"/>
                <ref bean="person2"/>
            </list>
        </property>
        <property name="workgroups">
            <map>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Human Resources</value>
                </entry>
            </map>
        </property>
    </bean>
  1. The <list> tag allows you to initialize a field of type array or one that implements the List interface with different values.
  2. The <map> tag allows you to do the same thing with a field that implements the Map interface

For our tests, we will use the JUnit test class already presented, adding the following [test3] method to it:

1
2
3
4
5
  public void test3() {
    // Retrieve the [group1] bean
    PeopleGroup group1 = (PeopleGroup) bf.getBean("group1");
    System.out.println("groupe1=" + groupe1.toString());
  }

The [test3] method retrieves the [groupe1] bean and displays it.

The structure of the Eclipse project remains the same as in the previous test. Executing the [test3] method of the JUnit test yields the following results:

Sep 18, 2004 3:51:45 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
Sep 18, 2004 3:51:45 PM org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'groupe1'
Sep 18, 2004 3:51:45 PM org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person1'
init person [name=[Simon], age=[40]]
Sep 18, 2004 3:51:45 PM org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person2'
init person [name=[Brigitte], age=[20]]
init PeopleGroup [members: [name=[Simon], age=[40]][name=[Brigitte], age=[20]], workgroups = {Brigitte=Marketing, Simon=Human Resources}]
group1 = members: [name=[Simon], age=[40]][name=[Brigitte], age=[20]], workgroups = {Brigitte=Marketing, Simon=Human Resources}

Comments:

  • The [test3] method requests a reference to the [group1] bean
  • line 4: Spring begins creating this bean
  • Because the [group1] bean references the [person1] and [person2] beans, these two beans are created (lines 6 and 9) and their init methods are executed (lines 7 and 10)
  • Line 11: The [group1] bean has been created. Its [init] method is now executed.
  • Line 12: Display requested by the [test3] method.

2.4. Spring for configuring three-tier web applications

2.4.1. General application architecture

We want to build a 3-tier application with the following structure:

  • The three layers will be made independent through the use of Java interfaces
  • The integration of the three layers will be handled by Spring
  • We will create separate packages for each of the three layers, which we will call Control, Domain, and Dao. An additional package will contain the test applications.

The application structure in Eclipse could be as follows:

Image

2.4.2. The DAO data access layer

The DAO layer will implement the following interface:

package istia.st.demo.dao;

public interface IDao1 {
  public int doSomethingInDaoLayer(int a, int b);
}
  • Write two classes, Dao1Impl1 and Dao1Impl2, that implement the IDao1 interface. The Dao1Impl1.doSomethingInDaoLayer method will return a+b, and the Dao1Impl2.doSomethingInDaoLayer method will return a-b.
  • Write a JUnit test class to test the two previous classes

2.4.3. The business layer

The business layer will implement the following interface:

package istia.st.demo.domain;

public interface IDomain1 {
  public int doSomethingInDomainLayer(int a, int b);
}
  • Write two classes, Domain1Impl1 and Domain1Impl2, that implement the IDomain1 interface. These classes will have a constructor that takes a parameter of type IDao1. The Domain1Impl1.doSomethingInDomainLayer method will increment a and b by one, then pass these two parameters to the doSomethingInDaoLayer method of the received IDao1 object. The Domain1Impl2.doSomethingInDomainLayer method, on the other hand, will decrement a and b by one before doing the same thing.
  • Write a JUnit test class to test the two previous classes

2.4.4. The user interface layer

The user interface layer will implement the following interface:

package istia.st.demo.control;

public interface IControl1 {
  public int doSomethingInControlLayer(int a, int b);
}
  • Write two classes, Control1Impl1 and Control1Impl2, that implement the IControl1 interface. These classes will have a constructor that takes a parameter of type IDomain1. The Control1Impl1.doSomethingInControlLayer method will increment a and b by one, then pass these two parameters to the doSomethingInDomainLayer method of the received IDomain1 object. The Control1Impl2.doSomethingInControlLayer method, on the other hand, will decrement a and b by one before doing the same thing.
  • Write a JUnit test class to test the two previous classes

2.4.5. Integration with Spring

  • Write a Spring configuration file that will determine which classes each of the three previous layers should use
  • Write a JUnit test class using different Spring configurations to highlight the flexibility of the application
  • Write a standalone application (main method) that passes two parameters to the IControl1 interface and displays the result returned by the interface.

2.4.6. A solution

2.4.6.1. The Eclipse project

Image

The archives in the [lib] folder have been added to the project's [ClassPath].

2.4.6.2. The [istia.st.demo.dao] package

The interface:

1
2
3
4
5
6
7
8
9
package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDao1 {
  public int doSomethingInDaoLayer(int a, int b);
}

A first implementation class:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public class Dao1Impl1 implements IDao1 {

    // do something in the [dao] layer
  public int doSomethingInDaoLayer(int a, int b) {
    return a + b;
  }

}

A second implementation class:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *
 */
public class Dao1Impl2 implements IDao1 {

    // do something in the [dao] layer
  public int doSomethingInDaoLayer(int a, int b) {
    return a - b;
  }
}

2.4.6.3. The [istia.st.demo.domain] package

The interface:

package istia.st.demo.domain;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDomain1 {

    // do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b);
}

A first implementation class:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl1 implements IDomain1 {

    // the service for accessing the [DAO] layer
  private IDao1 dao1;

  public Domain1Impl1() {
    // constructor without arguments
  }

    // stores the data access object [DAO]
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

    // do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b) {
    a++;
    b++;
    return dao1.doSomethingInDaoLayer(a, b);
  }
}

A second implementation class:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl2 implements IDomain1 {

    // the service for accessing the [DAO] layer
  private IDao1 dao1;

  public Domain1Impl2() {
    // constructor without arguments
  }

    // stores the data access object [DAO]
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

    // do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b) {
    a--;
    b--;
    return dao1.doSomethingInDaoLayer(a, b);
  }
}

2.4.6.4. The package [istia.st.demo.control]

The interface

1
2
3
4
5
6
7
8
9
package istia.st.demo.control;

/**
 * @author ST-ISTIA
 *  
 */
public interface IControl1 {
  public int doSomethingInControlLayer(int a, int b);
}

A first implementation class:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl1 implements IControl1 {
  // business class in the [domain] layer
    private IDomain1 domain1;

  public Control1Impl1() {
    // constructor without arguments
  }

    // instantiation of the access service to the [domain] layer
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

    // do something
  public int doSomethingInControlLayer(int a, int b) {
    a++;
    b++;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

A second implementation class:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl2 implements IControl1 {

    // the access class to the [domain] layer
    private IDomain1 domain1;

  public Control1Impl2() {
    // constructor without arguments
  }

    // stores the layer access class [domain]
  public Control1Impl2(IDomain1 domain1) {
    this.domain1 = domain1;
  }

    // do something
  public int doSomethingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. [Spring] Configuration Files

A first [springMainTest1.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- the DAO class -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl1">
    </bean>
    <!-- the business class -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl1">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
    <!-- the control class -->
    <bean id="control" class="istia.st.demo.control.Control1Impl1">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

A second [springMainTest2.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- the DAO class -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl2">
    </bean>
    <!-- the business class -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl2">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
    <!-- the control class -->
    <bean id="control" class="istia.st.demo.control.Control1Impl2">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

2.4.6.6. The test package [istia.st.demo.tests]

A [main] test:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest1 {
  public static void main(String[] arguments) {
    // retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
    // use the class
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
    // display the result
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Results displayed in the Eclipse console:

March 11, 2005 11:25:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest1.xml]
March 11, 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
March 11, 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
March 11, 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
March 11, 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl1(istia.st.demo.dao.IDao1)]
March 11, 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl1(istia.st.demo.domain.IDomain1)]
control(10,20)=34

Another test using the second [Spring] configuration file:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest2 {
  public static void main(String[] arguments) {
    // retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
    // use the class
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
    // display the result
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Results displayed in the Eclipse console:

March 11, 2005 11:28:52 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest2.xml]
March 11, 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
March 11, 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
March 11, 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
March 11, 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl2(istia.st.demo.dao.IDao1)]
March 11, 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl2(istia.st.demo.domain.IDomain1)]
control(10,20)=-10

Finally, a JUnit test:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

/**
 * @author ST-ISTIA
 *  
 */
public class JunitTest2Control1 extends TestCase {
  public void testControl1() {
    // retrieve an implementation of the IControl1 interface
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
    // we use the class
    int a1 = 10, b1 = 20;
    int res1 = control1.doSometingInControlLayer(a1, b1);
    assertEquals(34, res1);
    // we retrieve another implementation of the IControl1 interface
    IControl1 control2 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
    // we use the class
    int a2 = 10, b2 = 20;
    int res2 = control2.doSometingInControlLayer(a2, b2);
    assertEquals(-10, res2);
  }
}

2.5. Conclusion

The Spring framework offers true flexibility in both application architecture and configuration. We used the IoC concept, one of Spring’s two pillars. The other pillar is AOP (Aspect-Oriented Programming), which we did not cover. It allows you to add “behavior” to a class method through configuration without modifying the method’s code. In simple terms, AOP allows you to filter calls to certain methods:

  • the filter can be executed before or after the target method M, or both.
  • The method M is unaware of these filters. They are defined in the Spring configuration file.
  • The code of method M is not modified. Filters are Java classes that must be implemented. Spring provides predefined filters, particularly for managing DBMS transactions.
  • Filters are beans and, as such, are defined in the Spring configuration file as beans.

A common filter is the transaction filter. Consider a method M in the business layer that performs two inseparable operations on data (a unit of work). It calls two methods, M1 and M2, in the DAO layer to perform these two operations.

Because it resides in the business layer, method M abstracts away the underlying data storage. For example, it does not need to assume that the data is stored in a DBMS or that it must enclose the calls to methods M1 and M2 within a DBMS transaction. It is up to the DAO layer to handle these details. One solution to the previous problem is to create a method in the DAO layer that would itself call methods M1 and M2, wrapping these calls within a DBMS transaction.

The AOP filtering solution is more flexible. It allows you to define a filter that, before calling M, will start a transaction and, after the call, will perform a commit or rollback as appropriate.

There are several advantages to this approach:

  • once the filter is defined, it can be applied to multiple methods, for example, all those that require a transaction
  • the filtered methods do not need to be rewritten
  • since the filters to be used are defined via configuration, they can be changed

In addition to IoC and AOP concepts, Spring provides numerous support classes for three-tier applications:

  • for JDBC, SQLMap (iBatis), Hibernate, and JDO (Java Data Object) in the DAO layer
  • for the MVC model in the User Interface layer

For more information: http://www.springframework.org.