Hello! Welcome back to our course on building a production-grade rate limiter.
In the last lesson, we set up a foundational Spring Boot project. Right now, it's a clean slate, but it's ready to be wired into a functional application. Today, we'll explore the "magic" that holds a Spring application together: the mechanism for managing and connecting different components.
This lesson addresses a core concept of the Spring framework. Understanding it is essential for building the modular, extensible rate limiter we're aiming for, where different algorithms and key-resolution strategies can be plugged in and out as needed.
Your learning outcome for this lesson:
Explain the role of dependency injection and how Spring manages beans.
The Problem: Managing Object Dependencies
In object-oriented programming, classes often depend on other classes to do their work. For instance, a RateLimiterInterceptor will need a RateLimitingAlgorithm to check if a request should be allowed.
A straightforward but problematic way to handle this is to have the RateLimiterInterceptor create its own dependency:
// The 'traditional' way of managing dependencies
public class RateLimiterInterceptor {
private RateLimitingAlgorithm algorithm;
public RateLimiterInterceptor() {
// The interceptor is responsible for creating its own dependency.
this.algorithm = new TokenBucketAlgorithm();
}
// ... method to handle requests using the algorithm
}
This approach creates tight coupling. The RateLimiterInterceptor is permanently tied to the TokenBucketAlgorithm. What if you want to switch to a SlidingWindowAlgorithm? You'd have to change the RateLimiterInterceptor's code. This violates the principle of modularity and makes testing difficult.
Spring solves this with a powerful design principle: Inversion of Control (IoC).
1. Inversion of Control (IoC) and Dependency Injection (DI)
Inversion of Control means that you delegate the responsibility of creating and managing your objects (and their dependencies) to an external entity—in our case, the Spring framework. Your code no longer controls the creation of its dependencies; that control is inverted.
Dependency Injection (DI) is the specific programming pattern Spring uses to implement IoC. Instead of an object creating its dependencies, the dependencies are "injected" into it from an outside source.
To start, let's get a clear definition of these two related concepts.
How Spring Boot Really Works? IoC, DI, Beans & the Magic Behind the Scenes! | Geekific
The video 'How Spring Boot Really Works?' by Geekific provides a concise explanation of Inversion of Control as a design principle and Dependency Injection as the technique that enables it.
Watch from 00:00:26 to 00:01:39. Focus on how the video distinguishes the broader principle (IoC) from the specific implementation (DI).
This simple architectural shift has profound benefits:
- Decoupling: Components are no longer responsible for creating their dependencies, making them easier to swap, reconfigure, and reuse.
- Modularity: You can focus on a component's specific business logic.
- Testability: You can easily "inject" mock versions of dependencies during unit tests.
The diagram below illustrates this flow. A Controller doesn't create a NotificationService; it simply declares that it needs one, and the framework provides it.

2. The Spring IoC Container and Beans
So, if your objects aren't creating their dependencies, who is? This is the job of the Spring IoC Container.
The container is the heart of a Spring application. It's an environment that performs three main tasks:
- Instantiates objects.
- Configures these objects (e.g., injects their dependencies).
- Manages their complete lifecycle (from creation to destruction).
The objects that the Spring IoC Container manages are called Spring Beans. A bean is simply a Plain Old Java Object (POJO) that you've handed over to Spring for management.
For a more detailed breakdown, the following article from Baeldung is an excellent reference.
Inversion of Control and Dependency Injection with Spring
The article 'Inversion of Control and Dependency Injection with Spring' provides clear Java examples contrasting the traditional approach with DI and explains the role of the Spring IoC Container.
Read sections 3 ('What Is Dependency Injection?') and 4 ('The Spring IoC Container'). Pay attention to the simple Store and Item code examples, which make the concept very clear.
3. Declaring and Injecting Beans in Practice
Now for the practical part: how do you tell Spring what to manage as a bean, and how do you get those beans where you need them?
There are two primary ways to declare a bean:
@Componentand its specializations: Used to mark a class you've written, telling Spring's component scanner to pick it up and register it as a bean.@Bean: Used inside a special@Configurationclass to declare a bean from a method. This is perfect for when you don't control the source code (e.g., third-party libraries) or need custom instantiation logic.
The following video from Dan Vega provides a fantastic, code-driven walkthrough of both methods. It starts by demonstrating the problem of manual instantiation and then shows how Spring's DI solves it.
Spring Beans Showdown: Unraveling the Mystery of @Component vs @Bean!
This video, 'Spring Beans Showdown', walks through a practical example of creating services and controllers, first the wrong way (manually) and then the right way using Spring's bean management.
Please watch from 00:03:16 to 00:14:58. This covers three key parts: The Problem (03:16 - 05:52): Demonstrates creating a new PostService() inside a controller. @Component (08:19 - 11:46): Shows how adding @Component (or @Service) to the PostService class allows Spring to manage it. @Bean (11:46 - 14:58): Explains how to create a bean from a method, using RestTemplate as a practical example for a third-party class.
Stereotype Annotations
As you saw in the video, @Component is the general-purpose annotation. However, Spring provides more specific annotations to give your components semantic meaning. These are called stereotype annotations:
@Service: For business logic classes (e.g., our futureRateLimitingService).@Repository: For data access classes that interact with a database.@Controller/@RestController: For classes that handle web requests.
Under the hood, all these annotations are themselves annotated with @Component. Using the more specific ones helps organize your application and allows frameworks to apply special behaviors (e.g., exception translation for @Repository).
Injecting Dependencies: Constructor Injection
Once a bean is registered in the Spring container, you can inject it into another bean. While there are a few ways to do this (field, setter, constructor), constructor injection is the highly recommended approach.
@Service // This class is a bean
public class OrderService {
private final PricingService pricingService; // The dependency
// The dependency is injected through the constructor
public OrderService(PricingService pricingService) {
this.pricingService = pricingService;
}
public void processOrder() {
// Use the injected service
double price = pricingService.calculatePrice();
// ...
}
}
@Service // The dependency is also a bean
public class PricingService {
public double calculatePrice() {
// ... logic
return 100.0;
}
}
Why is constructor injection preferred?
- Immutability: You can declare your dependencies as
final, ensuring they cannot be changed after the object is constructed. - Clarity: A class's required dependencies are clearly stated in its constructor signature.
- Guaranteed State: The object is guaranteed to be in a complete, usable state upon creation, as it cannot be instantiated without its dependencies.
The Baeldung article you looked at earlier has excellent sections detailing the different injection types if you want to explore their trade-offs further. You can refer to sections 5, 6, and 7 of the resource "Inversion of Control and Dependency Injection with Spring".
Key Takeaways
- Inversion of Control (IoC) is a core principle where you delegate object creation and management to a framework.
- Dependency Injection (DI) is the pattern Spring uses to implement IoC, providing objects with their dependencies from an external source.
- The Spring IoC Container (or
ApplicationContext) is the framework component that manages your objects. - Objects managed by the container are called Spring Beans.
- You declare your own classes as beans using stereotype annotations like
@Component,@Service, or@RestController. - You declare beans from third-party code or with custom logic using the
@Beanannotation inside a@Configurationclass. - Constructor injection is the recommended way to provide a bean with its dependencies.
Next Steps
You now have a solid theoretical and practical understanding of how Spring connects the different components of an application. This is the last piece of foundational knowledge we need before we can start building our API.
In the next lesson, we will use this knowledge to create our first REST API endpoint using @RestController and @RequestMapping. We will build a simple controller that uses an injected service, putting today's concepts directly into practice. This endpoint will be the target we will eventually protect with our rate limiter.