12 Arten der Injection

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.

12.1 Constructor Injection

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());
    }
}

12.2 Field Injection

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.

12.3 Setter Injection

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.

12.4 Der Vergleich

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

12.5 Warum Constructor Injection gewinnt

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.

12.6 Wann Setter Injection?

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.

12.7 Die Empfehlung

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.