Spring kennt drei Wege, Abhängigkeiten zu injizieren: über den Konstruktor, über Felder oder über Setter-Methoden. Alle drei funktionieren – aber nicht alle drei sind gleichwertig.
Die Abhängigkeiten werden über den Konstruktor übergeben:
@Service
public class HighscoreService {
private final ScoreRepository scoreRepository;
private final PlayerRepository playerRepository;
public HighscoreService(ScoreRepository scoreRepository,
PlayerRepository playerRepository) {
this.scoreRepository = scoreRepository;
this.playerRepository = playerRepository;
}
}Spring erkennt: Diese Klasse hat einen Konstruktor mit Parametern. Die Parameter sind Typen, für die Beans existieren. Also ruft Spring den Konstruktor mit den passenden Beans auf.
Bei genau einem Konstruktor ist keine zusätzliche Annotation nötig.
Bei mehreren Konstruktoren markierst du den gewünschten mit
@Autowired:
@Service
public class HighscoreService {
private final ScoreRepository scoreRepository;
private final PlayerRepository playerRepository;
private final CacheService cacheService;
// Dieser Konstruktor wird für DI verwendet
@Autowired
public HighscoreService(ScoreRepository scoreRepository,
PlayerRepository playerRepository,
CacheService cacheService) {
this.scoreRepository = scoreRepository;
this.playerRepository = playerRepository;
this.cacheService = cacheService;
}
// Alternativer Konstruktor für Tests ohne Cache
public HighscoreService(ScoreRepository scoreRepository,
PlayerRepository playerRepository) {
this(scoreRepository, playerRepository, new NoOpCacheService());
}
}Die Abhängigkeiten werden direkt in Felder geschrieben:
@Service
public class HighscoreService {
@Autowired
private ScoreRepository scoreRepository;
@Autowired
private PlayerRepository playerRepository;
}Kein Konstruktor nötig. Spring erzeugt das Objekt mit dem Default-Konstruktor und setzt die Felder danach per Reflection.
Das sieht kompakter aus. Weniger Code, weniger Tipparbeit. Trotzdem ist Field Injection problematisch – dazu gleich mehr.
Die Abhängigkeiten werden über Setter-Methoden gesetzt:
@Service
public class HighscoreService {
private ScoreRepository scoreRepository;
private PlayerRepository playerRepository;
@Autowired
public void setScoreRepository(ScoreRepository scoreRepository) {
this.scoreRepository = scoreRepository;
}
@Autowired
public void setPlayerRepository(PlayerRepository playerRepository) {
this.playerRepository = playerRepository;
}
}Spring ruft nach der Objekterzeugung alle mit @Autowired
annotierten Setter auf.
Die Tabelle fasst die Unterschiede zusammen:
| Kriterium | Constructor | Field | Setter |
|---|---|---|---|
Felder final möglich |
✓ | ✗ | ✗ |
| Abhängigkeiten sichtbar | ✓ (Parameter) | ✗ (versteckt) | ○ (Methoden) |
| Ohne Spring testbar | ✓ | ✗ | ○ |
| Optionale Dependencies | ✗ | ○ | ✓ |
| Zirkuläre Abhängigkeiten | Fehler beim Start | Funktioniert | Funktioniert |
Immutability: Mit final-Feldern kann
das Objekt nach der Erzeugung nicht mehr verändert werden. Das
verhindert Bugs und macht den Code thread-safe.
// Constructor Injection: Felder sind final und unveränderlich
private final ScoreRepository scoreRepository;
// Field Injection: Felder können jederzeit überschrieben werden
@Autowired
private ScoreRepository scoreRepository;Explizite Abhängigkeiten: Der Konstruktor zeigt sofort, was die Klasse braucht. Bei Field Injection musst du die Klasse durchsuchen.
Testbarkeit: Ohne Spring kannst du das Objekt
einfach mit new erzeugen:
// Test ohne Spring-Context
@Test
void testHighscoreCalculation() {
ScoreRepository mockRepo = mock(ScoreRepository.class);
PlayerRepository mockPlayers = mock(PlayerRepository.class);
// Direkte Instanziierung - funktioniert nur mit Constructor Injection
HighscoreService service = new HighscoreService(mockRepo, mockPlayers);
// Test...
}Mit Field Injection bräuchtest du Spring oder Reflection-Tricks.
Fail-Fast: Wenn eine Abhängigkeit fehlt, scheitert
Constructor Injection sofort beim Start. Field Injection kann zu
NullPointerExceptions zur Laufzeit führen.
Setter Injection hat einen legitimen Anwendungsfall: optionale Abhängigkeiten.
@Service
public class HighscoreService {
private final ScoreRepository scoreRepository;
private CacheService cacheService; // Optional
public HighscoreService(ScoreRepository scoreRepository) {
this.scoreRepository = scoreRepository;
}
@Autowired(required = false)
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
public List<Score> getTopScores() {
if (cacheService != null) {
// Cache nutzen, wenn verfügbar
}
return scoreRepository.findTop10();
}
}Der CacheService ist nice-to-have, aber nicht
essentiell. Wenn keine Bean existiert, funktioniert der Service
trotzdem.
Für das Arcade-Projekt – und für die allermeisten Spring-Anwendungen – gilt:
Constructor Injection ist der Standard. Setter Injection ist die Ausnahme. Field Injection ist ein Code Smell.
Viele ältere Tutorials zeigen Field Injection, weil es weniger Tipparbeit ist. Lass dich davon nicht verleiten. Die Nachteile überwiegen bei Weitem.