AnasayfaHakkımda
 
  

Dependency Injection in Kotlin and Spring Boot

2 Kasım 2022

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.service
interface VehicleService {
fun getVehicle(): String
}

and the implementation of CarServiceImpl from VehicleService:

package dev.gokhana.dependencyinjection.service
import org.springframework.stereotype.Service
@Service
class 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.controller
import dev.gokhana.dependencyinjection.service.VehicleService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class 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.controller
import dev.gokhana.dependencyinjection.service.VehicleService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class 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.controller
import dev.gokhana.dependencyinjection.service.VehicleService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class VehicleController {
lateinit var vehicleService: VehicleService
@Autowired
fun 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.controller
import dev.gokhana.dependencyinjection.service.VehicleService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class VehicleControllerField {
@Autowired
lateinit 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

 

Gökhan Ayrancıoğlu

Gökhan Ayrancıoğlu

Software Engineer @Yemeksepeti | Tech Blogger

 

Copyright © 2022 All rights reserved

Made with Coffee ☕