Theorie ist verdaut. Jetzt wird es praktisch. In dieser Übung baust du die erste echte Service-Schicht für das Arcade-Projekt.
Am Ende dieser Übung hast du:
GameService, der Spiele verwaltetGameRepository für den DatenzugriffErstelle ein neues Package repository und darin das
Interface:
src/main/java/de/digitalfrontiers/arcade/repository/GameRepository.java
package de.digitalfrontiers.arcade.repository;
import de.digitalfrontiers.arcade.domain.Game;
import java.util.List;
import java.util.Optional;
public interface GameRepository {
List<Game> findAll();
Optional<Game> findById(String id);
Game save(Game game);
}Das Interface definiert den Vertrag. Wie die Daten gespeichert werden, ist hier egal.
Für den Anfang speichern wir alles im Speicher. Später wird das durch MongoDB ersetzt – der Service merkt davon nichts.
src/main/java/de/digitalfrontiers/arcade/repository/InMemoryGameRepository.java
package de.digitalfrontiers.arcade.repository;
import de.digitalfrontiers.arcade.domain.Game;
import org.springframework.stereotype.Repository;
import jakarta.annotation.PostConstruct;
import java.util.*;
@Repository
public class InMemoryGameRepository implements GameRepository {
private final Map<String, Game> games = new HashMap<>();
@PostConstruct
public void initializeGames() {
// Startdaten für die Arcade
save(new Game("pacman", "Pac-Man",
"Friss alle Punkte, meide die Geister", 3333360));
save(new Game("tetris", "Tetris",
"Stapel die Blöcke, räume Reihen ab", 999999));
save(new Game("snake", "Snake",
"Wachse, ohne dich selbst zu beißen", 99999));
save(new Game("space-invaders", "Space Invaders",
"Vernichte die Alien-Invasion", 99990));
}
@Override
public List<Game> findAll() {
return new ArrayList<>(games.values());
}
@Override
public Optional<Game> findById(String id) {
return Optional.ofNullable(games.get(id));
}
@Override
public Game save(Game game) {
games.put(game.getId(), game);
return game;
}
}Beachte: - @Repository markiert die Klasse als Bean -
@PostConstruct füllt initiale Testdaten ein - Die
Implementierung ist simpel – das ist Absicht
Jetzt der Service, der das Repository nutzt:
src/main/java/de/digitalfrontiers/arcade/service/GameService.java
package de.digitalfrontiers.arcade.service;
import de.digitalfrontiers.arcade.domain.Game;
import de.digitalfrontiers.arcade.repository.GameRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class GameService {
private final GameRepository gameRepository;
public GameService(GameRepository gameRepository) {
this.gameRepository = gameRepository;
}
public List<Game> getAllGames() {
return gameRepository.findAll();
}
public Game getGame(String id) {
return gameRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException(
"Game not found: " + id));
}
}Das ist Constructor Injection: - Das Feld ist final -
Der Konstruktor nimmt das Interface entgegen - Spring injiziert die
InMemoryGameRepository-Bean
Die Game-Klasse braucht einen Konstruktor mit allen
Feldern:
src/main/java/de/digitalfrontiers/arcade/domain/Game.java
package de.digitalfrontiers.arcade.domain;
public class Game {
private String id;
private String name;
private String description;
private Integer maxScore;
public Game() {}
public Game(String id, String name, String description, Integer maxScore) {
this.id = id;
this.name = name;
this.description = description;
this.maxScore = maxScore;
}
// Getter und Setter
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Integer getMaxScore() { return maxScore; }
public void setMaxScore(Integer maxScore) { this.maxScore = maxScore; }
}Der HomeController soll die Spiele anzeigen. Erweitere
ihn:
src/main/java/de/digitalfrontiers/arcade/web/HomeController.java
package de.digitalfrontiers.arcade.web;
import de.digitalfrontiers.arcade.service.GameService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@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";
}
}Auch hier: Constructor Injection mit final-Feld.
Starte die Anwendung:
./gradlew bootRunIn der Konsole solltest du keine Fehler sehen. Spring hat:
InMemoryGameRepository gefunden und instanziiertGameService gefunden und
InMemoryGameRepository injiziertHomeController gefunden und GameService
injiziertDie Kette funktioniert.
Spring hat die Abhängigkeitskette automatisch aufgelöst. Du hast
nirgends new GameService(...) geschrieben. Das ist
Dependency Injection.
Füge Logging hinzu, um die Injection zu sehen:
@Service
public class GameService {
private static final Logger log = LoggerFactory.getLogger(GameService.class);
private final GameRepository gameRepository;
public GameService(GameRepository gameRepository) {
this.gameRepository = gameRepository;
log.info("GameService created with repository: {}",
gameRepository.getClass().getSimpleName());
}
}Beim Start siehst du:
GameService created with repository: InMemoryGameRepository
Der Service weiß nicht, dass er InMemoryGameRepository
bekommt – er sieht nur das Interface. Aber das Log zeigt, was Spring
tatsächlich injiziert hat.