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 –
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 –
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();
}
}
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 Injection | Injection happens via constructor. used for mandatory dependencies. recommended. |
Setter Injection | Injection happens via setter methods. used for optional dependencies with a scope for fallback mechanism. recommended. |
Field Injection | Injection 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. |