Spring With XML Configuration
Inversion of Control
The Spring container (generally known as ApplicationContext
) has two main functions:
- Create and manage objects (Inversion of control)
- Inject object’s dependencies (Dependency Injection)
So Inversion Control is externalizing the construction and management of objects which will be handled by and object factory. This is illustrated in the following image:
- MyApp has the main method
- MyApp asks Spring to retrieve the appropiate object based on a configuration file or an annotation, instead of having to code it manually like:
package com.springdemo;
public class MyApp {
public static void main(String[] args) {
Coach theCoach = new TrackCoach();
// call methods on the bean
System.out.println(theCoach.getDailyWorkout());
}
}
Where we have defined an interface Coach
that is implemented by both TrackCoach
and BaseballCoach
package com.springdemo;
public interface Coach {
public String getDailyWorkout();
}
package com.springdemo;
public class TrackCoach implements Coach {
@Override
public String getDailyWorkout() {
return "Run a hard 5k";
}
}
To avoid this approach we create a Spring container. To configure a Spring container we can use:
- XML configuration file (legacy)
- Java Annotations
- Java Source Code
However what is a Spring Bean?
A “Spring Bean” is simply a Java object.
When Java objects are created by the Spring Container, then Spring refers to them as “Spring Beans”. Spring Beans are created from normal Java classes just like Java objects.
Why do we specify the Coach interface in getBean()?
When we pass the interface to the method, behind the scenes Spring will cast the object for you.
context.getBean("myCoach", Coach.class)
However, there are some slight differences than normal casting.
Behaves the same as getBean(String)
, but provides a measure of type safety by throwing a BeanNotOfRequiredTypeException
if the bean is not of the required type.
This means that ClassCastException
can’t be thrown on casting the result correctly, as can happen with getBean(String)
.
Dependency Injection
Injection Types
There are several injection types in Spring. The more common are:
Constructor Injection
Create Dependency Object
package com.springdemo;
public interface FortuneService {
public String getFortune();
}
Next we create the dependency class than implements the interface:
package com.springdemo;
public class HappyFortuneService implements FortuneService {
@Override
public String getFortune() {
return "Today is your lucky day!";
}
}
Establish Dependency
Let’s also update the Coach
Interface to add a method getDailyFortune
(note that all classes that implement this interface have to implement this new method):
package com.springdemo;
public interface Coach {
public String getDailyWorkout();
public String getDailyFortune();
}
Now create a constructor for the dependency in the class that has the dependency
package com.springdemo;
public class BaseballCoach implements Coach {
// define a private field for the dependency
private FortuneService fortuneService;
// define a constructor for dependency injection
public BaseballCoach(FortuneService theFortuneService) {
fortuneService = theFortuneService;
}
@Override
public String getDailyWorkout() {
return "Spend 30 minutes on batting practice";
}
@Override
public String getDailyFortune() {
// use my fortuneService to get a fortune
return fortuneService.getFortune();
}
}
Configuration File
Finally define the dependency in the configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Define your beans here -->
<!-- define the dependency -->
<bean id="myFortuneService"
class="com.springdemo.HappyFortuneService">
</bean>
<!-- Bean with the dependency -->
<bean id="myCoach"
class="com.springdemo.TrackCoach">
<!-- Set up constructor injection, note ref=id of bean -->
<constructor-arg ref="myFortuneService" />
</bean>
</beans>
Behind the scenes, Spring framework does:
package com.springdemo;
public class MyApp {
public static void main(String[] args) {
// Create object
// From the bean with id = myFortuneService in the config file
HappyFortuneService myFortuneService = new HappyFortuneService();
// Add dependency via constructor
// From the bean with id = myCoach in the config file
TrackCoach myCoach = new TrackCoach(fortuneService);
}
}
Main Method
We do not need to make any modifications to the app, when we create the Coach
bean using Spring, the framework deals with the dependency injection:
package com.luv2code.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloSpringApp {
public static void main(String[] args) {
// load the spring configuration file
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
// retrieve bean from spring container (with the dependency)
Coach theCoach = context.getBean("myCoach", Coach.class);
// call methods on the bean
System.out.println(theCoach.getDailyWorkout());
// let's call our new method for fortunes
System.out.println(theCoach.getDailyFortune());
// close the context
context.close();
}
}
Setter Injection
Create Dependency Object
Refer to Create Dependency Object
Define dependency
We include a setter
method that takes the dependency as an argument like:
package com.springdemo;
public class CricketCoach implements Coach {
private FortuneService fortuneService;
// create a no-arg constructor
public CricketCoach() {
System.out.println("CricketCoach: inside no-arg constructor");
}
// our setter method
public void setFortuneService(FortuneService fortuneService) {
System.out.println("CricketCoach: inside setter method - setFortuneService");
this.fortuneService = fortuneService;
}
@Override
public String getDailyWorkout() {
return "Practice fast bowling for 15 minutes";
}
@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
Configuration File
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Define your beans here -->
<!-- define the dependency -->
<bean id="myFortuneService" class="com.springdemo.HappyFortuneService">
</bean>
<bean id="myCoach"
class="com.springdemo.TrackCoach">
<!-- set up constructor injection -->
<constructor-arg ref="myFortuneService" />
</bean>
<bean id="myCricketCoach" class="com.springdemo.CricketCoach">
<!-- set up setter injection -->
<!-- ref: references the id of the bean we define previously -->
<!-- name: name of the setter method set<name>, where the first
letter of the name is capitalized -->
<property name="fortuneService" ref="myFortuneService" />
</bean>
</beans>
Behind the scenes, Spring framework does:
package com.springdemo;
public class MyApp {
public static void main(String[] args) {
// Create object
// From the bean with id = myFortuneService in the config file
HappyFortuneService myFortuneService = new HappyFortuneService();
// From the bean with id = myCricketCoach in the config file
CricketCoach myCricketCoach = new CricketCoach(fortuneService);
// Add dependency via setter
myCricketCoach.setFortuneService(myFortuneService);
}
}
Main Method
Now, on the main method of our Spring App, we create the object by reading the config file, and Spring automatically injects the dependency via the setter method:
package com.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SetterDemoApp {
public static void main(String[] args) {
// load the spring configuration file
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
// retrieve bean from spring container
CricketCoach theCoach = context.getBean("myCricketCoach", CricketCoach.class);
// call methods on the bean
System.out.println(theCoach.getDailyWorkout());
System.out.println(theCoach.getDailyFortune());
// close the context
context.close();
}
}
Injecting Literal Values
Define the Attributes
First we define the attributes emailAddress and team in the object. Also we create the set
and get
methods for both of them:
package com.luv2code.springdemo;
public class CricketCoach implements Coach {
private FortuneService fortuneService;
// add new fields for emailAddress and team
private String emailAddress;
private String team;
public CricketCoach() {
System.out.println("CricketCoach: inside no-arg constructor");
}
/** SETTERS AND GETTERS **/
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
System.out.println("CricketCoach: inside setter method - setEmailAddress");
this.emailAddress = emailAddress;
}
public String getTeam() {
return team;
}
public void setTeam(String team) {
System.out.println("CricketCoach: inside setter method - setTeam");
this.team = team;
}
/** Setter Injection **/
public void setFortuneService(FortuneService fortuneService) {
System.out.println("CricketCoach: inside setter method - setFortuneService");
this.fortuneService = fortuneService;
}
@Override
public String getDailyWorkout() {
return "Practice fast bowling for 15 minutes";
}
@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
Configuration File
Now we define the properties in the configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Define your beans here -->
<!-- define the dependency -->
<bean id="myFortuneService" class="com.springdemo.HappyFortuneService">
</bean>
<bean id="myCoach"
class="com.springdemo.TrackCoach">
<!-- set up constructor injection -->
<constructor-arg ref="myFortuneService" />
</bean>
<bean id="myCricketCoach" class="com.springdemo.CricketCoach">
<!-- set up setter injection -->
<!-- ref: references the id of the bean we define previously -->
<!-- name: name of the setter method set<name>, where the first
letter of the name is capitalized -->
<property name="fortuneService" ref="myFortuneService" />
<!-- inject literal values, where name is the name of the attribute in the bean
and value is the value to set the value to -->
<property name="emailAddress" value="email@email.com" />
<property name="team" value="Best Team" />
</bean>
</beans>
Main Method
Now in the main method of our app we can call the getters
and setters
for these new attributes:
package com.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SetterDemoApp {
public static void main(String[] args) {
// load the spring configuration file
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
// retrieve bean from spring container
CricketCoach theCoach = context.getBean("myCricketCoach", CricketCoach.class);
// retrieve attribute values
System.out.println(theCoach.getTeam());
System.out.println(theCoach.getEmailAddress());
// close the context
context.close();
}
}
Inject Values from the Properties Files
Create the properties file
Let’s define our properties inside a properties file sport.properties
:
foo.email=myeasycoach@email.com
foo.team=Royal Challengers Bangalore
Load the properties file
Now we load the properties file using the context
tag inside our config file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- load the properties file: sport.properties -->
<context:property-placeholder location="classpath:sport.properties"/>
<!-- Define your beans here -->
<!-- define the dependency -->
<bean id="myFortuneService" class="com.springdemo.HappyFortuneService">
</bean>
<bean id="myCoach"
class="com.springdemo.TrackCoach">
<!-- set up constructor injection -->
<constructor-arg ref="myFortuneService" />
</bean>
<bean id="myCricketCoach" class="com.springdemo.CricketCoach">
<!-- set up setter injection -->
<!-- ref: references the id of the bean we define previously -->
<!-- name: name of the setter method set<name>, where the first
letter of the name is capitalized -->
<property name="fortuneService" ref="myFortuneService" />
<!-- inject literal values, where name is the name of the attribute in the bean
and value is the value to set the value to -->
<!-- Note that we are now referencing the values from the properties file -->
<property name="emailAddress" value="${foo.email})" />
<property name="team" value="${foo.team}" />
</bean>
</beans>
Main Method
In the main method, we create our object as usual, and if we invoke the getter
methods, we retrieve the values passed in the property file:
package com.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SetterDemoApp {
public static void main(String[] args) {
// load the spring configuration file
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
// retrieve bean from spring container
CricketCoach theCoach = context.getBean("myCricketCoach", CricketCoach.class);
// retrieve attribute values from property file
System.out.println(theCoach.getTeam());
System.out.println(theCoach.getEmailAddress());
// close the context
context.close();
}
}
Bean Scopes
Intro
The scope of a bean refers to the life cycle of the bean:
- How long does it live
- How many instances are created
- How is the bean shared
The default scope of the bean is a Singleton
:
- The Spring container creates only one instance of the bean
- It is cached in memory
- All requests to the bean will return a shared reference to the same bean
Other scopes are:
- A singleton scope is good for stateless data
- A prototype scope is good for stateful data (the container returns a new bean for each request). Note that for this type of bean, Spring does not call the destroy method.
Specify Scope in XML Config File
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Define your beans here -->
<!-- define the dependency -->
<bean id="myFortuneService"
class="com.springdemo.HappyFortuneService">
</bean>
<!-- Note the new tag "scope" -->
<bean id="myCoach"
class="com.springdemo.TrackCoach"
scope="prototype">
<!-- set up constructor injection -->
<constructor-arg ref="myFortuneService" />
</bean>
</beans>
Main Method
Now, from our application we do:
package com.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanScopeDemoApp {
public static void main(String[] args) {
// load the spring configuration file
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("beanScope-applicationContext.xml");
// retrieve bean from spring container
Coach theCoach = context.getBean("myCoach", Coach.class);
Coach alphaCoach = context.getBean("myCoach", Coach.class);
// check if they are the same
boolean result = (theCoach == alphaCoach);
// print out the results
System.out.println("\nPointing to the same object: " + result);
System.out.println("\nMemory location for theCoach: " + theCoach);
System.out.println("\nMemory location for alphaCoach: " + alphaCoach + "\n");
// close the context
context.close();
}
}
Observe, the result
variable should be set to false, because we are using the prototype
scope. Also the values of the memory location for the two objects should be distinct for that same reason.
However if we were using scope="singleton"
, then result should be true, and both objects should have the same memory location.
Bean Life Cycle
Define Methods
First of all we define the methods in our bean:
package com.springdemo;
public class TrackCoach implements Coach {
private FortuneService fortuneService;
public TrackCoach() {
}
public TrackCoach(FortuneService fortuneService) {
this.fortuneService = fortuneService;
}
@Override
public String getDailyWorkout() {
return "Run a hard 5k";
}
@Override
public String getDailyFortune() {
return "Just Do It: " + fortuneService.getFortune();
}
// add an init method
public void doMyStartupStuff() {
System.out.println("TrackCoach: inside method doMyStartupStuff");
}
// add a destroy method
public void doMyCleanupStuffYoYo() {
System.out.println("TrackCoach: inside method doMyCleanupStuffYoYo");
}
}
Configure Hooks in the Configuration File
Once the initialization and clean-up methods have been defined, we configure them in our configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Define your beans here -->
<!-- define the dependency -->
<bean id="myFortuneService"
class="com.springdemo.HappyFortuneService">
</bean>
<!-- Note the new tag "scope" -->
<bean id="myCoach"
class="com.springdemo.TrackCoach"
init-method="doMyStartupStuff"
destroy-method="doMyCleanupStuffYoYo">
<!-- set up constructor injection -->
<constructor-arg ref="myFortuneService" />
</bean>
</beans>
Main Method
Now in our App, we create the bean to check that our methods are being called:
package com.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeCycleDemoApp {
public static void main(String[] args) {
// load the spring configuration file
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("beanLifeCycle-applicationContext.xml");
// retrieve bean from spring container
Coach theCoach = context.getBean("myCoach", Coach.class);
System.out.println(theCoach.getDailyWorkout());
// close the context
context.close();
}
}
Notes
When using XML configuration, I want to provide additional details regarding the method signatures of the init-method and destroy-method .
- Access modifier: The method can have any access modifier (public, protected, private)
- Return type: The method can have any return type. However, “void’ is most commonly used. If you give a return type just note that you will not be able to capture the return value. As a result, “void” is commonly used.
- Method name: The method can have any method name.
- Arguments: The method can not accept any arguments. The method should be no-arg.