Spring Framework – An Introduction Part I – Dependency Injection, Bean Reuse

Spring Framework has been around for some time. It has pretty much become the defacto choice in developing an enterprise application outside the Java EE/J2EE server domain. This blog post intends to provide a sneak peak of the features provided out-of-the-box by Spring. Essentially a short tutorial highlighting Spring capabilities. This tutorial uses Spring Framework version 3.1.0 M1 release. Besides the libraries available as a part of the Spring Framework release I have additionally used commons-logging-1.1.1 jar.

The starting point of this Spring framework tutorial is dependency injection or inversion of control. To understand what these two terms are look no further than Martin Fowler’s article ‘Inversion of Control Containers and the Dependency Injection pattern’.

Dependency Injection

Let’s start with a primer on the basics of Spring. I have created the following four Java classes namely Student, InternationalStudent, DataSingleton and Address. Their source code is as below:

package com.spring.di;

public class Student {

	private String name     = null;
	private Address address = null;

	public Student() {
	}

	public Student(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}

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

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	@Override
	public String toString() {

		StringBuilder sb = new StringBuilder();
		sb.append("&&&&&&&&&&&&&&&&&&&&&");
		sb.append("\n");
		sb.append((this.name != null)? this.name:"Not named");
		sb.append("\n");
		sb.append("Address: ");
		sb.append((this.address != null)? this.address.toString():"Address Not Available");
		sb.append("\n");
		sb.append("JVM heap Address of Student: "+ super.toString());
		sb.append("\n");
		sb.append("&&&&&&&&&&&&&&&&&&&&&");
		return sb.toString();
	}
}
package com.spring.di;

public class InternationalStudent extends Student {

	private String country = null;

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(super.toString());
		sb.append("Country: " + this.country);
		sb.append("\n");
		return sb.toString();
	}
}
package com.spring.di;

public class DataSingleton {

	private static DataSingleton SINGLETON = new DataSingleton();

	private DataSingleton() {

	}

	public static DataSingleton getInstance() {
		System.out.println("DataSingleton: No args");
		return SINGLETON;
	}

	public static DataSingleton getInstance(String value) {
		System.out.println("DataSingleton: with args: " + value);
		return SINGLETON;
	}
}
package com.spring.di;

public class Address {

	private String line1 = null;
	private String line2 = null;
	private String city  = null;
	private String state = null;

	public Address() {

	}

	public String getLine1() {
		return line1;
	}
	public void setLine1(String line1) {
		this.line1 = line1;
	}
	public String getLine2() {
		return line2;
	}
	public void setLine2(String line2) {
		this.line2 = line2;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	public String getState() {
		return state;
	}
	public void setState(String state) {
		this.state = state;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append((this.line1 != null)? "Line 1: " + this.line1 : "Line 1 information missing.");
		sb.append("\n");
		sb.append((this.line2 != null)? "Line 2: " + this.line2 : "Line 2 information missing.");
		sb.append("\n");
		sb.append((this.city != null)? "City: " + this.city : "City information missing.");
		sb.append("\n");
		sb.append((this.state != null)? "State: " + this.state : "State information missing.");
		sb.append("\n");
		sb.append("JVM heap Address of Address: "+ super.toString());
		return sb.toString();
	}
}

To provide these Java classes or JavaBeans with the Spring framework flavor you will need to create a Spring specific configuration file namely beans-di.xml.

Here’s the contents of the configuration file beans-di.xml.

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

  	<bean id="bill" class="com.spring.di.Student">
  	</bean>
  
  	<bean id="george" class="com.spring.di.Student" scope="prototype">
  		<property name="name" value="George" />
  		<property name="address" ref="george-address" />
  	</bean>
  
  	<bean id="bruce" class="com.spring.di.Student">
  		<constructor-arg value="Bruce" />
  	</bean>

  	<bean id="george-address" class="com.spring.di.Address">
  		<property name="line1" value="1300 Weeping Willow Dr" />
  		<property name="line2" value="" />
  		<property name="city" value="Lynchburg" />
  		<property name="state" value="VA" />
  	</bean>

  	<bean id="chang" class="com.spring.di.InternationalStudent">
	  	<property name="name" value="Chang" />
	  	<property name="country" value="Singapore" />
 	</bean>
  
  	<bean id="chou" class="com.spring.di.InternationalStudent" scope="prototype">
  		<property name="name" value="Chou" />
  		<property name="country" value="China" />
  	</bean>

  	<bean id="data" class="com.spring.di.DataSingleton"
  		factory-method="getInstance">
  	</bean>
	
  	<bean id="dataWithArg" class="com.spring.di.DataSingleton"
  		factory-method="getInstance">
  		<constructor-arg value="Argument" />
  	</bean>
	
</beans>

The configuration file stores the definition of objects, their collaborators, and default values.To gain a better understanding of the configuration file and its functioning, let’s look at the beans-di.xml file in detail.

Line number 8 defines a bean instance with id ‘bill’ and the bean type is class Student. As no other information is provided, the bean on invocation will create an instance of Student with all its member variables undergoing default initialization. Next we move on to the bean id ‘george’. george is also of the type Student but has some additional information provided in its definition. One addition is that it has a scope attribute defined and second its properties have been defined. The Student class has two member variables, one is a String variable ‘name’ and the other is address of type Address. Spring framework allows the developer to instantiate these variables in the configuration file. This is achieved via the property element within the bean element. For name property we have provided the value attribute “George”, which instantiates the name String with value ‘George’. The second property address is a complex type and here we instantiate the address with the definition of ‘george-address’ bean defined in a separate section of the configuration file. Note the difference in the property elements. Property name uses the value attribute while address uses the ref attribute.

  	<bean id="george" class="com.spring.di.Student" scope="prototype">
  		<property name="name" value="George" />
  		<property name="address" ref="george-address" />
  	</bean>

Now let’s move on to scope. The scope attribute defines the method used by Spring framework for bean creation. By default, the scope for a bean is ‘singleton’. This singleton is limited to Spring configuration file, not to Java Virtual Machine (JVM). I will explain this a little later in the blog. The other valid values of scope are prototype, session, request, global-session. The scope values session and request map to HTTP request and Session lifetimes, global-session has a significance in the portal concept. The prototype scope creates a new bean every time a request is made to Spring framework.

Unless specified otherwise Spring framework uses the default mechanism for object creation i.e. calling a constructor with no arguments. However in real life scenarios we need a properly instantiated object. This might involve object creation using runtime parameters. To achieve this we typically define a custom constructor which accepts parameters as constructor argument(s).The Student class supports a constructor with string input. Spring framework supports instantiating the Student class having constructor argument using the constructor-arg element. Refer the bruce bean definition for understanding the application.

	<bean id="bruce" class="com.spring.di.Student">
  		<constructor-arg value="Bruce" />
  	</bean>

Typical class definitions involve inheritance hierarchies. The configuration file allows definition of object’s own properties as well as inherited properties. Let’s create a child class for Student namely InternationalStudent. This class defines its own property called country. The definition of an instance of InternationalStudent ‘chang’ is illustrated below:

  	<bean id="chang" class="com.spring.di.InternationalStudent">
	  	<property name="name" value="Chang" />
	  	<property name="country" value="Singapore" />
 	</bean>

The property name is inherited from Student class, while country is InternationalStudent’s property. Classes with inheritance can have scope definition. Refer below for example:

  	<bean id="chou" class="com.spring.di.InternationalStudent" scope="prototype">
  		<property name="name" value="Chou" />
  		<property name="country" value="China" />
  	</bean>

Construction invocation is the most common methodology for creating an object. However in certain cases the design dictates that object creation be restricted for e.g. in case of a singleton only one object be created within one JVM. To achieve this the constructor’s access modifier is changed to private and access is routed via a different method typically we have a getInstance method which provides access. For our example refer DataSingleton class which is an eagerly instantiated singleton. The bean definition for this class is achieved using the factory-method attribute.

  	<bean id="data" class="com.spring.di.DataSingleton"
  		factory-method="getInstance">
  	</bean>

This informs the Spring framework that DataSingleton object can be accessed via the getInstance method.In case the getInstance method accepts an argument the bean is defined in the following manner.

  	<bean id="dataWithArg" class="com.spring.di.DataSingleton"
  		factory-method="getInstance">
  		<constructor-arg value="Argument" />
  	</bean>

Here’s the test code to validate the getInstance implementation.

package com.spring.test;

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

import com.spring.di.DataSingleton;

public class FactoryMethodTest {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext (new String[] {"beans-di.xml"});
		
		DataSingleton data1 = (DataSingleton)ctx.getBean("data");
		DataSingleton data2 = (DataSingleton)ctx.getBean("dataWithArg");
		
	}
}

So far we were talking about setting up the configuration file, how do we reference the configuration. That will be achieved by the following Java code:

package com.spring.test;

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

import com.spring.di.InternationalStudent;
import com.spring.di.Student;

public class StudentDITest {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext (new String[] {"beans-di.xml"});

		Student bill = (Student)ctx.getBean("bill");
		System.out.println(bill);

		Student george = (Student)ctx.getBean("george");
		System.out.println(george);

		Student bruce = (Student)ctx.getBean("bruce");
		System.out.println(bruce);

		Student bill2 = (Student)ctx.getBean("bill");
		System.out.println(bill2);

		Student george2 = (Student)ctx.getBean("george");
		System.out.println(george2);

		Student chang = (InternationalStudent)ctx.getBean("chang");
		System.out.println(chang);

		Student chou = (InternationalStudent)ctx.getBean("chou");
		System.out.println(chou);

		Student chang2 = (InternationalStudent)ctx.getBean("chang");
		System.out.println(chang2);

		Student chou2 = (InternationalStudent)ctx.getBean("chou");
		System.out.println(chou2);
	}
}

Line number 12 creates a Spring context using the beans-di.xml configuration file. The same code can be used for creating multiple contexts. Remember the singleton scope applies to this context. A context can be associated with one or more configuration files and a program can instantiate one or more Spring contexts. Using the context and the bean id, the developer can retrieve a bean instance. This is how the Spring framework facilitates inversion of control or dependency injection.

To get a better understanding on the scoping portion consider the following test code:

package com.spring.test;

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

import com.spring.di.Student;

public class BeanScopingTest {

	public static void main(String[] args) {
		ApplicationContext ctx1 = new ClassPathXmlApplicationContext (new String[] {"beans-di.xml"});
		
		Student bill1 = (Student)ctx1.getBean("bill");
		System.out.println("Bill 1: ");
		System.out.println(bill1);

		Student bill3 = (Student)ctx1.getBean("bill");
		System.out.println("Bill 3: ");
		System.out.println(bill3);
		
		Student george1 = (Student)ctx1.getBean("george");
		System.out.println("George 1: ");
		System.out.println(george1);

		Student george2 = (Student)ctx1.getBean("george");
		System.out.println("George 2: ");
		System.out.println(george2);
		
		ApplicationContext ctx2 = new ClassPathXmlApplicationContext (new String[] {"beans-di.xml"});
		
		Student bill2 = (Student)ctx2.getBean("bill");
		System.out.println("Bill 2: ");
		System.out.println(bill2);

		Student george3 = (Student)ctx2.getBean("george");
		System.out.println("George 3: ");
		System.out.println(george3);

		Student george4 = (Student)ctx2.getBean("george");
		System.out.println("George 4: ");
		System.out.println(george4);
	}

}

Running the test class generates the following output(I have kept the output limited to the relevant text):

Bill 1: 
&&&&&&&&&&&&&&&&&&&&&
Not named
Address: Address Not Available
JVM heap Address of Student: com.spring.di.Student@1394894
&&&&&&&&&&&&&&&&&&&&&
Bill 3: 
&&&&&&&&&&&&&&&&&&&&&
Not named
Address: Address Not Available
JVM heap Address of Student: com.spring.di.Student@1394894
&&&&&&&&&&&&&&&&&&&&&
George 1: 
&&&&&&&&&&&&&&&&&&&&&
George
Address: Line 1: 1300 Weeping Willow Dr
Line 2: 
City: Lynchburg
State: VA
JVM heap Address of Address: com.spring.di.Address@1cbfe9d
JVM heap Address of Student: com.spring.di.Student@1b8f864
&&&&&&&&&&&&&&&&&&&&&
George 2: 
&&&&&&&&&&&&&&&&&&&&&
George
Address: Line 1: 1300 Weeping Willow Dr
Line 2: 
City: Lynchburg
State: VA
JVM heap Address of Address: com.spring.di.Address@1cbfe9d
JVM heap Address of Student: com.spring.di.Student@bb7759
&&&&&&&&&&&&&&&&&&&&&

Bill 2: 
&&&&&&&&&&&&&&&&&&&&&
Not named
Address: Address Not Available
JVM heap Address of Student: com.spring.di.Student@3e89c3
&&&&&&&&&&&&&&&&&&&&&
George 3: 
&&&&&&&&&&&&&&&&&&&&&
George
Address: Line 1: 1300 Weeping Willow Dr
Line 2: 
City: Lynchburg
State: VA
JVM heap Address of Address: com.spring.di.Address@1c695a6
JVM heap Address of Student: com.spring.di.Student@8acf6e
&&&&&&&&&&&&&&&&&&&&&
George 4: 
&&&&&&&&&&&&&&&&&&&&&
George
Address: Line 1: 1300 Weeping Willow Dr
Line 2: 
City: Lynchburg
State: VA
JVM heap Address of Address: com.spring.di.Address@1c695a6
JVM heap Address of Student: com.spring.di.Student@1386918
&&&&&&&&&&&&&&&&&&&&&

The bean ‘bill’ is defined with singleton scope. In the test class BeanScopingTest I have retrieved ‘bill’ twice line number 13 and 17. I have printed the JVM address of the bean object as a part of the toString() implementation. Both the addresses are the same ‘Student@1394894’. I had created a new Context again refer line number 29. I have again retrieved ‘bill’. This time the JVM address is different ‘Student@3e89c3’. Clearly the singleton scope is limited to the Spring context. Now let’s understand the other scope prototype. The bean ‘george’ is defined with scope ‘prototype’. Refer the output all the JVM addresses for george bean are different. However the address bean JVM address is the same per context. For context 1 it is Address@1cbfe9d and context 2 is Address@1c695a6. Therefore the scope applies to the bean only and not its collaborators.

Bean reuse

As explained in the examples above the configuration file allows the developer to define a bean, its collaborators and facilitates setting up the bean properties with values if required. During application development one tends come across scenarios where we need to reuse the same definition at multiple
places. Spring framework promotes reuse. For the demonstration of this concept I am using the following Java classes:

package com.spring.others;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Order {

	private String countryOfOrigin   = null;
	private String state             = null;
	private boolean taxFree          = false;

	private List<LineItem> lineItems = new ArrayList<LineItem>();

	private Map<String, LineItem> map = new HashMap<String, LineItem>();

	public String getCountryOfOrigin() {
		return countryOfOrigin;
	}

	public void setCountryOfOrigin(String countryOfOrigin) {
		this.countryOfOrigin = countryOfOrigin;
	}

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	public boolean isTaxFree() {
		return taxFree;
	}

	public void setTaxFree(boolean taxFree) {
		this.taxFree = taxFree;
	}

	public List<LineItem> getLineItems() {
		return lineItems;
	}

	public void setLineItems(List<LineItem> lineItems) {
		this.lineItems = lineItems;
	}

	public Map<String, LineItem> getMap() {
		return map;
	}

	public void setMap(Map<String, LineItem> map) {
		this.map = map;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Country: ");
		sb.append(this.countryOfOrigin);
		sb.append("\n");
		sb.append("State: ");
		sb.append(this.state);
		sb.append("\n");
		sb.append("Tax Free: ");
		sb.append(this.taxFree);
		sb.append("\n");

		for (LineItem item : this.lineItems) {
			sb.append(item.toString());
		}

		return sb.toString();
	}
}
package com.spring.others;

public class LineItem {

	private String itemId   = null;
	private String itemdesc = null;
	private int    quantity = 0;

	public String getItemId() {
		return itemId;
	}
	public void setItemId(String itemId) {
		this.itemId = itemId;
	}
	public String getItemdesc() {
		return itemdesc;
	}
	public void setItemdesc(String itemdesc) {
		this.itemdesc = itemdesc;
	}
	public int getQuantity() {
		return quantity;
	}
	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Item Id: ");
		sb.append(this.itemId);
		sb.append("\n");
		sb.append("Item Desc: ");
		sb.append(this.itemdesc);
		sb.append("\n");
		sb.append("Quantity: ");
		sb.append(this.quantity);
		sb.append("\n");

		return sb.toString();
	}
}

Refer the beans-order.xml configuration file.

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

	<bean id="baseOrder" class="com.spring.others.Order" abstract="true">
		<property name="countryOfOrigin" value="US" />
		<property name="state" value="VA" />
	</bean>

	<bean id="lineitem" class="com.spring.others.LineItem">
	</bean>

  	<bean id="taxfreeOrder" parent="baseOrder">
  		<property name="taxFree" value="true" />
  	</bean>
  	<bean id="taxableOrder" parent="baseOrder">
  		<property name="lineItems">
  			<list>
  				<ref bean="camera" />
  				<ref bean="iPad" />
  			</list>
  		</property>
  		<property name="map">
  			<map>
  				<entry key="ITM01" value-ref="camera" />
  				<entry key="ITM02" value-ref="iPad" />
  			</map>
  		</property>
  	</bean>

  	<bean id="camera" class="com.spring.others.LineItem">
  		<property name="itemId" value="ITM01" />
  		<property name="itemdesc" value="Nokia D-90 Digital Camera" />
  		<property name="quantity" value="2" />
  	</bean>
  	<bean id="iPad" class="com.spring.others.LineItem">
  		<property name="itemId" value="ITM02" />
  		<property name="itemdesc" value="Apple i-Pad" />
  		<property name="quantity" value="1" />
  	</bean>

</beans>

In line 8 we have put together the base definition of order. The bean ‘baseOrder’ creates an Order instance with properties countryOfOrigin as US and state as VA. Note that the ‘baseOrder’ bean definition has an additional attribute abstract set to true. This attribute prevents the developer from directly instantiating the bean. The developer is free to use the bean definitions ‘taxfreeOrder’ or ‘taxableOrder’. Here’s the test class to validate:

package com.spring.test;

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

import com.spring.others.Order;

public class ParentAndCollectionTest {

	public static void main(String[] args) {
		
		ApplicationContext ctx = new ClassPathXmlApplicationContext (new String[] {"beans-order.xml"});
		
		//Order baseOrder = (Order)ctx.getBean("baseOrder");
		Order taxfreeOrder = (Order)ctx.getBean("taxfreeOrder");
		Order taxableOrder = (Order)ctx.getBean("taxableOrder");

		System.out.println("****************** TaxFree");
		System.out.println(taxfreeOrder);
		System.out.println("****************** Taxable");
		System.out.println(taxableOrder);
	}
}

Collection definition

Bean must allow Collections or Maps to be incorporated in their definitions. Spring framework facilitates this in the following manner. Refer the beans-order.xml. For demonstration purposes we have added two properties in the Order class lineItems and map. Property lineItems is a Collection and map is a map.To understand how these attributes are mapped in the configuration file refer to the bean definition taxableOrder with the beans-order file.

  	<bean id="taxableOrder" parent="baseOrder">
  		<property name="lineItems">
  			<list>
  				<ref bean="camera" />
  				<ref bean="iPad" />
  			</list>
  		</property>
  		<property name="map">
  			<map>
  				<entry key="ITM01" value-ref="camera" />
  				<entry key="ITM02" value-ref="iPad" />
  			</map>	
  		</property>  		
  	</bean>
  	<bean id="camera" class="com.spring.others.LineItem">
  		<property name="itemId" value="ITM01" />
  		<property name="itemdesc" value="Nokia D-90 Digital Camera" />
  		<property name="quantity" value="2" />
  	</bean>
  	<bean id="iPad" class="com.spring.others.LineItem">
  		<property name="itemId" value="ITM02" />
  		<property name="itemdesc" value="Apple i-Pad" />
  		<property name="quantity" value="1" />
  	</bean>

Two instances of LineItem namely ‘camera’ and ‘iPad’ have been defined and their references have been used in the ‘lineItems’. For Collection all the individual items are encapsulated within the list tag and for map it is within the map tag. Since I have used existing bean definitions, I have used the ref attribute alternatively in case values where present, they could be used directly. For example if the lineItems encapsulated a collection of Strings the definition would be as follows:

  		<property name="lineItems">
  			<list>
  				<value>camera</value>
  				<value>iPad</value>
  			</list>
  		</property>

For map definition look at the map property. Within the map tag, we have the entry element with value or value-ref attribute. Earlier ParentAndCollectionTest code can be used to validate.

That’s it at the moment.

Advertisements

4 thoughts on “Spring Framework – An Introduction Part I – Dependency Injection, Bean Reuse

  1. Hi,
    This is really really very nice tutorial on Spring Bean IOC for any one. If reader is fresher then they can get better fundamental understanding, if reader is experienced they can use this blog for reference.

    Very helpful.

    Many Thanks.

    Binod Suman
    Bangalore

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s