Lesson 3
Spring Beans
Introduction

Welcome to this lesson! So far, you have explored the foundational structure of a Spring Boot project. In this lesson, you'll delve into a crucial aspect of Spring Boot — Spring Beans. You'll learn how to create, manage, and utilize Spring Beans. Together, we'll understand dependencies, manual dependency management, Inversion of Control (IoC), and Dependency Injection (DI). We'll explore annotations like @Component and @Bean and see how they simplify dependency management using Spring Context. By the end of this lesson, you'll be equipped to leverage Spring's capabilities for effective bean management.

What is a Dependency?

Before diving into Spring Beans, let's clarify what a dependency is. Imagine you're building an application to prepare a sandwich. You can create a few classes like Bread, Lettuce, Tomato, and Cheese. Obviously, you'll also need a Grill class. Lastly, you'll need a skilled person to put all these ingredients together — the sandwich maker. The sandwich maker requires all the ingredients and a grill to prepare the sandwich, so all these objects are dependencies of the sandwich maker.

Understanding the Problem

Now, let's understand the complication of manually managing object dependencies. Imagine you need to grill a sandwich and you manually create all the ingredients and the grill. Here’s a simplified code snippet to demonstrate this:

Kotlin
1class Bread 2class Lettuce 3class Tomato 4class Cheese 5class Sandwich 6class Grill 7 8class SandwichMaker( 9 private val grill: Grill, 10 private val bread: Bread, 11 private val lettuce: Lettuce, 12 private val tomato: Tomato, 13 private val cheese: Cheese 14) { 15 fun prepareSandwich(): Sandwich { 16 println("Sandwich is ready!") 17 return Sandwich() 18 } 19} 20 21fun main() { 22 val grill = Grill() 23 val bread = Bread() 24 val lettuce = Lettuce() 25 val tomato = Tomato() 26 val cheese = Cheese() 27 val sandwichMaker = SandwichMaker(grill, bread, lettuce, tomato, cheese) 28 sandwichMaker.prepareSandwich() 29}

In this example, you manually handle all dependencies. While it looks simple, you need to ensure that you create all objects without dependencies first (bread, lettuce, tomato, and cheese) before constructing the more complex sandwichMaker object that requires these dependencies. Now, imagine you're working on an application for car manufacturing, where you try to assemble a car with thousands of components. As the number of dependencies grows, managing them manually becomes increasingly cumbersome.

Spring Context and Beans to the Rescue

This is where Inversion of Control (IoC) and Dependency Injection (DI) come into play. IoC is a broad principle or design pattern that inverts or delegates the control of object creation, configuration, and management from the class itself to an external framework. DI is a specific form of IoC, where dependencies are injected by the framework.

Spring manages objects in an IoC container called the Spring Context. Objects managed by the Spring Context are known as beans. The Spring Context is aware of all available beans and can determine the order and manner in which objects should be created.

Understanding @Component Annotation

Think of @Component as a special label that you put on a class to tell Spring, "Hey, this class should be managed by you!" By annotating a class with @Component, Spring will create a single instance of that class and manage it as a bean:

Kotlin
1package com.codesignal 2 3import org.springframework.stereotype.Component 4 5@Component 6class Grill

This automatically allows Spring to manage Grill as a bean within the context.

Let's break down what @Component does:

  • Discovery: When you run your Spring application, it will look through the code for classes labeled with @Component.
  • Creation: Spring will automatically create an instance (object) of each class it finds with this label. This means you don't have to manually create these objects yourself.
  • Management: Spring takes responsibility for these objects. It ensures they are created, configured, and provided wherever needed in your application.

This is useful for multiple reasons:

  • Reduces Manual Work: You don't need to write code to create objects of these classes repeatedly. Spring handles that for you.
  • Centralized Management: Spring keeps track of all these labeled classes, making it easier to manage dependencies and configurations.
  • Dependency Injection: Spring can automatically provide these objects to other parts of your application that need them.
How Spring Searches for Beans

The process where Spring searches for classes labeled with the @Component annotation, or other specific stereotype annotations like @Service and @Repository, is called component scanning.

It is essential to understand that Spring Boot scans only the base package of your application—where the bootstrap class is located—and its subpackages. For instance, if your bootstrap class is in the src/main/kotlin/com/codesignal/todoapp folder, all your components should be within this folder or its subfolders. If you place a component class in src/main/kotlin/com/codesignal/someotherfolder, it will be ignored.

Using @Bean Annotation

In addition to @Component, you can explicitly define beans using the @Bean annotation inside a configuration class. This offers you fine-grained control over bean creation.

Kotlin
1package com.codesignal 2 3import org.springframework.boot.autoconfigure.SpringBootApplication 4import org.springframework.boot.runApplication 5import org.springframework.context.annotation.Bean 6 7@SpringBootApplication 8class Application { 9 @Bean 10 fun grill(): Grill { 11 return Grill() 12 } 13} 14 15fun main(args: Array<String>) { 16 runApplication<Application>(*args) 17}

This allows you to manage and configure beans with more flexibility and precision.

Adding a Separate Configuration File with @Configuration

For better organization, you can extract configurations to separate files using the @Configuration annotation:

Kotlin
1package com.codesignal 2 3import org.springframework.context.annotation.Bean 4import org.springframework.context.annotation.Configuration 5 6@Configuration 7class SandwichConfig { 8 @Bean 9 fun bread(): Bread { 10 return Bread() 11 } 12 13 @Bean 14 fun lettuce(): Lettuce { 15 return Lettuce() 16 } 17 18 @Bean 19 fun tomato(): Tomato { 20 return Tomato() 21 } 22 23 @Bean 24 fun cheese(): Cheese { 25 return Cheese() 26 } 27 28 @Bean 29 fun sandwich(): Sandwich { 30 return Sandwich(bread(), lettuce(), tomato(), cheese(), grill()) 31 } 32 33 @Bean 34 fun grill(): Grill { 35 return Grill() 36 } 37}

Spring will scan all classes, and if it finds classes annotated with @Configuration, it will scan the methods. For methods annotated with @Bean, it will instantiate beans. Configuration classes make sense if you want to have separate configurations for different domains of your application. For example, you can have a separate configuration class for instantiating objects related to the database connection: DatabaseConfiguration, and another for security-related objects: SecurityConfiguration.

Difference between @Component and @Bean

The distinctions between the @Component and @Bean annotations are crucial for understanding how Spring manages beans:

  • @Component: This annotation is applied directly to a class. Its main purpose is to mark the class as a candidate for Spring's automatic management. Spring creates and manages a single instance of this class as a bean. It's suitable for classes defined and controlled within your application. However, @Component is not suitable when you need to create multiple instances of a class, as it only manages a single instance.

  • @Bean: This annotation is used within a method in a configuration class to explicitly declare a bean. It's particularly useful when you want to configure a third-party class or a class not directly controlled by your application code, such as classes from external libraries. The @Bean annotation is advantageous when you need to create multiple instances of a class and register them as beans. It offers more precision and control over how instances of these classes are created, configured, and managed.

Summary

In this lesson, you learned how to create and manage Spring Beans using @Component and @Bean annotations. Starting with the concept of dependencies and the challenge of manual dependency management, you were introduced to Inversion of Control (IoC) and Dependency Injection (DI). You saw how the Spring Context manages dependencies and how @Component enables automatic bean management. The @Bean annotation offers more control, while @Configuration aids in organizing beans in separate configuration files. Now, you’re ready for hands-on practice to solidify your understanding. Happy coding!

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