07 - Dependency Injection (DI) in Spring
- 01 - What is Spring? What Problems Does It Solve?
- 02 - Spring vs Spring Boot: Understanding the Difference
- 03 - Spring MVC vs Spring Boot: When and Why to Use Each
- 04 - Installing Java, Maven, & IDE Setup (STS, Eclipse, IntelliJ)
- 05 - Running Your First Spring Boot Application
- 06 - Inversion of Control (IoC) in Spring
- 07 - Dependency Injection (DI) in SpringCurrent
- 08 - BeanFactory vs ApplicationContext
- 09 - Spring Bean Lifecycle
- 10 - Bean Scopes: Singleton, Prototype & Custom Scopes
Dependency Injection (DI) is one of the most important concepts in the Spring ecosystem.
If Inversion of Control (IoC) defines who controls object creation, then DI defines how those objects are provided to each other.
When you fully understand DI, you'll appreciate why Spring applications are easy to maintain, test, and extend.
1. What Is Dependency Injection?
Dependency Injection is a design pattern where an object's dependencies are provided (or injected) by an external system — in our case, the Spring IoC Container — rather than the object creating them itself.
Without DI (Traditional Java):
Car car = new Car();
Engine engine = new Engine();
car.setEngine(engine);
The Car class creates/controls its dependencies.
With DI (Spring Way):
@Autowired
private Engine engine;
Spring creates and injects the dependency for you.
2. Why Does DI Matter?
DI solves real-world software problems:
✔ Reduces tight coupling
Objects no longer depend on concrete implementations.
✔ Easier unit testing
Replace real dependencies with mocks easily.
✔ Cleaner and modular architecture
Responsibilities are separated.
✔ Reusable components
Components can be swapped or replaced without modifying core logic.
✔ Better maintainability
Dependency configuration is centralized.
3. Types of Dependency Injection in Spring
Spring supports three types of DI:
3.1 Constructor Injection (Recommended)
Dependencies are injected via the constructor.
@Component
public class Car {
private final Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
Why it's recommended:
- Ensures immutability
- Best for mandatory dependencies
- Works perfectly with unit tests
- Preferred by Spring Boot team
3.2 Setter Injection
Dependencies are injected via setter methods.
@Component
public class Car {
private Engine engine;
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
}
When to use:
- Optional dependencies
- Configurable properties
3.3 Field Injection (Not recommended)
Injecting dependencies directly into fields:
@Component
public class Car {
@Autowired
private Engine engine;
}
Why avoid it?
- Harder to test
- Makes class less flexible
- Hides the dependency
Spring Boot projects should avoid field injection unless absolutely necessary.
4. How Dependency Injection Works Internally
When the Spring Boot application starts:
- Spring scans your project for components
- Creates Bean instances
- Determines which dependencies each Bean needs
- Injects dependencies based on
@Autowiredor constructor parameters - Stores everything inside the ApplicationContext
Spring manages the entire lifecycle automatically.
5. @Autowired Explained
@Autowired tells Spring to find and inject the appropriate dependency.
Spring matches dependencies using:
- Type (primary)
- Qualifiers
- Bean names
If multiple beans match, Spring requires clarification using:
@Qualifier("dieselEngine")
private Engine engine;
6. Using @Qualifier
Sometimes you have multiple implementations:
@Component("dieselEngine")
public class DieselEngine implements Engine {}
@Component("petrolEngine")
public class PetrolEngine implements Engine {}
Use:
@Autowired
@Qualifier("dieselEngine")
private Engine engine;
This gives you full control over what gets injected.
7. Dependency Injection in Spring Boot
Spring Boot enhances DI by:
- Auto-detecting Beans
- Auto-wiring common components
- Providing built-in Beans for Web, Security, JPA, etc.
- Reducing boilerplate
The biggest advantage:
Everything is auto-configured unless you override it.
8. Real-World Example
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
paymentService.processPayment();
}
}
DI ensures:
- OrderService doesn't create PaymentService
- Both classes stay independent
- Testing is easy by injecting a mock PaymentService
9. Common Mistakes to Avoid
❌ Using field injection everywhere
Use constructor injection instead.
❌ Forgetting to annotate components
Classes must be marked with:
@Component@Service@Repository@Controller
❌ Multiple matching beans without qualifiers
Leads to NoUniqueBeanDefinitionException.
❌ Overusing @Autowired
Spring Boot supports constructor injection without needing @Autowired.
10. Summary
- DI is the technique used to implement IoC.
- Spring injects dependencies automatically.
- Constructor injection is recommended.
- DI improves modularity, testability, and maintainability.
- Spring Boot simplifies DI with auto-configuration and component scanning.
Understanding DI is essential before diving into Bean lifecycle and scopes.
What's Next?
In the next post:
08 - BeanFactory vs ApplicationContext
We explore the two main IoC containers and how they manage Beans internally.
