Skip to main content

Chapter 1: Inversion of Control (IoC)

Introduction​

Welcome! In this chapter, you will understand the fundamental principle that makes Spring work.

What You Will Learn​

  1. The problem of tight coupling (and why it's bad)
  2. The solution: Inversion of Control
  3. How Spring implements IoC
  4. The Spring Container and its role

Duration: 30 minutes


The Problem: Tight Coupling​

Real Situation​

Imagine you are developing an e-commerce application. You need to send emails to customers.

❌ Traditional Approach (Without IoC)​

public class OrderService {

// Directly creating the EmailService object
private EmailService emailService = new EmailService();

public void processOrder(Order order) {
// Business logic
saveOrder(order);

// Send confirmation email
emailService.sendEmail(order.getCustomerEmail(), "Order confirmed");
}
}

Why Is It Problematic?​

Problem 1: Tight Coupling​

private EmailService emailService = new EmailService();
// ^^^^^^^^^^^^^^^^
// DIRECT dependency!

Consequence:

  • OrderService DEPENDS DIRECTLY on EmailService
  • Impossible to change the implementation easily
  • Difficult to test

Problem 2: Impossible to Change the Implementation​

Scenario: You want to switch from Gmail to SendGrid to send emails.

// You have to MODIFY OrderService
public class OrderService {
// Before
private EmailService emailService = new EmailService();

// After - CODE HAS TO CHANGE!
private SendGridEmailService emailService = new SendGridEmailService();
// ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
}

Problem: Modifying working code = risk of bugs!


Problem 3: Impossible to Test​

You want to test OrderService without sending real emails:

@Test
public void testProcessOrder() {
OrderService service = new OrderService();
Order order = new Order(...);

service.processOrder(order);

// PROBLEM: A real email will be sent!
// Impossible to "mock" EmailService
}

Problem Summary​

OrderService ──────────> EmailService
(direct dependency)

❌ Tight coupling
❌ Difficult to modify
❌ Difficult to test
❌ Rigid and fragile code

The Solution: Inversion of Control (IoC)​

Basic Principle​

"Don't create your dependencies, ask for them!"

Instead of:

private EmailService emailService = new EmailService();  // ❌ I create

Do:

private EmailService emailService;  // ✅ SOMEONE gives it to me

What is IoC?​

Inversion of Control (IoC) = Reversing who controls the creation of objects

Before IoC (Normal Control)​

OrderService says: "I need an EmailService"
OrderService says: "I will create it myself with 'new'"

With IoC (Reversed Control)​

OrderService says: "I need an EmailService"
Spring says: "No problem, I'll give it to you!"
Spring creates EmailService and gives it to OrderService

The control of creation is REVERSED: Spring manages it, not you!


✅ Approach with IoC (Dependency Injection)​

Improved Code​

@Service
public class OrderService {

// We do NOT create the object
private final EmailService emailService;

// We ASK for the object via the constructor
public OrderService(EmailService emailService) {
this.emailService = emailService;
}

public void processOrder(Order order) {
saveOrder(order);
emailService.sendEmail(order.getCustomerEmail(), "Order confirmed");
}
}

What Happens​


Advantages of IoC​

1. Loose Coupling​

// OrderService does NOT know the concrete implementation
private final EmailService emailService;
// ^^^^^^^^^^^^
// Interface (abstraction)

You can have multiple implementations:

public interface EmailService {
void sendEmail(String to, String message);
}

public class GmailEmailService implements EmailService {
// Sends via Gmail
}

public class SendGridEmailService implements EmailService {
// Sends via SendGrid
}

Spring chooses which one to use according to the configuration!


2. Easy to Modify​

Change implementation WITHOUT touching OrderService:

// Spring Configuration
@Configuration
public class AppConfig {

@Bean
public EmailService emailService() {
// Before
// return new GmailEmailService();

// After - Change in ONE place!
return new SendGridEmailService();
}
}

OrderService does NOT need to change!


3. Easy to Test​

@Test
public void testProcessOrder() {
// Create a "fake" EmailService for testing
EmailService fakeEmailService = new EmailService() {
@Override
public void sendEmail(String to, String message) {
System.out.println("Simulated email sent to: " + to);
// No real email sent!
}
};

// Inject the fake service
OrderService service = new OrderService(fakeEmailService);

Order order = new Order(...);
service.processOrder(order);

// ✅ No real email sent!
}

The Spring Container (ApplicationContext)​

What is the Container?​

The Spring Container = Spring's object manager

Its responsibilities:

  1. ✅ Create objects (beans)
  2. ✅ Configure them
  3. ✅ Assemble them (inject dependencies)
  4. ✅ Manage their lifecycle

Analogy​

Think of the Spring Container as a conductor:

Conductor (Spring Container)
↓
Musicians (Beans: Controller, Service, Repository)
↓
The orchestra plays music (The application runs)

The conductor:

  • ✅ Recruits the musicians (creates the beans)
  • ✅ Places them in the right spot (injection)
  • ✅ Coordinates their work (lifecycle management)

The Container in Action​

// At application startup
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// This line starts the Spring Container
SpringApplication.run(DemoApplication.class, args);
// ^^^
// The Container starts here!
}
}

What the Container does at startup:

1. Scan classes with @Component, @Service, @Repository, @Controller
↓
2. Create instances (beans)
↓
3. Resolve dependencies
↓
4. Inject dependencies
↓
5. Application ready!

Container Visualization​

The Container manages ALL objects and their dependencies!


Complete Example​

Without IoC (Old Code)​

public class OrderService {
private EmailService emailService = new EmailService();
private PaymentService paymentService = new PaymentService();
private InventoryService inventoryService = new InventoryService();

public void processOrder(Order order) {
paymentService.charge(order);
inventoryService.reserve(order);
emailService.sendConfirmation(order);
}
}

Problems:

  • ❌ 3 dependencies created with new
  • ❌ Tight coupling everywhere
  • ❌ Impossible to test
  • ❌ Rigid and fragile

With IoC (Spring Code)​

@Service
public class OrderService {

// Declaration of dependencies (no 'new')
private final EmailService emailService;
private final PaymentService paymentService;
private final InventoryService inventoryService;

// Injection via constructor
public OrderService(
EmailService emailService,
PaymentService paymentService,
InventoryService inventoryService
) {
this.emailService = emailService;
this.paymentService = paymentService;
this.inventoryService = inventoryService;
}

public void processOrder(Order order) {
paymentService.charge(order);
inventoryService.reserve(order);
emailService.sendConfirmation(order);
}
}

Advantages:

  • ✅ 0 use of new (Spring manages everything)
  • ✅ Loose coupling
  • ✅ Easy to test (can inject fakes)
  • ✅ Flexible and maintainable

Key Concepts to Remember​

1. Inversion of Control (IoC)​

"Spring controls the creation of objects, not you."

Before: You create your objects with new
After: Spring creates your objects and gives them to you


2. Dependency Injection (DI)​

"Spring injects (gives) the dependencies you need."

Mechanism: Via constructor, setters, or directly in fields


3. Spring Container​

"The central manager that creates and assembles all objects."

Responsibilities:

  • Create beans
  • Resolve dependencies
  • Inject dependencies
  • Manage lifecycle

Chapter Summary​

The Problem​

// ❌ Tight coupling
private EmailService service = new EmailService();

Consequences:

  • Difficult to modify
  • Difficult to test
  • Rigid code

The Solution​

// ✅ IoC + Dependency Injection
@Service
public class OrderService {
private final EmailService service;

public OrderService(EmailService service) {
this.service = service;
}
}

Advantages:

  • Loose coupling
  • Easy to modify
  • Easy to test
  • Flexible code

Final Analogy​

IoC = Restaurant

Without IoC:

You: "I'm hungry"
You: "I'll buy the ingredients"
You: "I'll cook"
You: "I eat"

With IoC:

You: "I'm hungry" (declaration of need)
Restaurant: "Here's your dish!" (Spring provides)
You: "I eat" (you use)

You don't create the dish, it is GIVEN to you. That's IoC!


Self-Assessment​

Answer these questions to check your understanding:

  1. What is Inversion of Control?
  2. What is the problem with tight coupling?
  3. What is the role of the Spring Container?
  4. Why does IoC make testing easier?

If you can't answer, reread the chapter!


Next Step​

Now that you understand WHY IoC exists, let's move on to HOW: Dependency Injection.

➡️ Chapter 2: Dependency Injection

⬅️ Back to Module 5