Dependency Injection in Kotlin and Spring Boot
Dependency injection is a programming technique/principle that aims to minimize the dependencies of a class and makes that class as independent as possible.
Instead of using the dependencies directly, the goal is to minimize the system’s dependence by injecting required objects. Dependency Injection(DI) simplifies the writing of unit tests and increases code accuracy. Also, loose coupling is one of the essential principles of Object Oriented Design, and one way to achieve loose coupling is Dependency Injection.
Dependency injection is a way to implement Inversion of control(IoC). Spring Containers are responsible for object creation and injecting dependencies.
Kotlin is getting more and more popular these days in JVM Ecosystem. But there are not many articles about the best practices for Kotlin. However, this article will cover all dependency injection practices. Dependency injection can be used in Kotlin to make our code more testable, maintainable, or less dependent.
There are three ways to implement dependency injection:
- Constructor Injection
- Setter Injection
- Field Injection
Diving Into Implementation
We will build a Vehicle Web Service for providing the get Vehicle feature via Controller.
Let’s say we have an interface for our service:
package dev.gokhana.dependencyinjection.serviceinterface VehicleService {fun getVehicle(): String}
and the implementation of CarServiceImpl from VehicleService:
package dev.gokhana.dependencyinjection.serviceimport org.springframework.stereotype.Service@Serviceclass CarServiceImpl : VehicleService {override fun getVehicle(): String {return "Vehicle is Car"}}
Constructor Injection
In the constructor injection, the arguments are passed to the constructor with the required dependencies of a class. If dependencies are mandatory (not optional) anyway, constructor injection might be the best way to inject these dependencies.
package dev.gokhana.dependencyinjection.controllerimport dev.gokhana.dependencyinjection.service.VehicleServiceimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RestController@RestControllerclass VehicleController @Autowired constructor(private val vehicleService: VehicleService) {@GetMapping("/vehicles/constructor")fun getVehicle(): String? {return vehicleService.getVehicle()}}
Since Spring v4.3: If only one constructor exists, then we don’t need to use @Autowired
annotation in Spring. We can operate the constructor injection directly without @Autowired
annotation for a single constructor, and it is more readable :)
package dev.gokhana.dependencyinjection.controllerimport dev.gokhana.dependencyinjection.service.VehicleServiceimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RestController@RestControllerclass VehicleController(private val vehicleService: VehicleService) {@GetMapping("/vehicles/constructor")fun getVehicle(): String? {return vehicleService.getVehicle()}}
If all dependencies are required/mandatory, and you want to guarantee this, constructor injection should be your first option. In constructor Injection, the IoC Container ensures that the dependencies are ready for injection.
“Spring included, provide a mechanism for ensuring that all dependencies are defined when you use Setter Injection, but by using Constructor Injection, you assert the requirement for the dependency in a container-agnostic manner” — Pro Spring Book
Constructor Injection supports immutability to provide thread safety, state safety, and readability in the classes.
Constructor injection is the most widely used dependency injection method.
I would suggest you follow constructor injection unless you run into one of the specific problems — Martin Fowler
Setter Injection
The setter injection passes the arguments of the setter methods of a class to set its required dependencies. Therefore, we should use @Autowired annotation for the setter method. Moreover, you can use setter injection in case the dependencies are optional.
Most of the cases, the setter injection is not chosen cause it is not guaranteed that “the object will be injected when the class is created.“ Setter Injection does not support immutability either.
package dev.gokhana.dependencyinjection.controllerimport dev.gokhana.dependencyinjection.service.VehicleServiceimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RestController@RestControllerclass VehicleController {lateinit var vehicleService: VehicleService@Autowiredfun setVehicleServiceSetter(vehicleService: VehicleService) {this.vehicleService = vehicleService}@GetMapping("/vehicles/setter")fun getVehicle(): String? {return vehicleService.getVehicle()}}
Field Injection
Spring passes the required dependencies to the fields with the @Autowired
annotation. The injection is done by assigning fields of the class. Thus, we are using @Autowired
annotation to let Spring handle field injection.
Field Injection is not supporting immutability.
Assume that we need to use VehicleService from another service like VehicleController.
package dev.gokhana.dependencyinjection.controllerimport dev.gokhana.dependencyinjection.service.VehicleServiceimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RestController@RestControllerclass VehicleControllerField {@Autowiredlateinit var vehicleService: VehicleService@GetMapping("/vehicles/field")fun getVehicle(): String? {return vehicleService.getVehicle()}}
Field injection is not recommended in most cases for Spring. It should not be used unless necessary.
Conclusion
Dependency injection is a powerful technique to write clean, loosely coupled, easy-to-maintainable, and testable code.
There are three types of injection; constructor, setter, and field injection. Constructor injection is the most preferred type. Constructor injection enforces the dependency requirement and makes it container-agnostic. Using constructor injection provides independence from Spring. Testing the class within the unit tests and elimination of an application context configuration and complexity.
In the setter injection, the values can be overridden which is not possible in the constructor injection. That means setter injection does not guarantee complete dependency injection. But setter Injection can be useful for optional dependencies.
Field injection has many drawbacks, and you should be careful while using it. *The field injection is not recommended overall.
Here is GitHub repository for the source code.
— Contact: gokhana.dev, Twitter, Linkedin, gokhana@mail.com
— Talk is cheap, show me code: GitHub
— 1:1 Superpeer