Spring Core Dependency Injection: Step-by-Step Guide with Complete Example

Dependency Injection (DI) is a key concept in Spring Core that promotes loose coupling between components. This guide covers IoC container, types of DI, bean configuration, and bean lifecycle, with a step-by-step complete example to help you implement these concepts in a real-world application.

1. Understanding Spring’s IoC Container

The Inversion of Control (IoC) container is the foundation of the Spring framework. It manages the lifecycle and configuration of objects (beans) and their dependencies. The IoC container:

  • Instantiates beans.
  • Resolves and injects dependencies.
  • Configures bean lifecycle callbacks.

Types of IoC Containers in Spring:

  1. BeanFactory: The basic IoC container for simple applications.
  2. ApplicationContext: An advanced container with support for AOP, internationalization, and event handling.

2. Types of Dependency Injection

Spring supports two main types of dependency injection:

  • Constructor Injection
  • Setter Injection

a. Constructor Injection

Dependencies are provided via the constructor at the time of object creation.

@Component
public class Car {
    private Engine engine;

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

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

b. Setter Injection

Dependencies are provided via setter methods after the bean is instantiated.

@Component
public class Car {
    private Engine engine;

    @Autowired
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

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

3. Beans Configuration

Beans are the backbone of Spring applications. You can configure them in two ways:

  • XML-based Configuration
  • Annotation-based Configuration

a. XML-based Configuration

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- Bean for Engine -->
    <bean id="engine" class="com.example.Engine"/>

    <!-- Bean for Car with Constructor Injection -->
    <bean id="car" class="com.example.Car">
        <constructor-arg ref="engine"/>
    </bean>
</beans>

b. Annotation-based Configuration

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    // No explicit bean definitions are required; Spring automatically detects @Component classes.
}

4. Lifecycle of a Spring Bean

Spring beans go through several lifecycle stages:

  1. Instantiation: The IoC container creates the bean.
  2. Populate Properties: Dependencies are injected.
  3. Initialization: Custom initialization logic is executed (e.g., methods annotated with @PostConstruct).
  4. Destruction: Resources are cleaned up (e.g., methods annotated with @PreDestroy).

Example:

@Component
public class MyBean {

    @PostConstruct
    public void init() {
        System.out.println("Bean initialized.");
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("Bean destroyed.");
    }
}

Complete Step-by-Step Example

This section demonstrates how to set up a Spring application with DI using Eclipse or IntelliJ IDEA.

Step 1: Create a Maven Project

  1. Open Eclipse or IntelliJ IDEA.
  2. Create a new Maven project.
  3. Add the following dependencies in your pom.xml:
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.30</version>
    </dependency>
</dependencies>

Step 2: Create Classes

Engine.java

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

Car.java

@Component
public class Car {
    private Engine engine;

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

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

Step 3: Configure Spring Application

AppConfig.java

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}

Step 4: Write Main Method

MainApp.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Car car = context.getBean(Car.class);
        car.drive();
    }
}

Step 5: Run the Application

  1. Right-click on the MainApp class and select Run As -> Java Application.
  2. Output:
Engine started.
Car is driving...

Best Practices

  • Prefer Constructor Injection for mandatory dependencies.
  • Use @ComponentScan to simplify bean discovery.
  • Manage resources effectively with @PostConstruct and @PreDestroy.
  • Use Singleton Scope for stateless beans and Prototype Scope for stateful beans.