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 and FileSystemXmlApplicationContext. Similarly, AnnotationConfigApplicationContext if you are working in standalone annotations based configurations.

The metadata in Spring can be configured in 3 ways.

  1. Using pure XML based configuration
  2. Using the annotation-based configuration. This approach makes use of annotations and XML based configurations.
  3. 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);
  }
}
package basic.ioc.constructor.annotation;

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();
  }
}
package basic.ioc.constructor.annotation;

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();
  }
}

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);
  }
}
package basic.ioc.setter.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();
  }
}
package basic.ioc.setter.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();
  }
}

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);
  }
}
package basic.ioc.setter.annotation;

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();
  }
}
package basic.ioc.setter.annotation;

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();
  }
}

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 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();
  }
}
package basic.ioc.autowire;

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.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 early cerates 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.