Spring kennt mehrere Annotationen, um Klassen als Komponenten zu markieren. Sie heißen Stereotypen, weil sie die Rolle einer Klasse beschreiben.
Alle Stereotypen haben denselben Effekt: Die Klasse wird als Bean registriert. Aber sie kommunizieren unterschiedliche Absichten.
@Service, @Repository und
@Controller sind Spezialisierungen von
@Component. Technisch sind sie austauschbar – semantisch
nicht.
Die generische Annotation. Verwende sie, wenn keine der spezifischeren passt.
@Component
public class ScoreCalculator {
public int calculateBonus(int baseScore, int multiplier) {
return baseScore * multiplier;
}
}Ein ScoreCalculator ist weder Service noch Repository
noch Controller. Er ist eine Hilfskomponente. @Component
passt.
Markiert Klassen mit Geschäftslogik. Services orchestrieren Abläufe und implementieren fachliche Regeln.
@Service
public class HighscoreService {
private final ScoreRepository scoreRepository;
private final PlayerRepository playerRepository;
public HighscoreService(ScoreRepository scoreRepository,
PlayerRepository playerRepository) {
this.scoreRepository = scoreRepository;
this.playerRepository = playerRepository;
}
public LeaderboardEntry getTopPlayer(String gameId) {
Score topScore = scoreRepository.findTopByGameId(gameId);
Player player = playerRepository.findById(topScore.getPlayerId())
.orElseThrow();
return new LeaderboardEntry(player.getNickname(), topScore.getPoints());
}
}Der Service kennt keine HTTP-Details, keine Datenbank-Queries. Er arbeitet mit Abstraktionen und implementiert die Fachlogik.
Markiert Klassen für den Datenzugriff. Repositories kapseln die Persistenz.
@Repository
public class InMemoryScoreRepository implements ScoreRepository {
private final Map<String, List<Score>> scoresByGame = new HashMap<>();
@Override
public Score findTopByGameId(String gameId) {
return scoresByGame.getOrDefault(gameId, List.of()).stream()
.max(Comparator.comparing(Score::getPoints))
.orElse(null);
}
@Override
public void save(Score score) {
scoresByGame
.computeIfAbsent(score.getGameId(), k -> new ArrayList<>())
.add(score);
}
}@Repository hat einen Zusatzeffekt: Spring übersetzt
datenbankspezifische Exceptions in einheitliche Spring-Exceptions. Bei
In-Memory-Implementierungen irrelevant, bei echten Datenbanken
nützlich.
Markiert Klassen der Web-Schicht. Controller nehmen HTTP-Requests entgegen und liefern Antworten.
@Controller
public class HomeController {
private final GameService gameService;
public HomeController(GameService gameService) {
this.gameService = gameService;
}
@GetMapping("/")
public String home(Model model) {
model.addAttribute("games", gameService.getAllGames());
return "index"; // View-Name
}
}Ein @Controller arbeitet mit Views. Die Methode gibt
einen View-Namen zurück, Spring rendert das passende Template.
Eine Kombination aus @Controller und
@ResponseBody. Für REST-APIs, die JSON zurückgeben.
@RestController
@RequestMapping("/api/games")
public class GameApiController {
private final GameService gameService;
public GameApiController(GameService gameService) {
this.gameService = gameService;
}
@GetMapping
public List<Game> getAllGames() {
return gameService.getAllGames(); // Wird zu JSON
}
@GetMapping("/{id}")
public Game getGame(@PathVariable String id) {
return gameService.getGame(id); // Wird zu JSON
}
}Kein View-Name, kein Template. Die Rückgabewerte werden direkt als JSON serialisiert.
| Schicht | Annotation | Typische Aufgaben |
|---|---|---|
| Web (HTML) | @Controller |
Requests verarbeiten, Views rendern |
| Web (API) | @RestController |
JSON-Endpoints bereitstellen |
| Logik | @Service |
Geschäftsregeln, Orchestrierung |
| Daten | @Repository |
CRUD, Queries, Persistenz |
| Sonstiges | @Component |
Hilfsklassen, Utilities |
Die Zuordnung ist nicht willkürlich. Sie spiegelt die Schichtenarchitektur wider:
Technisch könntest du alles mit @Component annotieren.
Der Code würde funktionieren. Aber:
Lesbarkeit: @Service sagt sofort, was
die Klasse tut. @Component sagt nur, dass sie eine Bean
ist.
Tooling: IDEs und Analyse-Tools nutzen die Stereotypen. Sie zeigen Services anders an als Repositories, warnen bei falscher Schichtzuordnung.
Zusatzfunktionen: @Repository aktiviert
Exception-Translation. @Controller aktiviert
Web-spezifisches Verhalten. @Component ist neutral.
Team-Kommunikation: In Code-Reviews ist sofort klar, welche Rolle eine Klasse spielt.
Die richtige Annotation ist wie der richtige Klassenname: technisch egal, praktisch wichtig.