Skip to main content

Welcome to a Grasp lesson

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

Lesson 1

Spring IoC: Containers & Dependency Injection

Recommended resources

Hello! Welcome to the start of our course designed to sharpen your expertise for senior engineering interviews. Given your strong background in building and deploying complex systems, our goal isn't to learn things from scratch, but to formalize the concepts you use daily, helping you articulate design decisions with precision and confidence.

We'll begin with a deep dive into Spring Boot, a framework you're already very familiar with. This first module will solidify your understanding of its foundational "magic," ensuring you can explain not just what you're doing, but why the framework is designed that way.

In this first lesson, we'll tackle the core principles that underpin the entire Spring ecosystem. You'll be able to confidently explain Inversion of Control (IoC), the role of the ApplicationContext container, and the critical differences and trade-offs between the main types of Dependency Injection (DI): constructor, setter, and field injection. While these are patterns you instinctively use, mastering the terminology and rationale is key for any technical deep-dive or design discussion.

1. Inversion of Control (IoC): Flipping the Script

In traditional programming, your code is in charge. A UserService that needs a UserRepository would be responsible for creating and managing its instance: UserRepository repo = new UserRepositoryImpl();. Your code calls the library or dependency.

Inversion of Control, as the name suggests, flips this model. The framework takes control of the program flow and the creation and management of objects. Your custom code is then called by the framework. You provide the components (your services, repositories, controllers), and the framework wires them together and manages their lifecycle.

This principle is fundamental to modern application frameworks. It promotes loose coupling, making your components more modular, easier to test in isolation, and simpler to manage.

To formalize this concept, let's start with a concise article from Baeldung.

Inversion of Control and Dependency Injection with Spring

This article provides a clear, high-level overview of IoC. As you read, focus on the distinction between traditional programming and the IoC model.

Please read the introduction and the section titled "2. What Is Inversion of Control?". This covers the core principle and its advantages, from the beginning of the article down to the end of the second section, ending just before "We're going to look at DI next."

There are several ways to implement IoC, such as the Service Locator or Factory patterns. However, the most dominant and important pattern for our discussion is Dependency Injection (DI).

This diagram shows that Inversion of Control (IoC) is the overarching principle. Dependency Injection (DI) and Dependency Lookup (DL) are two primary strategies for implementing IoC. We will focus on DI, which is central to Spring.

2. Dependency Injection (DI) and the ApplicationContext

Dependency Injection is the concrete pattern that Spring uses to achieve IoC. Instead of an object creating its own dependencies, an external entity—the IoC container—"injects" those dependencies into the object.

Let's look at what this means in practice.

Inversion of Control and Dependency Injection with Spring

This section provides a simple, effective code example contrasting the traditional approach with DI.

Please read the section titled "3. What Is Dependency Injection?". Pay close attention to the Store class example, which clearly illustrates the shift in responsibility for dependency creation.

So, who is this "assembler" or "container" that does the injecting? In Spring, this is the IoC Container, and its most common implementation is the ApplicationContext.

When your Spring Boot application starts, it creates an ApplicationContext. This context is responsible for:

  1. Instantiating objects. In Spring, these managed objects are called beans.
  2. Configuring them by reading metadata (e.g., @Component, @Service, @Repository annotations).
  3. Assembling them by injecting beans into other beans that depend on them.
  4. Managing their entire lifecycle.

A "bean" is simply a Plain Old Java Object (POJO) whose lifecycle is managed by the Spring IoC container. The following video provides a very clear explanation of these core terms.

Understanding Beans and ApplicationContext in Spring Framework

This video by CodeSnippet offers a straightforward definition of beans and the ApplicationContext.

Watch the segment from the beginning of the main explanation. The speaker clarifies what a bean is, defines the IoC container, and explains how ApplicationContext is its concrete implementation. The diagram shown is particularly useful.

This diagram visualizes the role of the Spring IoC Container (`ApplicationContext`). It reads configuration metadata (like your annotations) to create and wire your business objects (beans).

3. Types of Dependency Injection: The Trade-offs

Now that we understand the what and why, let's focus on the how. Spring provides three primary mechanisms for dependency injection. Knowing the trade-offs between them is crucial for interview discussions, as it demonstrates a deeper understanding of software design principles.

The following video from Dan Vega is an excellent resource for this. It's aimed at developers like you and clearly articulates not just how each DI type works, but more importantly, why one is strongly preferred over the others.

Spring Constructor Injection: Why is it the recommended approach to Dependency Injection?

This video provides a practical walkthrough of the three DI types and a compelling argument for using constructor injection.

First, watch the section that introduces the three types of injection: Constructor, Setter, and Field. This will show you the syntax for each. Next, watch the detailed explanation of the pros and cons, particularly the drawbacks of field injection and the benefits of constructor injection. This is the most critical part for your interview preparation.

Let's summarize the key points from the video and solidify your understanding.

a) Constructor Injection (Recommended)

This is the recommended approach for mandatory dependencies. Dependencies are provided as arguments to the class's constructor.

@Service
public class MyService {

    private final DependencyRepository repository;

    // The dependency is injected through the constructor
    // @Autowired is optional on a single constructor since Spring 4.3
    public MyService(DependencyRepository repository) {
        this.repository = repository;
    }
}

Why it's preferred:

  • Immutability: Dependencies can be declared as final, ensuring they cannot be changed after the object is created. This makes your components more robust and thread-safe.
  • Explicit Dependencies: The constructor signature clearly defines the required dependencies. The object cannot be created in an invalid state (i.e., without its dependencies).
  • High Testability: It's simple to instantiate the class in a pure unit test by passing mocks directly to the constructor. You don't need the Spring framework to do this.
  • Circular Dependency Detection: If ServiceA depends on ServiceB and ServiceB depends on ServiceA, Spring will detect this circular dependency during context creation and fail fast with an error, forcing you to fix the design flaw.

b) Setter Injection

Dependencies are injected by the container calling setter methods after the bean has been instantiated with a no-argument constructor.

@Service
public class MyService {

    private OptionalDependency optionalDependency;

    @Autowired
    public void setOptionalDependency(OptionalDependency optionalDependency) {
        this.optionalDependency = optionalDependency;
    }
}

When to use it:

  • Optional Dependencies: This is the primary use case. The component can function without the dependency, but its behavior can be enhanced if one is provided.
  • Reconfigurability: Allows dependencies to be changed after creation, which can be useful in some specific contexts (e.g., via JMX), but is generally rare in modern microservices.

Setter injection can resolve circular dependencies, but this is usually a symptom of a poor design that should be refactored rather than accommodated.

c) Field Injection (Generally Discouraged)

Dependencies are injected directly into the fields using reflection, marked with @Autowired.

@Service
public class MyService {

    @Autowired
    private DependencyRepository repository; // Not recommended

}

Why it's discouraged:

  • Hides Dependencies: The class's dependencies are not part of its public contract (constructors or methods).
  • Violates Design Principles: It encourages reliance on a container and makes it difficult to instantiate the object manually. It also uses reflection to bypass private access modifiers.
  • Lower Testability: In a unit test, you must either use reflection yourself to set the private field or run the test with a Spring context, which slows it down and turns it into an integration test.
  • No Immutability: You cannot declare the fields as final.

The one place where field injection is widely considered acceptable is within test classes, where convenience often wins out.

Spring Constructor Injection: Why is it the recommended approach to Dependency Injection?

To round this out, watch this final clip from the same video.

This short segment shows the one scenario where field injection is commonly accepted: within test classes, often in combination with @MockBean.


Conclusion

In this lesson, we've established a solid conceptual framework for Spring's core mechanics. Let's recap the key interview takeaways:

  • Inversion of Control (IoC) is a design principle where a framework controls the flow and lifecycle of objects, rather than your code managing them directly. This promotes loose coupling.
  • The ApplicationContext is Spring's implementation of an IoC container. It acts as a central registry that instantiates, configures, and assembles your application's components, known as beans.
  • Dependency Injection (DI) is the pattern Spring uses to achieve IoC. It's the process of providing a bean with its required dependencies.
  • Constructor Injection is the preferred DI method for mandatory dependencies because it guarantees immutability (final), makes dependencies explicit, improves testability, and fails fast on circular dependencies.
  • Setter Injection is suitable for optional dependencies.
  • Field Injection is generally discouraged due to its negative impact on design clarity and testability, with the main exception being its use for convenience in test classes.

Being able to articulate these points clearly demonstrates a mature understanding of software design that goes beyond simply using a framework.

In our next lesson, we will build directly on this foundation to explore one of Spring Boot's most powerful features: auto-configuration. We'll see how Spring Boot uses the principles of IoC and DI, along with starter dependencies, to automatically configure your application context with minimal effort.