Chapter 2: Dependency Injection (DI)
Introduction
In Chapter 1, you understood WHY IoC exists. Now, you will learn HOW Spring injects dependencies.
What You Will Learn
- The 3 types of dependency injection
- Why constructor injection is preferred
- How to use
@Autowired,@Inject,@Resource - Managing optional dependencies
Duration: 45 minutes
The 3 Types of Injection
Spring offers 3 ways to inject dependencies:
- Constructor Injection ✅ (Recommended)
- Setter Injection
- Field Injection ⚠️ (Discouraged)
Let's look at each in detail.
1. Constructor Injection ✅
Syntax
@Service
public class OrderService {
// Dependencies declared as 'final'
private final EmailService emailService;
private final PaymentService paymentService;
// Injection via constructor
public OrderService(EmailService emailService,
PaymentService paymentService) {
this.emailService = emailService;
this.paymentService = paymentService;
}
}
How Does It Work?
At application startup:
1. Spring finds OrderService
↓
2. Spring sees the constructor with 2 parameters
↓
3. Spring creates EmailService
↓
4. Spring creates PaymentService
↓
5. Spring creates OrderService by passing the 2 dependencies
↓
6. OrderService is ready!
Advantages of Constructor Injection
Advantage 1: Immutability
private final EmailService emailService;
// ^^^^^
// Cannot be modified after creation!
Why is this good?
- ✅ Thread-safe (no concurrency issues)
- ✅ Predictable (cannot change)
- ✅ Fewer bugs
Advantage 2: Mandatory Dependencies
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
If Spring cannot find EmailService:
❌ Application refuses to start!
❌ Clear error at startup
That's GOOD! You discover the problem immediately, not in production!
Advantage 3: Easy to Test
@Test
public void testOrderService() {
// Create fakes
EmailService fakeEmail = new FakeEmailService();
PaymentService fakePayment = new FakePaymentService();
// Easily inject via constructor
OrderService service = new OrderService(fakeEmail, fakePayment);
// Test
service.processOrder(new Order());
}
No need for Spring to test!
Advantage 4: No Need for @Autowired
Since Spring 4.3, @Autowired is optional on constructors:
@Service
public class OrderService {
private final EmailService emailService;
// No need for @Autowired!
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Spring automatically detects the constructor!
Summary: Constructor Injection
✅ Immutable dependencies (final)
✅ Mandatory dependencies
✅ Easy to test
✅ No need for @Autowired
✅ Recommended by Spring
👉 ALWAYS USE CONSTRUCTOR INJECTION!
2. Setter Injection
Syntax
@Service
public class OrderService {
// NON-final dependency
private EmailService emailService;
// Injection via a setter
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
How Does It Work?
1. Spring creates OrderService (empty constructor)
↓
2. Spring creates EmailService
↓
3. Spring calls setEmailService(emailService)
↓
4. OrderService is ready
When to Use?
Use case: Optional dependencies
@Service
public class OrderService {
private EmailService emailService;
private SmsService smsService; // Optional
@Autowired(required = false) // ← Optional!
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notifyCustomer(String message) {
// Email always sent
emailService.send(message);
// SMS only if available
if (smsService != null) {
smsService.send(message);
}
}
}
Disadvantages
❌ Mutable dependencies (not 'final')
❌ Can be null (risk of NullPointerException)
❌ Object created in an incomplete state
❌ More difficult to test
Use only for optional dependencies!
3. Field Injection ⚠️
Syntax
@Service
public class OrderService {
// Direct injection into the field
@Autowired
private EmailService emailService;
@Autowired
private PaymentService paymentService;
}
How Does It Work?
1. Spring creates OrderService
↓
2. Spring uses REFLECTION to access private fields
↓
3. Spring injects dependencies directly
↓
4. OrderService is ready
Why Is It Discouraged?
Problem 1: Cannot Make 'final'
@Autowired
private EmailService emailService; // ❌ Cannot be 'final'
Problem 2: Difficult to Test
@Test
public void testOrderService() {
OrderService service = new OrderService();
// ❌ How to inject dependencies?
// Fields are private!
// Need to use reflection... complicated!
}
Problem 3: Hidden Dependencies
@Service
public class OrderService {
@Autowired
private Dep1 dep1;
@Autowired
private Dep2 dep2;
@Autowired
private Dep3 dep3;
@Autowired
private Dep4 dep4;
@Autowired
private Dep5 dep5;
// ... 10 dependencies!
}
Problem: It's not easy to see all dependencies!
With constructor:
public OrderService(Dep1 d1, Dep2 d2, Dep3 d3, Dep4 d4, Dep5 d5) {
// ❌ WOW! Too many dependencies!
// ➡️ Signal that the class does too much
}
Summary: Field Injection
❌ Not immutable
❌ Difficult to test
❌ Hides design issues
❌ Uses reflection (slower)
👉 AVOID FIELD INJECTION!
Comparison of the 3 Types
| Criteria | Constructor | Setter | Field |
|---|---|---|---|
| Immutability (final) | ✅ Yes | ❌ No | ❌ No |
| Mandatory dependencies | ✅ Yes | ❌ No | ⚠️ Not clear |
| Easy to test | ✅ Yes | ⚠️ Medium | ❌ No |
| Readability | ✅ Clear | ✅ Clear | ⚠️ Hidden |
| @Autowired needed | ❌ No | ✅ Yes | ✅ Yes |
| Recommendation | ✅ USE | ⚠️ Optional | ❌ AVOID |
Injection Annotations
Spring supports 3 annotations for injection:
1. @Autowired (Spring)
@Service
public class OrderService {
private final EmailService emailService;
@Autowired // Spring annotation
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Origin: Spring Framework
Usage: Most common
2. @Inject (Java Standard)
import jakarta.inject.Inject;
@Service
public class OrderService {
private final EmailService emailService;
@Inject // Jakarta annotation (Java standard)
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Origin: Jakarta EE (formerly Java EE)
Usage: If you want portable code (not tied to Spring)
3. @Resource (Java Standard)
import jakarta.annotation.Resource;
@Service
public class OrderService {
@Resource(name = "emailService") // Injection by name
private EmailService emailService;
}
Origin: Jakarta EE
Difference: Injects by name instead of type
Which One to Choose?
Recommendation: @Autowired (or none for constructor)
Why?
✅ Spring standard
✅ Optional on constructor
✅ More options (required, @Qualifier)
✅ Better documented
Handling Multiple Implementations
Problem
public interface EmailService {
void send(String to, String message);
}
@Service
public class GmailService implements EmailService { ... }
@Service
public class SendGridService implements EmailService { ... }
// ❌ Spring doesn't know which one to choose!
Solution 1: @Primary
@Service
@Primary // ← This is the default implementation
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Sending via Gmail to " + to);
}
}
@Service
public class SendGridService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Sending via SendGrid to " + to);
}
}
Spring will use GmailService by default!
Solution 2: @Qualifier
@Service
public class OrderService {
private final EmailService emailService;
public OrderService(@Qualifier("sendGridService") EmailService emailService) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Specific bean name
this.emailService = emailService;
}
}
Spring will specifically use SendGridService!
Solution 3: Explicit Naming
@Service("gmailEmailService") // ← Explicit name
public class GmailService implements EmailService { ... }
@Service("sendGridEmailService") // ← Explicit name
public class SendGridService implements EmailService { ... }
// Usage
public OrderService(@Qualifier("sendGridEmailService") EmailService service) {
this.emailService = service;
}
Optional Dependencies
With @Autowired(required = false)
@Service
public class NotificationService {
private final EmailService emailService;
private SmsService smsService; // Optional
// Mandatory
public NotificationService(EmailService emailService) {
this.emailService = emailService;
}
// Optional
@Autowired(required = false)
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notify(String message) {
emailService.send(message); // Always
if (smsService != null) {
smsService.send(message); // If available
}
}
}
With Optional<T>
import java.util.Optional;
@Service
public class NotificationService {
private final EmailService emailService;
private final Optional<SmsService> smsService;
public NotificationService(
EmailService emailService,
Optional<SmsService> smsService // ← Optional wrapper
) {
this.emailService = emailService;
this.smsService = smsService;
}
public void notify(String message) {
emailService.send(message);
smsService.ifPresent(sms -> sms.send(message));
// ^^^^^^^^^
// Called only if present
}
}
Complete Example
// Interface
public interface PaymentProcessor {
void process(Order order);
}
// Implementation 1
@Service
@Primary // ← Default
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void process(Order order) {
System.out.println("Payment via Stripe: " + order.getTotal());
}
}
// Implementation 2
@Service("paypalProcessor")
public class PayPalPaymentProcessor implements PaymentProcessor {
@Override
public void process(Order order) {
System.out.println("Payment via PayPal: " + order.getTotal());
}
}
// Usage - Option 1: Use @Primary
@Service
public class OrderService {
private final PaymentProcessor paymentProcessor;
// Spring injects StripePaymentProcessor (@Primary)
public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}
// Usage - Option 2: Specify with @Qualifier
@Service
public class SpecialOrderService {
private final PaymentProcessor paymentProcessor;
// Force the use of PayPal
public SpecialOrderService(
@Qualifier("paypalProcessor") PaymentProcessor paymentProcessor
) {
this.paymentProcessor = paymentProcessor;
}
}
Chapter Summary
The 3 Types of Injection
// 1. Constructor ✅ RECOMMENDED
public MyService(Dependency dep) {
this.dep = dep;
}
// 2. Setter ⚠️ For optional dependencies
@Autowired(required = false)
public void setDependency(Dependency dep) {
this.dep = dep;
}
// 3. Field ❌ TO AVOID
@Autowired
private Dependency dep;
Implementation Choice
// Option 1: @Primary
@Primary
@Service
public class DefaultImpl implements MyInterface { }
// Option 2: @Qualifier
public MyService(@Qualifier("specificImpl") MyInterface service) { }
Key Points
✅ Always use constructor injection
✅ Declare dependencies as final
✅ @Autowired optional on constructor
✅ @Primary for default implementation
✅ @Qualifier for specific choice
⚠️ Setter only for optional dependencies
❌ Avoid field injection
Self-Evaluation
- What are the 3 types of injection?
- Why is constructor injection preferred?
- What is the difference between @Primary and @Qualifier?
- How to make a dependency optional?
Next Step
You now know HOW to inject dependencies. Let's move on to creating and managing Beans!