Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 52

Dependency Injection and IoC in Spring

Recommended resources

Welcome to the next major phase of your journey to becoming a backend developer! In the last module, you mastered the fundamentals of connecting a Java application to a database using JDBC. You saw firsthand how much manual work is involved: managing connections, creating statements, handling ResultSets, and carefully closing resources. This process, while functional, is boilerplate-heavy and prone to errors.

Today, we begin our exploration of the Spring Framework, a powerful tool that eliminates this boilerplate and allows you to focus on building business logic. We will start with the two most fundamental principles that make Spring so effective: Inversion of Control (IoC) and Dependency Injection (DI). Understanding these concepts is non-negotiable for any Spring developer, as they form the philosophical core of the entire framework. By the end of this lesson, you will be able to explain what IoC and DI are and why they are crucial for building modern, maintainable applications.

The Problem: Tightly Coupled Code

Before we dive into Spring's solution, let's first understand the problem it solves. In object-oriented programming, we often build systems where objects collaborate with each other. A common anti-pattern is creating objects that are rigidly "tied" to the specific implementations of their collaborators. This is known as tight coupling.

Tightly coupled code is difficult to test, maintain, and extend. If you want to change one part of the system, you often have to make changes in many other places. A great way to understand this is with a simple algorithm example.

Spring Framework Tutorial for Beginners - Your First 10 Steps

This tutorial from springboottutorial.com uses a clear example of a binary search algorithm to demonstrate the problems of tight coupling and how to move towards a more flexible design.

Please read the first few steps of the tutorial. Start with Step 2. Here, a BinarySearchImpl class directly creates and uses a BubbleSortAlgorithm instance. Consider why this makes it difficult to swap bubble sort for, say, quick sort. Next, read Step 3, which introduces a SortAlgorithm interface. This is the classic object-oriented solution to break the tight coupling, a concept you're familiar with from our OOP module.

By introducing an interface, the BinarySearchImpl class is no longer dependent on a specific sorting algorithm, but rather on the abstraction of a sort algorithm. This is called loose coupling. The question now becomes: if BinarySearchImpl doesn't create its own SortAlgorithm instance, who does? And how does BinarySearchImpl get it?


Inversion of Control (IoC): Flipping the Responsibility

In traditional programming, your code is in charge. It creates objects, calls their methods, and controls the application's flow.

MyObject myObject = new MyObject();
myObject.doSomething();

Inversion of Control (IoC) is a design principle that inverts this flow. Instead of your code controlling everything, you delegate control to an external entity, typically a framework. This framework then becomes responsible for creating objects and calling your code when needed.

Think of it like this:

  • Traditional Control: You go to a restaurant and repeatedly walk to the kitchen to check if your food is ready. You control the process.
  • Inversion of Control: You go to the restaurant and they give you a buzzer. You sit down and relax. The kitchen (the framework) buzzes you when your food is ready. Control has been inverted—the framework calls you.

The Geekific channel has a short, animated video that provides an excellent high-level overview of this principle.

How Spring Boot Really Works? IoC, DI, Beans & the Magic Behind the Scenes! | Geekific

This video succinctly explains the core idea behind IoC and how it relates to Dependency Injection.

Watch the initial segment from the beginning until this timestamp. Focus on the clear distinction made between IoC as the guiding principle (handing over control) and Dependency Injection as the technique to achieve it.


Dependency Injection (DI): The How-To of IoC

So, if IoC is the principle of "giving up control," Dependency Injection (DI) is the primary pattern used to implement it. DI is the process whereby an object receives its dependencies from an external source rather than creating them itself.

Let's revisit our BinarySearchImpl example. It has a dependency on SortAlgorithm.

  • Without DI, BinarySearchImpl would create its own SortAlgorithm instance.
  • With DI, the SortAlgorithm instance is "injected" into BinarySearchImpl by an external entity.

The Baeldung article, a go-to resource for Java developers, illustrates this with a simple Store and Item example.

Inversion of Control and Dependency Injection with Spring

This article provides clear definitions and code examples to distinguish traditional object creation from the Dependency Injection pattern.

Please read the section What Is Dependency Injection?. Contrast the "traditional programming" example where the Store class is tightly coupled to ItemImpl1 with the DI example where the Store simply accepts any object that implements the Item interface.


The Spring IoC Container: The Master Assembler

In the Spring framework, the "external entity" responsible for IoC and DI is called the Spring IoC Container. Its primary job is to act as a master assembler for your application. You tell it what objects to manage and how they depend on each other, and it takes care of the rest.

The container's process looks like this:

  1. Read Configuration: It reads metadata you provide (in modern Spring, this is done via annotations).
  2. Instantiate Objects: It creates instances of your application's objects. These managed objects are called Beans.
  3. Assemble Dependencies: It performs dependency injection, wiring the beans together to form a fully configured application.
  4. Manage Lifecycle: It manages the entire lifecycle of these beans.

This entire process can be visualized with the following diagram:

This diagram shows the core function of the Spring IoC Container. It takes your business objects (POJOs) and configuration metadata (often annotations), and uses them to produce a fully configured and ready-to-use application.

DI in Action with Spring

Now, let's see how this works in practice. The "Selenium Express" channel has a fantastic video that walks through transforming a manual Java application into one managed by Spring. We will watch key segments to see the concepts come to life.

Spring Boot : IOC & Dependency Injection | Code your 1st Spring Boot project | Spring core

This video demonstrates the "before" and "after" of using Spring's IoC container. You'll see the manual, tedious process of object creation and dependency wiring, followed by the clean, automated approach using Spring.

The Manual Way: First, watch the segment from this timestamp. The presenter builds a simple application with Computer and Keyboard classes. Notice how the App class is responsible for creating a Keyboard object and manually "injecting" it into the Computer object using a setter method. This is the tight coupling and manual work we want to avoid. The Spring Way: Now, watch from this timestamp. The presenter adds the Spring framework and introduces two key annotations: @Component: Placed on Computer and Keyboard. This tells Spring, "These are beans. I want you to create and manage them." @Autowired: Placed on the setKeyboard method in the Computer class. This tells Spring, "Find a bean that matches this dependency (a Keyboard bean) and inject it here." Notice how the manual creation and wiring code in the App class disappears, replaced by a call to the Spring container to get the fully assembled Computer bean.

This demonstration shows the power of DI. The developer declares the components and their dependencies, and the Spring container does the hard work of wiring everything together.


Types of Dependency Injection

Spring provides three ways to perform dependency injection. It's crucial to know them, especially the recommended approach.

Inversion of Control and Dependency Injection with Spring

The Baeldung article explains the three types of DI with clear code examples. For now, focus on the annotation-based examples and the conceptual differences.

Read the following sections to understand the three DI mechanisms: Constructor-Based DI: Read the explanation in this section. In this style, dependencies are provided as arguments to the constructor. This is the most recommended approach because it ensures that an object is created with all its mandatory dependencies, making your objects more robust and their dependencies more explicit. Setter-Based DI: Read the explanation in this section. Here, the container calls setter methods on your bean after it has been instantiated. This is useful for optional dependencies that don't need to be present at creation time. Field-Based DI: Read the explanation and the drawbacks in this section. This involves placing @Autowired directly on the fields. While it looks clean and simple, it's generally discouraged. It makes your code harder to test in isolation (as you can't easily set the fields without reflection) and it can hide the dependencies of a class.

As a professional developer, you should default to constructor injection for mandatory dependencies. It makes your code cleaner, safer, and easier to test—qualities that are highly valued in the industry.


Conclusion

Today, you've grasped the foundational philosophy of the Spring Framework. Moving from the manual world of JDBC to the automated world of Spring is a significant leap, and it all starts here.

Key Takeaways:

  • Tight coupling makes code brittle and hard to maintain. Loose coupling, achieved through interfaces, is a much better design.
  • Inversion of Control (IoC) is a principle where you delegate the control of your application's flow to a framework. The framework calls your code, not the other way around.
  • Dependency Injection (DI) is the pattern used to achieve IoC. It's the process of providing an object with its dependencies from an external source.
  • The Spring IoC Container (ApplicationContext) is the "external source" that manages your objects (beans), including their creation and the injection of their dependencies.
  • You use annotations like @Component to declare a bean and @Autowired to request an injection.
  • Constructor injection is the preferred method for DI as it creates more robust and testable components.

You now understand the "magic" behind Spring. In our next lesson, we'll put this theory into immediate practice by using the Spring Initializr to set up your very first Spring Boot project and create beans that the container can manage.