Spring IoC & Dependency Injection

Prepared By SRIKANTH MAMILLAPALLI

Spring Training Notes • Dependency, IoC, Dependency Lookup, Dependency Injection & Spring Beans

1. What is a Dependency?

A dependency exists when one class needs another class to perform its work.

class EmailService {

    public void sendEmail(String message) {
        System.out.println("Sending: " + message);
    }
}

class OrderService {

    private EmailService emailService;

    public void placeOrder() {
        emailService.sendEmail("Order placed");
    }
}
Relationship:
OrderService ---> EmailService

OrderService depends on EmailService because it needs EmailService to perform its email-related functionality.

2. The Problem: Who Creates the Dependency?

A simple implementation may create the dependency inside the class.

class OrderService {

    private EmailService emailService = new EmailService();

    public void placeOrder() {
        emailService.sendEmail("Order placed");
    }
}

Now OrderService is responsible for creating and using EmailService.

OrderService | | creates v EmailService

If the implementation changes from EmailService to SMS or another notification mechanism, OrderService must also change.

3. What is Tight Coupling?

When a class directly creates and depends on a concrete implementation, the classes become tightly coupled.

class OrderService {

    private EmailService emailService =
            new EmailService();

    public void placeOrder() {
        emailService.sendEmail("Order placed");
    }
}

OrderService is tightly coupled to EmailService.

Problem: If we replace EmailService with GmailService, SmsService or another implementation, OrderService has to be modified.

A common design principle is:

Program to an interface, not an implementation.

4. Better Design Using an Interface

public interface NotificationService {

    void sendNotification(String message);
}

Email Implementation

class EmailNotificationService
        implements NotificationService {

    @Override
    public void sendNotification(String message) {
        System.out.println(
            "Sending Email: " + message);
    }
}

SMS Implementation

class SmsNotificationService
        implements NotificationService {

    @Override
    public void sendNotification(String message) {
        System.out.println(
            "Sending SMS: " + message);
    }
}

Now the client can depend on the interface rather than a concrete implementation.

class OrderService {

    private NotificationService notificationService;

    public void placeOrder() {
        notificationService.sendNotification(
            "Order placed");
    }
}

The remaining question is: Who provides NotificationService to OrderService?

This leads us to Dependency Injection.

5. Different Approaches for Managing Dependencies

Ways of obtaining/managing a dependency ├── 1. Composition ├── 2. Factory Pattern ├── 3. JNDI Registry ├── 4. Inheritance └── 5. IoC ├── Dependency Lookup / Pull └── Dependency Injection / Push ├── Constructor Injection ├── Setter Injection └── Field Injection

6. Composition

Composition means one class contains or uses another object as a member.

class Engine {

    public void start() {
        System.out.println("Engine started");
    }
}

class Car {

    private Engine engine;

    public Car() {
        this.engine = new Engine();
    }

    public void drive() {
        engine.start();
        System.out.println("Car is running");
    }
}
Car | | contains v Engine

This represents a HAS-A relationship.

Composition with Dependency Injection

class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        engine.start();
        System.out.println("Car is running");
    }
}

Engine engine = new Engine();
Car car = new Car(engine);

Now Car does not create Engine. The dependency is provided from outside.

7. Factory Pattern

A Factory centralizes object creation logic.

interface NotificationService {

    void send(String message);
}

class EmailNotificationService
        implements NotificationService {

    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

class SmsNotificationService
        implements NotificationService {

    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

Factory

class NotificationFactory {

    public static NotificationService
        getService(String type) {

        if ("EMAIL".equalsIgnoreCase(type)) {
            return new EmailNotificationService();
        }

        if ("SMS".equalsIgnoreCase(type)) {
            return new SmsNotificationService();
        }

        throw new IllegalArgumentException(
            "Unknown notification type");
    }
}

Client

class OrderService {

    private NotificationService notificationService;

    public OrderService(String type) {
        this.notificationService =
            NotificationFactory.getService(type);
    }

    public void placeOrder() {
        notificationService.send("Order placed");
    }
}
OrderService | | asks v NotificationFactory | +----> EmailNotificationService | +----> SmsNotificationService

The Factory hides object creation logic, but the client still knows about the Factory.

8. JNDI Registry

JNDI stands for Java Naming and Directory Interface.

It allows an application to look up resources from a naming registry.

Application | | lookup("resource-name") v JNDI Registry | v Resource

Example

Context context = new InitialContext();

DataSource dataSource =
    (DataSource) context.lookup(
        "java:/comp/env/jdbc/MyDB");

The application asks the registry for a resource registered under a particular name.

This is an example of Dependency Lookup.

9. Dependency Lookup / Pull Model

In Dependency Lookup, the object itself asks a container or registry for its dependency.

class OrderService {

    public void process() {

        NotificationService service =
            Container.getNotificationService();

        service.send("Order placed");
    }
}
OrderService | | "Give me NotificationService" v Container

This is called the Pull Model because the object pulls the dependency.

Key point: The class generally knows about the container or lookup mechanism.

10. Dependency Injection / Push Model

In Dependency Injection, the class does not ask for its dependency. The dependency is provided to the class.

class OrderService {

    private NotificationService notificationService;

    public OrderService(
            NotificationService notificationService) {

        this.notificationService =
            notificationService;
    }

    public void placeOrder() {
        notificationService.send("Order placed");
    }
}
NotificationService notificationService =
    new EmailNotificationService();

OrderService orderService =
    new OrderService(notificationService);
NotificationService | | injected v OrderService

The dependency is pushed into OrderService.

11. What is IoC?

IoC means Inversion of Control.

Normally, application code controls object creation:

OrderService service = new OrderService();

EmailService emailService =
    new EmailService();

Your code decides:

When to create What to create How to create Which implementation to use

With Spring, control is transferred to the Spring IoC Container.

You | | Configure application components v Spring IoC Container | | creates objects | manages objects | injects dependencies v Application
IoC means that control over object creation and dependency management is transferred from your application code to the Spring container.

12. Spring IoC Container

The central concept in Spring Core is the IoC Container.

BeanFactory | v ApplicationContext

ApplicationContext is the commonly used container in Spring applications.

It manages Spring Beans.

A Spring Bean is simply an object that is created and managed by Spring.

@Component
public class EmailService {

    public void sendEmail(String message) {
        System.out.println(
            "Email sent: " + message);
    }
}

Spring detects the class and creates an object managed by the container.

13. Basic Spring IoC Example

Step 1: Interface

public interface NotificationService {

    void send(String message);
}

Step 2: Implementation

import org.springframework.stereotype.Component;

@Component
public class EmailNotificationService
        implements NotificationService {

    @Override
    public void send(String message) {
        System.out.println(
            "Email sent: " + message);
    }
}

@Component tells Spring to create and manage an object of this class.

Step 3: Client Class

import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final NotificationService notificationService;

    public OrderService(
            NotificationService notificationService) {

        this.notificationService =
            notificationService;
    }

    public void placeOrder() {

        System.out.println("Order placed");

        notificationService.send(
            "Order confirmation");
    }
}
Notice: We did not use new EmailNotificationService() inside OrderService. Spring provides the dependency.

14. What Happens During Spring Boot Startup?

@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(
            Application.class,
            args);
    }
}

Conceptually, Spring Boot performs these activities:

SpringApplication.run() | v Create ApplicationContext | v Scan classes | v Find @Component / @Service / @Repository / @Controller | v Create Bean Definitions | v Create Bean Objects | v Resolve Dependencies | v Perform Dependency Injection | v Initialize Beans | v Application Ready

15. Constructor Injection

Constructor Injection is generally the preferred form of dependency injection for required dependencies.

@Service
public class OrderService {

    private final NotificationService notificationService;

    public OrderService(
            NotificationService notificationService) {

        this.notificationService =
            notificationService;
    }
}

With a single constructor, modern Spring can automatically use it without @Autowired.

You can also explicitly write:

@Autowired
public OrderService(
        NotificationService notificationService) {

    this.notificationService =
        notificationService;
}

Why Constructor Injection?

  • Required dependencies are available during construction.
  • Dependencies are clearly visible.
  • Fields can be final.
  • Easy to unit test.
  • Reduces the possibility of creating an invalid object.

16. Setter Injection

In Setter Injection, Spring creates the object and then calls a setter method to provide the dependency.

@Service
public class OrderService {

    private NotificationService notificationService;

    @Autowired
    public void setNotificationService(
            NotificationService notificationService) {

        this.notificationService =
            notificationService;
    }
}
1. Spring creates OrderService | v 2. Spring calls setNotificationService() | v 3. Dependency is injected

When is Setter Injection useful?

  • Optional dependencies
  • Configurable dependencies
  • Dependencies that do not need to exist during construction

17. Field Injection

@Service
public class OrderService {

    @Autowired
    private NotificationService notificationService;

    public void placeOrder() {

        notificationService.send(
            "Order placed");
    }
}

Spring injects the dependency directly into the field, generally using reflection.

Advantages

  • Very little code.
  • Easy to write for simple examples.

Disadvantages

  • Dependencies are hidden.
  • Fields are generally not final.
  • Unit testing without Spring is less convenient.
  • It can encourage classes with too many hidden dependencies.
Recommendation: Prefer constructor injection for required dependencies in new application code.

18. Constructor vs Setter vs Field Injection

Feature Constructor Setter Field
Required dependency Excellent Less suitable Less suitable
Optional dependency Possible Good Possible
Immutable field Yes No Usually no
Dependencies clearly visible Yes Moderately No
Unit testing Easy Easy Less convenient
General recommendation Preferred Sometimes Generally avoid in new code
Required dependency ↓ Constructor Injection Optional/configurable dependency ↓ Setter Injection Field Injection ↓ Usually avoid in new code

19. Why Interfaces Are Important in DI

public interface NotificationService {

    void send(String message);
}

Possible implementations:

EmailNotificationService SmsNotificationService PushNotificationService

All implementations follow the same abstraction:

NotificationService

Therefore, OrderService can depend on the interface:

public OrderService(
        NotificationService notificationService) {

    this.notificationService =
        notificationService;
}
OrderService does not need to know which concrete implementation is used. This promotes loose coupling.

20. Multiple Implementations and @Qualifier

Suppose there are two implementations:

@Component("emailService")
public class EmailNotificationService
        implements NotificationService {
}

@Component("smsService")
public class SmsNotificationService
        implements NotificationService {
}

If we write:

public OrderService(
        NotificationService notificationService) {
}

Spring may not know which implementation should be injected.

Use @Qualifier:

@Service
public class OrderService {

    private final NotificationService notificationService;

    public OrderService(
            @Qualifier("emailService")
            NotificationService notificationService) {

        this.notificationService =
            notificationService;
    }
}
OrderService | | @Qualifier("emailService") v EmailNotificationService

21. @Primary

If multiple implementations exist, one implementation can be marked as the default using @Primary.

@Component
@Primary
public class EmailNotificationService
        implements NotificationService {
}

Then Spring can inject it when the dependency type is:

NotificationService

without requiring a qualifier in every injection point.

22. What is a Spring Bean?

A Spring Bean is an object created, configured, and managed by the Spring IoC container.

@Component
public class EmailService {
}

Other common stereotype annotations include:

AnnotationTypical Purpose
@ComponentGeneral Spring-managed component
@ServiceBusiness/service layer
@RepositoryPersistence/data-access layer
@ControllerWeb/MVC controller
@RestControllerREST API controller
@Component | +---- @Service | +---- @Repository | +---- @Controller

23. Using @Bean

Instead of annotating a class with @Component, we can explicitly configure a bean using @Bean.

@Configuration
public class AppConfig {

    @Bean
    public EmailService emailService() {

        return new EmailService();
    }
}

This is particularly useful for configuring third-party classes that you cannot modify to add @Component.

Example

@Configuration
public class AppConfig {

    @Bean
    public ObjectMapper objectMapper() {

        return new ObjectMapper();
    }
}

Spring can then inject ObjectMapper wherever it is required.

24. @Component vs @Bean

@Component@Bean
Placed on the class Placed on a method
Usually discovered through component scanning Explicitly registered through configuration
Good for classes you control Useful for third-party classes
Example: business/service classes Example: ObjectMapper, custom clients

25. Composition vs Inheritance

Inheritance

class Vehicle {

    public void start() {
    }
}

class Car extends Vehicle {
}

Relationship:

Car IS-A Vehicle

Composition

class Car {

    private Engine engine;
}

Relationship:

Car HAS-A Engine
Inheritance ↓ IS-A Composition ↓ HAS-A

For many application-design situations, composition is preferred when the relationship is genuinely "has-a", because it avoids unnecessary inheritance coupling.

26. Real-World Example: Payment System

PaymentGateway Interface

public interface PaymentGateway {

    void pay(double amount);
}

Implementation

@Component
public class StripePaymentGateway
        implements PaymentGateway {

    @Override
    public void pay(double amount) {

        System.out.println(
            "Payment using Stripe: " + amount);
    }
}

PaymentService

@Service
public class PaymentService {

    private final PaymentGateway paymentGateway;

    public PaymentService(
            PaymentGateway paymentGateway) {

        this.paymentGateway = paymentGateway;
    }

    public void processPayment(double amount) {

        paymentGateway.pay(amount);
    }
}

OrderService

@Service
public class OrderService {

    private final PaymentService paymentService;

    public OrderService(
            PaymentService paymentService) {

        this.paymentService = paymentService;
    }

    public void placeOrder(double amount) {

        paymentService.processPayment(amount);

        System.out.println(
            "Order placed successfully");
    }
}

Dependency Graph

Spring IoC Container | v OrderService | v PaymentService | v PaymentGateway | v StripePaymentGateway

None of these classes needs to manually create its dependency using new.

27. Dependency Injection and Unit Testing

One of the biggest advantages of DI is testability.

@Service
public class PaymentService {

    private final PaymentGateway paymentGateway;

    public PaymentService(
            PaymentGateway paymentGateway) {

        this.paymentGateway = paymentGateway;
    }

    public void processPayment(double amount) {

        paymentGateway.pay(amount);
    }
}

Production:

PaymentService | v StripePaymentGateway

Testing:

PaymentService | v MockPaymentGateway

Example:

PaymentGateway mockGateway =
    Mockito.mock(PaymentGateway.class);

PaymentService service =
    new PaymentService(mockGateway);

The test can execute PaymentService without contacting a real payment provider.

28. IoC vs DI

IoCDI
Inversion of Control is a broader design principle. Dependency Injection is a technique/mechanism.
Control of object creation and dependency management is transferred to a container/framework. Dependencies are supplied to an object instead of the object creating or looking them up.
Describes the overall change in control. Describes how dependencies are provided.
Remember: DI is one of the primary techniques used to implement IoC.

29. Pull vs Push Model

Pull — Dependency Lookup

class OrderService {

    public void process() {

        NotificationService service =
            container.getBean(
                NotificationService.class);

        service.send("Order placed");
    }
}
Object ---> pulls ---> Dependency

Push — Dependency Injection

class OrderService {

    private final NotificationService service;

    public OrderService(
            NotificationService service) {

        this.service = service;
    }
}
Container ---> pushes ---> Dependency ---> Object

30. Typical Spring Boot Dependency Graph

Controller | v OrderService | +----------------+ | | v v PaymentService OrderRepository | v PaymentGateway

Spring manages these objects and resolves the dependencies between them.

For example:

@RestController
public class OrderController {

    private final OrderService orderService;

    public OrderController(
            OrderService orderService) {

        this.orderService = orderService;
    }
}
@Service
public class OrderService {

    private final PaymentService paymentService;
    private final OrderRepository orderRepository;

    public OrderService(
            PaymentService paymentService,
            OrderRepository orderRepository) {

        this.paymentService = paymentService;
        this.orderRepository = orderRepository;
    }
}

31. Important Interview Answers

What is IoC?

IoC, or Inversion of Control, is a design principle in which the responsibility for creating, configuring, managing, and wiring objects is transferred from application code to a container or framework. In Spring, the IoC container manages Spring beans and their dependencies.

What is Dependency Injection?

Dependency Injection is a technique used to implement IoC. Instead of a class creating or looking up its dependencies, the dependencies are provided to the class by an external container. Spring supports constructor, setter, and field injection, with constructor injection generally preferred for required dependencies.

Dependency Lookup vs Dependency Injection?

In Dependency Lookup, the object actively asks a container or registry for its dependency, so it follows a pull model. In Dependency Injection, the dependency is supplied to the object from outside, so it follows a push model. Spring primarily promotes Dependency Injection because it reduces coupling between application classes and the container.

Why is Constructor Injection preferred?

  • It makes required dependencies explicit.
  • It supports immutable fields.
  • It makes objects easier to construct correctly.
  • It simplifies unit testing.
  • It avoids hidden dependencies.

32. Final Summary

DEPENDENCY | v How do I obtain my dependency? | +----------+----------+ | | | v v v Composition Factory JNDI | | | +----------+----------+ | v IoC | +---------+---------+ | | v v Dependency Lookup Dependency Injection (Pull) (Push) | +---------------+---------------+ | | | v v v Constructor Setter Field Injection Injection Injection

The Most Important Comparison

Without IoC/DI

class OrderService {

    PaymentService paymentService =
        new PaymentService();
}

The class creates its dependency.

With IoC/DI

class OrderService {

    private final PaymentService paymentService;

    OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

The dependency is provided to the class.

One-Sentence Definition

IoC means Spring takes control of object creation and dependency management; Dependency Injection is the mechanism through which Spring supplies those dependencies to your objects.

Easy Memory Trick

ConceptEasy Meaning
DependencyOne class needs another class
CompositionHAS-A relationship
InheritanceIS-A relationship
FactoryObject creation is centralized
JNDILook up a resource from a registry
Dependency LookupObject pulls the dependency
Dependency InjectionDependency is pushed into the object
IoCFramework/container controls object creation and management
Constructor InjectionRequired dependency provided through constructor
Setter InjectionDependency provided through setter
Field InjectionDependency injected directly into a field
Spring BeanObject managed by Spring IoC container