Dependency Injection im Detail
Konstruktor-, Setter- und Feld-Injektion; Mehrdeutigkeit mit @Qualifier und @Primary auflösen; und warum die Konstruktor-Injektion gewinnt.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
The container wires objects together through dependency injection (DI) - it injects each bean's collaborators instead of the bean fetching them. Spring supports three styles, but they are not equal. Choosing well is one of the clearest marks of a professional Spring developer.
The three styles
// 1. CONSTRUCTOR injection - the recommended default
@Service
class BookService {
private final BookRepository repository;
BookService(BookRepository repository) { // Spring passes the bean in
this.repository = repository;
}
}
// 2. SETTER injection - for genuinely optional dependencies
@Service
class BookService {
private NotificationClient notifier;
@Autowired(required = false)
void setNotifier(NotificationClient notifier) { this.notifier = notifier; }
}
// 3. FIELD injection - concise, but avoid it (see below)
@Service
class BookService {
@Autowired private BookRepository repository;
}You can drop @Autowired on constructors
If a bean has a single constructor, Spring uses it automatically - no
@Autowired needed. That's why constructor injection reads so cleanly.
Why constructor injection wins
Prefer constructor injection for required dependencies. It gives you three things the others can't:
finalfields & immutability - the dependency is set once and never null.- Fail-fast wiring - a missing dependency is caught the instant the container
starts, not at first use with a
NullPointerException. - Trivial testing - you construct the object with plain
newin a unit test and pass a mock, no Spring required.
A constructor that lists every dependency is a recipe that names all its ingredients up front: read it once and you know exactly what the dish requires - and you can't start cooking while an ingredient is missing. Field injection is a recipe that says "grab whatever you need from the pantry as you go" - you only discover a missing ingredient halfway through, at runtime.
Resolving ambiguity
What if two beans could satisfy one dependency? Say two PaymentGateway
implementations. Spring can't guess, so it fails - unless you disambiguate:
@Component
@Primary // the default choice when ambiguous
class StripeGateway implements PaymentGateway { }
@Component("legacyGateway")
class PaypalGateway implements PaymentGateway { }
@Service
class Checkout {
Checkout(@Qualifier("legacyGateway") PaymentGateway gateway) { // pick explicitly
// ...
}
}@Primarymarks one bean as the default winner.@Qualifier("name")picks a specific bean by name.
You can even inject all implementations at once - Spring collects every
matching bean into a List or Map:
@Service
class NotificationDispatcher {
private final List<NotificationChannel> channels; // every channel bean, injected
NotificationDispatcher(List<NotificationChannel> channels) {
this.channels = channels;
}
}This is a beautifully clean way to implement the Strategy pattern - add a new
NotificationChannel bean and it joins the list automatically, no registration.
Here is a class using field injection:
@Service
class BookService {
@Autowired private BookRepository repository;
List<Book> featured() { return repository.findFeatured(); }
}Rewrite it with constructor injection, then write the one line that constructs it in a unit test with a mock repository (no Spring context).
Why is constructor injection preferred over field injection for required dependencies?
Key takeaways
- Dependency injection means the container supplies a bean's collaborators; Spring offers constructor, setter, and field injection.
- Prefer constructor injection for required dependencies: it enables final fields, fails fast on missing beans, and makes unit testing trivial.
- A single constructor needs no @Autowired annotation.
- Disambiguate multiple candidates with @Primary (default winner) or @Qualifier (pick by name).
- Injecting a List or Map of an interface gives you every implementing bean - a clean, registration-free Strategy pattern.