Lesson 4
Dependency Injection in Spring Boot
Introduction

Welcome to this lesson on Dependency Injection in Spring Boot. So far, we've covered the basics of Spring and Spring Boot, examined the typical project structure, and delved into the important files within a Spring Boot project. We've also discussed core concepts like Inversion of Control (IoC) and Dependency Injection (DI). In our last lesson, we learned how to create simple beans without dependencies. Today, we’ll build on that foundation by learning how to create beans with dependencies, leveraging Spring’s powerful DI capabilities.

Dependency Injection at a Glance

Dependency Injection is a straightforward concept that becomes incredibly powerful when used correctly. Essentially, if you manually instantiate an object, you have a few approaches:

  • Use the default constructor and instantiate properties via setters.
  • Use a non-default constructor and pass all parameters into it.
  • Use the default constructor with reflection to instantiate private class fields.

Spring simplifies this process by searching for dependencies by type, making it easier to wire dependencies into your classes.

To handle this automatic wiring, Spring uses the @Autowired annotation, which marks the points where dependencies should be injected. In Kotlin, you can use val, var, and property injection effectively.

Constructor-based Implicit @Autowired

Constructor-based DI is the preferred method in Kotlin due to its compatibility with the primary constructor paradigm and alignment with immutability principles. This involves passing all dependencies into the constructor to create an object:

Kotlin
1package com.codesignal 2 3import org.springframework.stereotype.Component 4 5@Component 6class Salad( 7 private val lettuce: Lettuce, 8 private val tomato: Tomato 9)

In this example, the Salad class uses a primary constructor where the dependencies lettuce and tomato are injected. The @Autowired annotation is omitted because Spring will automatically use the primary constructor for dependency injection when only one constructor is present. Constructor injection is generally preferred in Kotlin for better readability, immutability, and testability.

Constructor-based Explicit @Autowired

Although Spring boot automatically detects only one constructor, you can explicitly mark the constructor with @Autowired:

Kotlin
1package com.codesignal 2 3import org.springframework.beans.factory.annotation.Autowired 4import org.springframework.stereotype.Component 5 6@Component 7class Sandwich @Autowired constructor( 8 private val bread: Bread, 9 private val cheese: Cheese 10)

Here, the @Autowired annotation explicitly marks the constructor, making it clear where Spring should perform the injection.

Field-based @Autowired With lateinit

Field-based DI involves directly injecting dependencies into class fields:

Kotlin
1package com.codesignal 2 3import org.springframework.beans.factory.annotation.Autowired 4import org.springframework.stereotype.Component 5 6@Component 7class Juice { 8 9 @Autowired 10 lateinit var water: Water 11 12 @Autowired 13 lateinit var sugar: Sugar 14 15 // Getters for water and sugar can be added if needed 16}

In this example, @Autowired is placed directly on the properties water and sugar. Note that the lateinit keyword is used to indicate that these properties will be initialized later. However, constructor injection is generally preferred for better testability and encapsulation.

Mixed Approach

Using a mixed approach can be beneficial by injecting essential dependencies through the constructor and optional ones through field injection with setters if needed:

Kotlin
1package com.codesignal 2 3import org.springframework.beans.factory.annotation.Autowired 4import org.springframework.stereotype.Component 5 6@Component 7class Pizza @Autowired constructor( 8 private val dough: Dough, 9 private val sauce: Sauce 10) { 11 12 @Autowired 13 lateinit var cheese: Cheese 14}

In this example, the Pizza class is annotated with @Component, making it a Spring-managed bean. The class uses constructor-based dependency injection for Dough and Sauce as essential dependencies. Although the @Autowired annotation is not required on the constructor (since Spring will automatically use the primary constructor if only one is present), it can be included for clarity.

Additionally, the cheese property is marked with @Autowired and lateinit, indicating that Spring will inject this dependency after the object's construction. This example demonstrates a mix of mandatory constructor-based injection and field-based injection, providing flexibility in managing dependencies.

Method Dependency Injection with `@Bean`

Spring also allows for method-based dependency injection using the @Bean annotation. This technique involves injecting dependencies via method parameters in Kotlin configuration classes.

Example:

Kotlin
1package com.codesignal 2 3import org.springframework.context.annotation.Bean 4import org.springframework.context.annotation.Configuration 5 6@Configuration 7class AppConfig { 8 9 @Bean 10 fun sandwich(bread: Bread, cheese: Cheese): Sandwich { 11 return Sandwich().apply { 12 this.bread = bread 13 this.cheese = cheese 14 } 15 } 16 17 @Bean 18 fun bread(): Bread { 19 return Bread() 20 } 21 22 @Bean 23 fun cheese(): Cheese { 24 return Cheese() 25 } 26}

In this example, the sandwich method is annotated with @Bean, and it takes Bread and Cheese as parameters. Spring's application context will automatically resolve these dependencies by calling the bread and cheese methods, respectively, to supply the required beans. This method-based injection is useful for creating bean instances that depend on other beans defined in the same configuration class.

Summary

In this lesson, we explored various Dependency Injection methods in Spring Boot, including setter, constructor, field injection, @Bean method injection, along with a mixed approach combining these methods. These techniques help create modular, testable, and maintainable applications. Up next, you’ll get hands-on practice with these concepts to solidify your understanding.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.