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 layer (Data Access Object), it is common to work with a SGBD and therefore with a JDBC driver. Consider the skeleton of a class accessing an article table in a SGBD:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

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

    public List getAllArticles() {
        // the list of items is requested
        try {
             // load the JDBC driver
            Class.forName(driverClassName);
            // create a connection to BD
            connexion = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

To perform an operation on SGBD, any 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 construct this object, four pieces of information are required:

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

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 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 downside 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 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 then shifts to the code that passed it the four pieces of information. How did it obtain them? The following class, [ArticlesManagerWithDataBase], from 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 a 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 object [ArticlesDaoPlainJdbc] is provided to the constructor of the object [ArticlesManagerWithDataBase]. We can imagine that this information is passed to it by a higher-level 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 transmit 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 the information that may change over time. There may be several 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 a [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>
     <!-- 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 getters and setters of an object’s private fields. Objects in an application that are designed to provide a service are often created as a single instance. These are called singletons. Thus, in the multi-tier application example we are examining here, access to the product database will be handled by a single instance of the [ArticlesDaoPlainJdbc] class. In a web application, these service objects serve multiple clients instances at the same time. A service object is not created for each client.

The Spring configuration file above allows you to create 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 constructed.

When will the objects defined in the Spring file be constructed? Application initialization can be delegated to the main method of that application, 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 class [ArticlesDaoPlainJdbc] using a test JUnit. A test class JUnit has a method [setUp] that is executed before any other method. This is where we will create the singleton [ArticlesDaoPlainJdbc].

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

public class TestArticlesPlainJdbc extends TestCase {
    // tests the ArticlesDaoPlainJdbc item access 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 singleton [ArticlesDaoPlainJdbc] 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 item access 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 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

Any changes to the configuration file, outside of these two entities, have 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 the data in SGBD, 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 a data access service
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

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

The class [ArticlesDaoPlainJdbc] is supposed to implement the interface [IArticlesDao] 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 implementation class of the [IArticlesDao] interface were to change, the code of 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 test class JUnit for the class [ArticlesManagerWithDataBase], 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 under test
        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, in its constructor, an instance of the data access class [ArticlesDaoPlainJdbc].

The Spring solution will eliminate the need for the business class [ArticlesManagerWithDataBase] to know the name ([ArticlesDaoPlainJdbc]) of the data access class it requires. This will allow the data access class to be changed without modifying the Java code of the business class. Spring will allow both singletons—the one for the data access layer and the one for the business layer—to be created simultaneously. 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>
     <!-- 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 bean [articlesManager] 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;
  }

In order 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 very 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 singleton [ArticlesManagerWithDataBase], it will also create the singleton [ArticlesDaoPlainJdbc]:
    • Spring will build a dependency graph of the beans and see that the bean [articlesManager] depends on the bean [articlesDao]
    • it will construct the bean [articlesDao], which is an object of type [ArticlesDaoPlainJdbc]
    • then it will construct the bean [articlesManager] of type [ArticlesManagerWithDataBase]

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

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // test business class [ArticlesManagerWithDataBase]

    // an instance of the business class under test
    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 and finds the bean [articlesManager]. 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 bean [articlesManager] on the bean [articlesDao]. It therefore creates the singleton [articlesDao] of type [ArticlesDaoPlainJdbc] if it has not already been created (singleton).
  • It creates the singleton [articlesManager] of type [ArticlesManagerWithDataBase]

This mechanism could be schematized as follows:

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 a dependency has been injected 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; but rather the top-level application (when it initializes) 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 the code of the latter. Simply 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 Personne {
  private String nom;
  private int age;

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

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

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

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

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

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }
}

The class has:

  • two private fields, name and age
  • read (get) and write (set) methods for these two fields
  • a method toString to retrieve the value of the object [Personne] 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 [Personne], 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="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <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 [Personne]
  • It initializes the [nom, age] fields for each person
  • It defines the methods to be called during the initial construction of the [init-method] object and during the destruction of the [destroy-method] object

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

package istia.st.springioc.tests;

import istia.st.springioc.domain.Personne;
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;

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

  public void test1() {
    // retrieve [Person] bean keys from the Spring file
    Personne personne1 = (Personne) bf.getBean("personne1");
    System.out.println("personne1=" + personne1.toString());
    Personne personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
  }
}

Comments:

  • To retrieve the beans defined in the [config.xml] file, we use an object of type [ListableBeanFactory]. There are other object types 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 file [config.xml] will be placed in the application’s [ClassPath], c.a.d, 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 the XML format.
  • Parsing a Spring file yields an object of type [ListableBeanFactory], in this case the object bf. With this object, a bean identified by the key C is obtained via bf.getBean(C).
  • The method [test1] 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 file [config.xml] is located at the root of the [src] folder. Building the project automatically copies it to the [bin] folder, which is part of the application’s [ClassPath] folder. This is where it is located 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:

18 sept. 2004 11:28:53 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person1'
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person2'
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[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*
Personne personne1 = (Personne) bf.getBean("personne1");

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

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

displayed the value of the created object [Personne].

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

did not result in the creation of a new object of type [Personne]. 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 a 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.

  • We can see that there is no trace of the [close] method of the [Personne] object, even though we had written it in the definition of the [destroy-method=close] bean. 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 terminated, and writing to the screen has no effect. To be verified.

Now that we have a solid grasp of 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 class [Voiture]:

package istia.st.springioc.domain;

public class Voiture {
  private String marque;
  private String type;
  private Personne propriétaire;

   // manufacturers

  public Voiture() {
  }

  public Voiture(String marque, String type, Personne propriétaire) {
    this.marque = marque;
    this.type = type;
    this.propriétaire = propriétaire;
  }

   // toString
  public String toString() {
    return "Voiture : marque=[" + this.marque + "] type=[" + this.type
        + "] propriétaire=[" + this.propriétaire + "]";
  }

     // getters-setters
  public String getMarque() {
    return marque;
  }

  public void setMarque(String marque) {
    this.marque = marque;
  }

  public Personne getPropriétaire() {
    return propriétaire;
  }

  public void setPropriétaire(Personne propriétaire) {
    this.propriétaire = propriétaire;
  }

  public String getType() {
    return type;
  }

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

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

  public void close() {
    System.out.println("destroy voiture [" + 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 method toString to retrieve the value of the [Voiture] 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 [Voiture], we will use the following Spring file [config.xml]:

<?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="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        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="personne2"></ref>
        </constructor-arg>
    </bean>
</beans>

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

    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <property name="marque">
            <value>Peugeot</value>
        </property>
        <property name="type">
            <value>307</value>
        </property>
        <property name="propriétaire">
            <ref bean="personne2"/>
        </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 [voiture1] bean defines the method to be called during the initial construction of the [init-method] object and the method to be called during the destruction of the [destroy-method] object.

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() {
     // recovery of bean [voiture1]
    Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
    System.out.println("Voiture1=" + Voiture1.toString());
  }

The [test2] method retrieves the [voiture1] 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:

18 sept. 2004 14:56:10 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'car1'
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person2'
init personne [nom=[Brigitte], age=[20]]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'voiture1' instantiated via constructor [public istia.st.springioc.domain.Voiture(java.lang.String,java.lang.String,istia.st.springioc.domain.Personne)]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]

Comments:

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

2.3.3. Example 3

We introduce the following new class [GroupePersonnes]:

package istia.st.springioc.domain;

import java.util.Map;

public class GroupePersonnes {
  private Personne[] membres;
  private Map groupesDeTravail;

   // getters - setters
  public Personne[] getMembres() {
    return membres;
  }

  public void setMembres(Personne[] membres) {
    this.membres = membres;
  }

  public Map getGroupesDeTravail() {
    return groupesDeTravail;
  }

  public void setGroupesDeTravail(Map groupesDeTravail) {
    this.groupesDeTravail = groupesDeTravail;
  }

   // display
  public String toString() {
    String liste = "membres : ";
    for (int i = 0; i < this.membres.length; i++) {
      liste += "[" + this.membres[i].toString() + "]";
    }
    return liste + ", groupes de travail = " + this.groupesDeTravail.toString();
  }

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

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

Its two private members are:

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

groupesDeTravail: a dictionary mapping a person to a workgroup

Note here that the [GroupePersonnes] 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-type fields. We add a new bean to the previous Spring file [config.xml]:

    <bean id="groupe1" class="istia.st.springioc.domain.GroupePersonnes" 
        init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref bean="personne1"/>
                <ref bean="personne2"/>
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</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 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() {
    // bean retrieval [group1]]
    GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
    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 from the JUnit test yields the following results:

18 sept. 2004 15:51:45 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'group1'
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person1'
init personne [nom=[Simon], age=[40]]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'person2'
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}

Comments:

  • The [test3] method requires a reference to the [groupe1] bean
  • line 4: Spring begins creating this bean
  • because the bean [groupe1] references the beans [personne1] and [personne2], these two beans are created (lines 6 and 9) and their init method is executed (lines 7 and 10)
  • Line 11: The bean [groupe1] has been created. Its method [init] 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 name 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 doSometingInDaoLayer(int a, int b);
}
  • Write two classes, Dao1Impl1 and Dao1Impl2, that implement the IDao1 interface. The method Dao1Impl1.doSomethingInDaoLayer will return a+b, and the method Dao1Impl2.doSomethingInDaoLayer will return a-b.
  • Write a test class JUnit that tests 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 method Domain1Impl1.doSomethingInDomainLayer will increment a and b by one, then pass these two parameters to the method doSomethingInDaoLayer of the received object of type IDao1. The method Domain1Impl2.doSomethingInDomainLayer, on the other hand, will decrement a and b by one before doing the same thing.
  • Write a test class JUnit 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 doSometingInControlLayer(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 method Control1Impl1.doSomethingInControlLayer will increment a and b by one, then pass these two parameters to the method doSomethingInDomainLayer of the received object of type IDomain1. The Control11Impl2.doSomethingInControlLayer method, on the other hand, will decrement a and b by one before doing the same thing.
  • Write a test class JUnit 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 test class JUnit 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 rendered by the interface.

2.4.6. A solution

2.4.6.1. The Eclipse project

Image

The archives from the [lib] folder have been added to the [ClassPath] project.

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 doSometingInDaoLayer(int a, int b);
}

A first implementation class:

package istia.st.demo.dao;

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

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

}

A second implementation class:

package istia.st.demo.dao;

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

     // we do something in the [dao] layer
  public int doSometingInDaoLayer(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 {

     // we 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 [dao] layer access service
  private IDao1 dao1;

  public Domain1Impl1() {
     // constructor with no arguments
  }

     // stores the [dao] layer access service
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // we do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b) {
    a++;
    b++;
    return dao1.doSometingInDaoLayer(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 [dao] layer access service
  private IDao1 dao1;

  public Domain1Impl2() {
     // constructor with no arguments
  }

     // stores the [dao] layer access service
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

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

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

The interface

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

/**
 * @author ST-ISTIA
 *  
 */
public interface IControl1 {
  public int doSometingInControlLayer(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 layer [domain]
    private IDomain1 domain1;

  public Control1Impl1() {
     // constructor with no arguments
  }

     // domain] layer access service enhancement
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // we're doing something
  public int doSometingInControlLayer(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 [domain] layer access class
    private IDomain1 domain1;

  public Control1Impl2() {
     // constructor with no arguments
  }

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

     // we're doing something
  public int doSometingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. The [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 trade 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 trade 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 [istia.st.demo.tests] test package

A test of type [main]:

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) {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // we use the
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // the result is displayed
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Results displayed in the Eclipse console:

11 mars 2005 11:25:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest1.xml]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 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)]
11 mars 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 configuration file [Spring]:

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) {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // we use the
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // the result is displayed
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Results displayed in the Eclipse console:

11 mars 2005 11:28:52 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest2.xml]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 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)]
11 mars 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() {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // we use the
    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
    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 have not covered. 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 M method is unaware of these filters. They are defined in the Spring configuration file.
  • The code for method M remains unchanged. Filters are Java classes that need to be created. Spring provides predefined filters, including ones for managing transactions for SGBD.
  • Filters are beans and, as such, are defined in the Spring configuration file as beans.

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

Because it is in the business layer, method M abstracts away the data storage. It does not, for example, need to assume that the data is in a SGBD or that it needs to place the two calls to methods M1 and M2 within a SGBD 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, calls that it would enclose within a transaction in SGBD.

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 methods filtered in this way do not need to be rewritten
  • since the filters to be used are defined via configuration, they can be changed

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

  • for JDBC, SqlMap (iBatis), and for Hibernate, JDO (JavaObject) in the DAO layer
  • for the MVC model in the User Interface layer

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