Dependency Injection
What Is Dependency Injection?
Dependency Injection (DI) is a pattern in which an object does not create its own dependencies; instead, they are provided (injected) by an external party — in this case, the Spring IoC container.
// Tight Coupling — the class creates its own dependencies
public class OrderService {
private final PaymentService payment = new PaymentService(); // ← problem!
private final NotificationService notif = new NotificationService();
}
// Loose Coupling — dependencies are injected from outside
public class OrderService {
private final PaymentService payment;
private final NotificationService notif;
public OrderService(PaymentService payment, NotificationService notif) {
this.payment = payment;
this.notif = notif;
}
}
IoC (Inversion of Control)
IoC is the underlying principle: the program's control flow is inverted from the class to the framework. Instead of the class managing its dependencies' lifecycle, Spring manages it.
Without IoC: Class → creates → dependency
With IoC: Container → injects → into Class
@Autowired
The @Autowired annotation tells Spring to automatically inject a bean.
Constructor Injection (Recommended)
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired // Can be omitted if there is only one constructor
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Since Spring 4.3,
@Autowiredis not required if the class has only one constructor.
Setter Injection
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Field Injection (Not Recommended)
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // ← avoid!
}
| Method | Immutability | Testable | Recommended |
|---|---|---|---|
| Constructor | ✅ Final field | ✅ Easy to mock | ✅ Yes |
| Setter | ❌ Can be null | ⚠️ Needs setup | ⚠️ Sometimes |
| Field | ❌ Can be null | ❌ Hard to mock | ❌ No |
@Qualifier
When there is more than one bean of the same type, use @Qualifier to pick one:
@Component("emailNotifier")
public class EmailNotifier implements Notifier {
// ...
}
@Component("smsNotifier")
public class SmsNotifier implements Notifier {
// ...
}
@Service
public class NotificationService {
private final Notifier notifier;
public NotificationService(@Qualifier("emailNotifier") Notifier notifier) {
this.notifier = notifier;
}
}
@Primary
Mark a bean as the default when multiple beans of the same type exist:
@Component
@Primary
public class EmailNotifier implements Notifier {
// ...
}
@Component
public class SmsNotifier implements Notifier {
// ...
}
// NotificationService will get EmailNotifier by default
Loose Coupling vs Tight Coupling
| Aspect | Tight Coupling | Loose Coupling |
|---|---|---|
| Dependency | Created by itself | Injected |
| Testing | Hard | Easy to mock |
| Swapping implementation | Change the class code | Change the configuration |
| Example | new PaymentService() |
Constructor injection |
Best Practices
- Use constructor injection — immutable, testable, dependencies are clear
- Avoid field injection — hard to test, not immutable
- Use interfaces for abstraction — swap the implementation without changing the caller
- Don't use
newto create dependencies managed by Spring
With dependency injection, your application becomes loosely coupled and easy to test. In the next chapter, we'll understand Bean Lifecycle & Scopes — how Spring creates, manages, and destroys beans.