26 Konfigurationswerte einbinden

Konfiguration in Dateien ist nur die halbe Miete. Der Code muss die Werte auch lesen können. Spring bietet dafür mehrere Wege – von einfach bis typsicher.

26.1 @Value – der direkte Weg

Die @Value-Annotation injiziert einzelne Werte direkt in Felder:

# application.yaml
arcade:
  max-scores-per-player: 100
  default-game: pacman
  admin-email: admin@arcade.local
@Service
public class LeaderboardService {
    
    @Value("${arcade.max-scores-per-player}")
    private int maxScoresPerPlayer;
    
    @Value("${arcade.default-game}")
    private String defaultGame;
    
    @Value("${arcade.admin-email}")
    private String adminEmail;
    
    public List<Score> getTopScores(String gameId) {
        String game = (gameId != null) ? gameId : defaultGame;
        return scoreRepository.findTopByGame(game, maxScoresPerPlayer);
    }
}

Die Syntax ${property.name} verweist auf die Property. Spring löst den Wert beim Start auf.

26.2 Defaultwerte

Was passiert, wenn eine Property fehlt? Ohne Absicherung: Fehler beim Start. Mit Defaultwert: kein Problem.

// Defaultwert nach dem Doppelpunkt
@Value("${arcade.max-scores-per-player:50}")
private int maxScoresPerPlayer;

@Value("${arcade.feature.new-ui:false}")
private boolean newUiEnabled;

@Value("${arcade.welcome-message:Willkommen!}")
private String welcomeMessage;

Wenn arcade.max-scores-per-player nicht definiert ist, wird 50 verwendet. Praktisch für optionale Konfiguration.

26.3 @Value mit Constructor Injection

@Value funktioniert auch mit Constructor Injection:

@Service
public class LeaderboardService {
    
    private final int maxScoresPerPlayer;
    private final String defaultGame;
    
    public LeaderboardService(
            @Value("${arcade.max-scores-per-player:50}") int maxScoresPerPlayer,
            @Value("${arcade.default-game:pacman}") String defaultGame) {
        this.maxScoresPerPlayer = maxScoresPerPlayer;
        this.defaultGame = defaultGame;
    }
}

Die Felder können jetzt final sein. Besser für Immutability.

26.4 @ConfigurationProperties – der typsichere Weg

Bei vielen zusammengehörigen Properties ist @Value umständlich. @ConfigurationProperties bündelt sie in einer Klasse:

# application.yaml
arcade:
  max-scores-per-player: 100
  default-game: pacman
  leaderboard:
    page-size: 20
    cache-duration: 300
@ConfigurationProperties(prefix = "arcade")
public class ArcadeProperties {
    
    private int maxScoresPerPlayer = 50;
    private String defaultGame = "pacman";
    private Leaderboard leaderboard = new Leaderboard();
    
    // Getter und Setter
    public int getMaxScoresPerPlayer() { return maxScoresPerPlayer; }
    public void setMaxScoresPerPlayer(int max) { this.maxScoresPerPlayer = max; }
    
    public String getDefaultGame() { return defaultGame; }
    public void setDefaultGame(String game) { this.defaultGame = game; }
    
    public Leaderboard getLeaderboard() { return leaderboard; }
    public void setLeaderboard(Leaderboard lb) { this.leaderboard = lb; }
    
    public static class Leaderboard {
        private int pageSize = 20;
        private int cacheDuration = 300;
        
        // Getter und Setter
        public int getPageSize() { return pageSize; }
        public void setPageSize(int size) { this.pageSize = size; }
        
        public int getCacheDuration() { return cacheDuration; }
        public void setCacheDuration(int duration) { this.cacheDuration = duration; }
    }
}

Die Klasse muss aktiviert werden:

@SpringBootApplication
@ConfigurationPropertiesScan  // Aktiviert @ConfigurationProperties
public class ArcadeHighscoreApplication {
}

Oder alternativ:

@Configuration
@EnableConfigurationProperties(ArcadeProperties.class)
public class AppConfig {
}

26.5 Properties-Klasse nutzen

Die Properties-Klasse wird wie jede andere Bean injiziert:

@Service
public class LeaderboardService {
    
    private final ArcadeProperties properties;
    
    public LeaderboardService(ArcadeProperties properties) {
        this.properties = properties;
    }
    
    public List<Score> getTopScores(String gameId, int page) {
        String game = (gameId != null) ? gameId : properties.getDefaultGame();
        int pageSize = properties.getLeaderboard().getPageSize();
        return scoreRepository.findTopByGame(game, page, pageSize);
    }
}

Vorteile gegenüber @Value:

26.6 Wann welchen Ansatz?

Situation Empfehlung
Ein einzelner Wert @Value
Wenige unabhängige Werte @Value
Zusammengehörige Werte @ConfigurationProperties
Verschachtelte Struktur @ConfigurationProperties
Validierung nötig @ConfigurationProperties

26.7 Eigene Properties im Arcade-Projekt

Für das Arcade-Projekt reicht vorerst @Value. Ein Beispiel im HomeController:

# application.yaml
arcade:
  title: "Arcade Highscore API"
  version: "1.0"
@Controller
public class HomeController {
    
    private final GameService gameService;
    
    @Value("${arcade.title:Arcade}")
    private String title;
    
    @Value("${arcade.version:0.0.0}")
    private String version;

    public HomeController(GameService gameService) {
        this.gameService = gameService;
    }
    
    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("games", gameService.getAllGames());
        model.addAttribute("title", title);
        model.addAttribute("version", version);
        return "index";
    }
}

Die Werte erscheinen jetzt auf der Landingpage – konfigurierbar, ohne Code-Änderung.