Inversion of Control (IoC) and Dependency Injection (DI) are programming patterns for decoupling the Class dependencies. In this article, we will talk about IoC and DI with respect to Spring Framework.
Read High Level introduction of Spring Framework if you are new to Spring.
1. What is Inversion of Control (IoC)?
For simplicity, let’s say class Vehicle
is dependent on class Engine
which means Vehicle
without an Engine
does not make sense. We need to make sure that this dependency is fulfilled by keeping the code loosely coupled. So we make use of a special class to make sure that the object creation is done in proper order and dependencies are taken care of. This special class is sometimes called a Container or Factory.
Inversion of Control is a technique that ensures decoupling, by transferring the objection creation job to a Container or Framework. The framework also allows us to customize the Objection creation and add custom behavior as per the developer’s needs. It is called inversion because the process is inverse where the bean creation and dependency is controlled by the container instead of the bean itself.
Advantages of IoC
- Decoupling the objects and its executions
- Helps in runtime Polymorphism
- Better code Maintainability.
- Easy code Testing due to loose coupling between the interfaces and implementations.
Mechanisms to achieve IoC
IoC can be achieved by various programming techniques and patterns such as Strategy Design Pattern, Service Locator Pattern, Factory Pattern, and Dependency Injection.
2. What is Dependency Injection?
In Dependency Injection, objects define their dependencies only through constructor arguments, arguments to a factory method, or properties that are set on the object instance. A container then injects those dependencies when it creates the bean.
In traditional programming (tightly coupled code), you would typically create object dependency as shown below.
//Traditional - tightly coupled code.
public class Store {
private Item item;
public Store() {
this.item = new Item();
}
}
A better way to manage the Item
dependency inside Store is to rewrite the code as below where constructor injection is used.
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
NOTE – We need to configure the IoC container through metadata so that the container has the necessary information to create Beans.
Next, we will explore the Spring Framework IOC Container. Let’s get started with the actual Spring coding.
3. The Spring Framework IoC Container
The Inversion of Control Container is the core of the Spring Framework. Every other library/module in the Spring ecosystem is built on top of this. To use the Spring IoC, make sure you have the spring-context
dependency in your pom.xml.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
In Spring Framework,
- The
ApplicationContext
interface represents the Spring IoC container. - The same interface is also responsible for instantiation, configuration, and management of the Beans.
- The bean configuration metadata can be provided in XML, Java Annotations or Java Code.
- Spring provides several implementations of ApplicationContext interface. For example, to work with XML based metadata configuration in standalone applications, it has
ClassPathXmlApplicationContext
andFileSystemXmlApplicationContext
. Similarly,AnnotationConfigApplicationContext
if you are working in standalone annotations based configurations.
The metadata in Spring can be configured in 3 ways.
- Using pure XML based configuration
- Using the annotation-based configuration. This approach makes use of annotations and XML based configurations.
- 100% Java-based configuration. This approach also makes use of annotations.
NOTE: If you are starting a new project, it is recommended to use Java Based Spring configurations with annotations.
4. Three ways of Dependency Injection in Spring
Remember, Dependency Injection in Spring can be done in three ways, through Constructors, Setters or Fields. We will learn each of these DI methods with Java-based configurations as well as XML based configurations.
1. Constructor based Dependency Injection
In Constructor based DI, the IoC container will invoke the constructor to inject its necessary dependency. The metadata configuration can be provided in a java config file or in an XML bean-config file.
1.a. XML based metadata Config for constructor injection
The below code shows the use of XML based config to do Constructor based dependency injection. You don’t have to touch the java POJO classes. All the configuration is done in bean-config.xml
file.
A bean in Spring has the default scope as Singleton. As we want a new instance to be created every time we ask spring to return a new bean, the bean scope is specified as prototype scope="prototype"
. The constructor DI is done using the constructor-arg as you can see in the bean-config.xml
file.
As you can see in the TestXMLConstructorInjection
class, ApplicationContext instance is created using the ClassPathXmlApplicationContext
class.
package basic.ioc.constructor.xml;
import java.util.StringJoiner;
public class Item {
private Long id;
private String name;
public Item() {
}
public Item(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new StringJoiner(", ", Item.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("name='" + name + "'")
.toString();
}
}
package basic.ioc.constructor.xml;
import java.util.StringJoiner;
public class Store {
private String id;
private Item item;
public Store() {
}
public Store(String id, Item item) {
this.id = id;
this.item = item;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
@Override
public String toString() {
return new StringJoiner(", ", Store.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("item=" + item)
.toString();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="item" scope="prototype" class="basic.ioc.constructor.xml.Item"/>
<bean id="store" scope="prototype" class="basic.ioc.constructor.xml.Store">
<constructor-arg type="java.lang.String" value="#{ T(java.util.UUID).randomUUID().toString() }"/>
<constructor-arg type="basic.ioc.constructor.xml.Item" ref="item"/>
</bean>
</beans>
package basic.ioc.constructor.xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXMLConstructorInjection {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-config.xml");
System.out.println(context.getBean(Store.class));
}
}
Output:
Store[id=ad25498e-9538-422f-99a4-eb65d112d6fd, item=Item[id=null, name='null']]
1.b. Java-based metadata config for constructor injection
Just like the XML bean config file, we create a java class BasicIocConfig
which is used to hold the metadata configurations. In a real-world scenario, you can have more than one such configuration class.
- The
@Configuration
annotation indicates the class is a bean definition config file. - The
@Bean
annotation indicates the bean name, used on a method to create a bean. - The default bean scope is a singleton, to get the prototype scope, so we used
@Scope
annotation. - Finally,
AnnotationConfigApplicationContext
the implementation class is used to create the instance of ApplicationContext.
package basic.ioc.constructor.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.util.UUID;
@Configuration
public class BasicIocConfig {
@Bean
@Scope("prototype")
public Item item() {
return new Item();
}
@Bean
@Scope("prototype")
public Store store() {
return new Store(
UUID.randomUUID().toString(), this.item()
);
}
}
package basic.ioc.constructor.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestInjection {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BasicIocConfig.class);
Store store = context.getBean(Store.class);
System.out.println(store);
}
}
// Store class same as above
public class Store {
private String id;
private Item item;
....
...
}
//Item class same as above
public class Item {
private Long id;
private String name;
...
...
}
Output:
Store[id=c960066d-5142-49f5-83bd-1d53012449ef, item=Item[id=null, name='null']]
NOTE: It is advisable to use Java based configuration with annotations, instead of XML based configurations.
2. Setter based Dependency Injection
The ApplicationContext
supports field-based dependency injection for the beans it manages. The IoC container calls the setter method(s) for the managed beans after it invokes the no-argument constructor or no-arg static method to instantiate.
Constructor based and setter based injection can be combined for the same bean.
2.a. XML based config for Setter DI
Setter injection is done using the property
tag as shown below.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="item" scope="prototype" class="basic.ioc.setter.xml.Item"/>
<bean id="store" scope="prototype" class="basic.ioc.setter.xml.Store">
<property name="id" value="#{ T(java.util.UUID).randomUUID().toString() }"/>
<property name="item" ref="item"/>
</bean>
</beans>
package basic.ioc.setter.xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXmlSetterInjection {
public static void main(String[] args) {
ApplicationContext context
= new ClassPathXmlApplicationContext("basic.ioc.setter.bean-config.xml");
Store store = context.getBean(Store.class);
System.out.println(store);
}
}
2.b. Java-based configuration for setter-injection
package basic.ioc.setter.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.util.UUID;
@Configuration
public class BasicIocSetterConfig {
@Bean
@Scope("prototype")
public Item item() {
return new Item();
}
@Bean
@Scope("prototype")
public Store store() {
Store storeObj = new Store();
storeObj.setId(UUID.randomUUID().toString());
storeObj.setItem(this.item());
return storeObj;
}
}
package basic.ioc.setter.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSetterInjection {
public static void main(String[] args) {
ApplicationContext context
= new AnnotationConfigApplicationContext(BasicIocSetterConfig.class);
Store store = context.getBean(Store.class);
System.out.println(store);
}
}
3. Field-based annotation-driven Dependency Injection
You can declare a class to be a Spring-managed bean class by using the @Component
annotation. This is a common practice.
NOTE: You can use @Inject
or @Autowired
annotations on a field (property). But this is not recommended due to performance impact. Instead, use the same annotation on setter or constructor.
package basic.ioc.autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.StringJoiner;
import java.util.UUID;
@Component
@Scope("prototype")
public class Store {
private String id;
/* Field injection is not recommended. Instead use, Constructor
or Setter injection
*/
@Autowired
private Item item;
public Store() {
id = UUID.randomUUID().toString();
}
...
}
public class Item {
private Long id;
private String name;
...
}
package basic.ioc.autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.util.Random;
import java.util.UUID;
@Configuration
@ComponentScan("basic.ioc.autowire")
public class AutowireBeanConfig {
@Bean
@Scope("prototype")
public Item item(){
return new Item (
new Random().nextLong(),
UUID.randomUUID().toString()
);
}
}
package basic.ioc.autowire;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAutowireExample {
public static void main(String[] args) {
ApplicationContext context
= new AnnotationConfigApplicationContext(AutowireBeanConfig.class);
System.out.println(context.getBean(Store.class));
System.out.println(context.getBean(Store.class));
}
}
Output
Store[id=c00b6e1c-253b-4957-a0ec-dc59a0a9161c, item=Item[id=-5148911103221427956, name='dd87f4ad-79bf-48e0-9e2f-3881fd874d47']] Store[id=81cdfbb3-1026-4c8f-83da-997806c09415, item=Item[id=2474607863045812177, name='381c9dbf-e21e-4ad3-922c-fdd62884da63']]
Lazy initialized Beans in Spring
By default, ApplicationContext
implementations eagerly creates all Singleton beans. You can control the behavior of the pre-instantiation of a singleton bean by marking it as Lazy bean.
...
<bean id="lazyPDFReader" class="com.something.PDFReader" lazy-init="true"/>
...
If the lazy-initialized bean is a dependency of non-lazy singleton bean, then Spring will create an instance of the lazy-bean at the application startup.
I have tried to keep the article as precise as possible. Do share your feedback in the comments below. This is all as part of the Inversion of Control and Dependency Injection in Spring. You can download the examples from the link below.