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.
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.
Now, let's understand the complication of manually managing object dependencies. Imagine you need to grill a sandwich and you manually create all ingredients and the grill. Here’s a simplified code snippet to demonstrate this:
Java1class Bread {} 2class Lettuce {} 3class Tomato {} 4class Cheese {} 5class Sandwich {} 6class Grill {} 7 8public class SandwichMaker { 9 private Grill grill; 10 11 private Bread bread; 12 private Lettuce lettuce; 13 private Tomato tomato; 14 private Cheese cheese; 15 16 public SandwichMaker(Grill grill, Bread bread, Lettuce lettuce, Tomato tomato, Cheese cheese) { 17 this.grill = grill; 18 19 this.bread = bread; 20 this.lettuce = lettuce; 21 this.tomato = tomato; 22 this.cheese = cheese; 23 } 24 25 public Sandwich prepareSandwich() { 26 System.out.println("Sandwich is ready!"); 27 return new Sandwich(); 28 } 29 30 public static void main(String[] args) { 31 Grill grill = new Grill(); 32 33 Bread bread = new Bread(); 34 Lettuce lettuce = new Lettuce(); 35 Tomato tomato = new Tomato(); 36 Cheese cheese = new Cheese(); 37 38 SandwichMaker sandwichMaker = new SandwichMaker(grill, bread, lettuce, tomato, cheese); 39 sandwichMaker.prepareSandwich(); 40 } 41}
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.
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.
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:
Java1package com.codesignal; 2 3import org.springframework.stereotype.Component; 4 5@Component 6public class 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.
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/java/com/codesignal/todo-app
folder, all your components should be within this folder or its subfolders. If you place a component class in src/main/java/com/codesignal/some-other-folder
, it will be ignored.
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.
Java1package com.codesignal; 2 3import org.springframework.boot.SpringApplication; 4import org.springframework.boot.autoconfigure.SpringBootApplication; 5import org.springframework.context.annotation.Bean; 6 7@SpringBootApplication 8public class Application { 9 public static void main(String[] args) { 10 SpringApplication.run(Application.class, args); 11 } 12 13 @Bean 14 public Grill grill() { 15 return new Grill(); 16 } 17}
This allows you to manage and configure beans with more flexibility and precision.
For better organization, you can extract configurations to separate files using the @Configuration
annotation:
Java1package com.codesignal; 2 3import org.springframework.context.annotation.Bean; 4import org.springframework.context.annotation.Configuration; 5 6@Configuration 7public class SandwichConfig { 8 @Bean 9 public Bread bread() { 10 return new Bread(); 11 } 12 13 @Bean 14 public Lettuce lettuce() { 15 return new Lettuce(); 16 } 17 18 @Bean 19 public Tomato tomato() { 20 return new Tomato(); 21 } 22 23 @Bean 24 public Cheese cheese() { 25 return new Cheese(); 26 } 27 28 @Bean 29 public Sandwich sandwich() { 30 return new Sandwich(bread(), lettuce(), tomato(), cheese(), grill()); 31 } 32 33 @Bean 34 public Grill grill() { 35 return new 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 database connection: DatabaseConfiguration
, and another for security-related objects: SecurityConfiguration
.
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!