Types of Dependency Injection in Spring Boot Explained

In this article, let us look at how Dependency Injection works and the different ways of Injection supported in Spring Boot with illustrating examples.

What is Dependency Injection (DI) and Inversion of Control (IoC)?

Inversion of control is an architectural pattern, where instead of classes instantiating the objects they need when needed using the new keyword, this instantiation is delegated to another component that is external to these classes. This component is called an IoC Container.

Each class requests an object of the class or component it needs from the container and the container provides the object of the dependency if registered with it.

This way the class doesn’t need to initialize and maintain the object by itself and the container is responsible for the object management.

This also promotes loose coupling of the classes, because there is no new keyword being used anywhere explicitly.

Dependency Injection is the approach in which the container injects the object of a dependency that is requested by the class.

Dependency Injection is the implementation that enforces Inversion of control.

Dependency Injection in Spring Boot

With Spring Boot, developers can use Dependency Injection in developing applications. Spring Boot provides a DI container where developers can register the dependencies they want to be managed by the container and can write code to inject these in the code.

There are 3 ways in which Spring Boot supports Dependency Injection in the application –

  • Constructor Injection
  • Setter Injection
  • Field Injection

Out of these 3, Constructor Injection is the most recommended by the Spring, followed by Setter Injection. In this article, let us look at each of these Injection approaches with a suitable example.

Constructor Injection in Spring Boot with an Example

In this method, the dependencies needed to be injected are passed as parameters to the constructor of the class and these dependencies are initialized within the constructor.

When the class is instantiated, the constructor is called and the container injects the dependencies before the class object is created.

To demonstrate Constructor Injection, let’s say we have a simple RESTful API Controller HeroController that has a single endpoint that will return a list of Heroes from the Service layer. The HeroController depends on HeroService that will return the data.

Now HeroService is injected into the HeroController using Constructor Injection as below –

package com.example.hello.controllers;

import com.example.hello.services.HeroService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/hero")
public class HeroController {

    private final HeroService constructorInjectedHeroService;

    public HeroController(HeroService constructorInjectedHeroService) {
        this.constructorInjectedHeroService = constructorInjectedHeroService;
    }

    @GetMapping("/")
    public String getHero() {
        return "I'm a Super Hero!";
    }

    @GetMapping("/getHeroesByConstructor")
    public List<String> getHeroesByConstructor() {
        return constructorInjectedHeroService.getHeroes();
    }
}

The field constructorInjectedHeroService is assigned to the object of HeroService within the Constructor and gets the instance during runtime.

when we run the application and hit the endpoint, we get the data as below –

GET Heroes by using Constructor Injection

Constructor Injection is the recommended approach for injection because the dependencies for the class are predictable, making it easy to unit test. This method is used when the dependencies to be injected are required for the class.

Setter Injection in Spring Boot with an Example

In this method, the dependencies are injected via setter methods inside the class. For the dependencies that are to be injected, class variables are created and setter methods for these are defined, where the dependencies are passed as method parameters.

These setter methods are annotated with @Autowired annotation to mark for injection.

To demonstrate Setter Injection, we will modify the HeroController and add another endpoint that will receive data from the HeroService using another object that is injected via setter injection.

This is done as below –

package com.example.hello.controllers;

import com.example.hello.services.HeroService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/hero")
public class HeroController {
    @Autowired
    public void setSetterInjectedHeroService(HeroService setterInjectedHeroService) {
        this.setterInjectedHeroService = setterInjectedHeroService;
    }

    private HeroService setterInjectedHeroService;

    @GetMapping("/")
    public String getHero() {
        return "I'm a Super Hero!";
    }

    @GetMapping("/getHeroesBySetter")
    public List<String> getHeroesBySetter() {
        return setterInjectedHeroService.getHeroes();
    }
}

The field setterInjectedHeroService is assigned to the object of HeroService within the Setter and gets the instance during runtime.

when we run the application and hit the endpoint, we get the data as below –

GET Heroes by Setter Injection

This is also a recommended approach for Dependency Injection, but is used when the dependencies to be injected are optional for the class. The advantage here is that you can add some custom fallback logic when the dependency injection fails.

Field Injection in Spring Boot with an Example

In this method, the dependencies are injected directly to the private variables inside the class. The variables which need to be assigned are annotated with @Autowired annotation.

To demonstrate field injection, lets say we have a private field in our HeroesController named fieldInjectedHeroService. To inject an instance of HeroService to this private variable, we annotate the field with @Autowired annotation. Spring Boot will internally use Reflections to assign the variable with the type required. When we modify our endpoint to use this variable, we will still get data during runtime.

The code looks like below –

package com.example.hello.controllers;

import com.example.hello.services.HeroService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/hero")
public class HeroController {

    @Autowired
    private HeroService fieldInjectedHeroService;

    @GetMapping("/")
    public String getHero() {
        return "I'm a Super Hero!";
    }

    @GetMapping("/getHeroesByField")
    public List<String> getHeroesByField() {
        return fieldInjectedHeroService.getHeroes();
    }
}
GET Heroes by Field Injection using Autowired

Although Field injection is convenient because we just need to annotate the field that needs injection, this is not a recommended approach for Dependency Injection because it makes unit testing the class harder, because the dependencies for the class are not predictable.

Conclusion

Dependency Injection is a coding approach that follows the concept of Inversion of Control. The aim here is to avoid instantiation and tight coupling of classes using new keyword and instead externalize the object management and its lifecycle to the framework.

Spring Boot provides Dependency Injection to Java applications and supports 3 types of Injection.

Constructor InjectionInjection happens via constructor. used for mandatory dependencies. recommended.
Setter InjectionInjection happens via setter methods. used for optional dependencies with a scope for fallback mechanism. recommended.
Field InjectionInjection happens via private fields within the class using Autowired annotation and reflections. although convenient, not recommended as it makes the class hard to unit test.
Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.