Inversion of Control ist das Prinzip. Dependency Injection ist die Technik, mit der Spring dieses Prinzip umsetzt. Der Name beschreibt genau, was passiert: Abhängigkeiten (Dependencies) werden eingespritzt (injected).
Eine Dependency ist jedes Objekt, das eine Klasse braucht, um ihre
Arbeit zu erledigen. Im Arcade-Projekt hat ein hypothetischer
LeaderboardService mehrere Dependencies:
Ohne ScoreRepository kann der Service keine Scores
laden. Ohne PlayerRepository keine Spielernamen. Ohne
CacheService keine Performance-Optimierung. Diese Objekte
sind Abhängigkeiten – der Service hängt von ihnen ab.
Dependency Injection bedeutet: Die Abhängigkeiten werden von außen bereitgestellt, nicht intern erzeugt.
// OHNE Dependency Injection
public class LeaderboardService {
private ScoreRepository scores = new MongoScoreRepository();
private PlayerRepository players = new MongoPlayerRepository();
}
// MIT Dependency Injection
public class LeaderboardService {
private final ScoreRepository scores;
private final PlayerRepository players;
public LeaderboardService(ScoreRepository scores, PlayerRepository players) {
this.scores = scores;
this.players = players;
}
}Im ersten Fall ist die Klasse für die Erzeugung verantwortlich. Im zweiten Fall erwartet sie, dass jemand anders die Objekte liefert. Dieser “jemand anders” ist Spring.
Die wahre Macht von DI entfaltet sich durch Interfaces. Statt konkreter Klassen verwendet der Service abstrakte Typen:
Der LeaderboardService weiß nicht, ob er mit MongoDB
oder PostgreSQL arbeitet. Er kennt nur das Interface. Die konkrete
Implementierung wird zur Laufzeit injiziert – und kann jederzeit
ausgetauscht werden.
Im Arcade-Projekt könnte ein HighscoreService so
aussehen:
@Service
public class HighscoreService {
private final ScoreRepository scoreRepository;
private final GameRepository gameRepository;
public HighscoreService(ScoreRepository scoreRepository,
GameRepository gameRepository) {
this.scoreRepository = scoreRepository;
this.gameRepository = gameRepository;
}
public List<Score> getTopScores(String gameId, int limit) {
Game game = gameRepository.findById(gameId)
.orElseThrow(() -> new GameNotFoundException(gameId));
return scoreRepository.findByGameOrderByPointsDesc(game, limit);
}
}Die @Service-Annotation sagt Spring: “Das ist eine
Komponente, verwalte sie.” Die Constructor-Parameter sagen Spring:
“Diese Objekte brauche ich.”
Spring sieht das und handelt:
ScoreRepositoryGameRepositoryHighscoreService mit diesen BeansDu schreibst keinen Verdrahtungs-Code. Spring erledigt das.
Kopplung beschreibt, wie stark zwei Komponenten voneinander abhängen. Enge Kopplung bedeutet: Eine Änderung hier erfordert Änderungen dort. Lose Kopplung bedeutet: Komponenten können unabhängig voneinander geändert werden.
| Aspekt | Enge Kopplung | Lose Kopplung (DI) |
|---|---|---|
| Abhängigkeit | Konkrete Klasse | Interface |
| Erzeugung | new im Code |
Container injiziert |
| Änderbarkeit | Quellcode anpassen | Konfiguration anpassen |
| Testbarkeit | Echte Objekte nötig | Mocks möglich |
Dependency Injection erzwingt lose Kopplung. Du kannst gar nicht anders, als gegen Interfaces zu programmieren – denn du kontrollierst nicht, welche Implementierung du bekommst.
Ohne DI führt eine Änderung oft zu Kettenreaktionen:
Mit DI bleibt die Änderung isoliert:
Der Service arbeitet weiter mit dem gleichen Interface. Dass dahinter jetzt PostgreSQL statt MongoDB steckt, merkt er nicht.
IoC und DI gehören zusammen, sind aber nicht dasselbe:
IoC ist die Philosophie: Kontrolle abgeben. DI ist die Methode: Abhängigkeiten von außen liefern. Spring ist das Werkzeug: Der Container, der alles zusammenbringt.