1. Konfiguration als „Wörterbuch“ und Schlüsselstruktur
Wenn man die Spring Boot-Konfiguration sehr bodenständig betrachtet, ähnelt sie einer großen Karte „Schlüssel → Wert“, wobei der Schlüssel ein String wie app.catalog.title ist und der Wert beispielsweise "Spring+ Catalog". Das Problem beginnt, wenn Schlüssel „irgendwie“ auftauchen: heute title, morgen catalogTitle, übermorgen mainTitle, und dann sind Sie sich nicht mehr sicher, was Sie gerade ändern. Spring Boot verbietet dieses Chaos nicht – es geht einfach ehrlich davon aus, dass Sie erwachsen sind und selbst Herr Ihrer Entscheidungen. Daher ist unsere Aufgabe, Chaos aus Gewohnheit unmöglich zu machen.
Die grundlegende application.yaml haben wir bereits, und das nächste Problem ist nun nicht „wohin mit den Einstellungen“, sondern „wie man die Datei nicht in eine Müllkippe aus title, mode und limit ohne Struktur verwandelt“.
Stellen Sie sich die Konfiguration als eine normale Map<String, String> in Java vor. Das ist nicht „wie innerhalb von Spring“, aber als Modell für den Kopf funktioniert es hervorragend:
import java.util.HashMap;
import java.util.Map;
// Konfiguration als „Schlüssel → Wert“ darstellen (vereinfachtes Modell zum Verständnis)
Map<String, String> props = new HashMap<>();
// Guter Schlüssel: mit verständlichem Namespace, um Mehrdeutigkeit zu vermeiden
props.put("app.catalog.title", "Spring+ Catalog");
// Wert strikt nach vollständigem Schlüssel lesen
System.out.println(props.get("app.catalog.title")); // Spring+ Catalog
Stellen Sie sich nun vor, Sie legen dort einfach title ohne Präfix ab:
import java.util.HashMap;
import java.util.Map;
// Schlüssel in einer solchen „Karte“ sind global – und es ist leicht, auf die eigenen Rechen zu treten
Map<String, String> props = new HashMap<>();
props.put("title", "Spring+ Catalog");
// Zweites put mit demselben Schlüssel überschreibt den ersten Wert
props.put("title", "Oops, another title");
System.out.println(props.get("title")); // Oops, another title
Ja, das Beispiel ist etwas bewusst „frontal“, aber der Sinn ist: Schlüssel sind global, und wenn Sie keine Struktur einführen, bereiten Sie sich selbst eine Quest „Rate mal, welcher title jetzt der Haupt- ist“.
Genau deshalb verwenden Spring Boot-Anwendungen normalerweise „Namespaces“ durch Präfixe: spring.*, server.*, logging.*, und (für benutzerdefinierte Einstellungen) so etwas wie app.*. Das ist keine „Formsache“, sondern eine Möglichkeit, die Konfiguration lesbar, vorhersagbar und erweiterbar zu machen.
| YAML-Pfad | Flacher Property-Schlüssel | Beispielwert |
|---|---|---|
| spring → application → name | spring.application.name | catalog-service |
| app → catalog → title | app.catalog.title | "Spring+ Catalog" |
| app → catalog → max-featured-count | app.catalog.max-featured-count | 4 |
2. Namespaced Properties und Schlüsselstil
Wenn Sie Ihre Konfiguration schreiben, möchten Sie es am liebsten so machen:
title: "Spring+ Catalog"
maxFeaturedCount: 4
maintenanceMode: false
Die ersten 15 Minuten scheint es genial einfach. Dann wächst das Projekt, ein zweiter „title“ taucht auf (z.B. title für Landing Page, title für Logs, title für irgendwelche Metadaten), und plötzlich stellt sich heraus, dass die „Einfachheit“ nur Fehlen von Struktur war.
Im catalog-service legen wir von vornherein fest: Alle benutzerdefinierten Katalogeinstellungen leben unter dem Präfix app.catalog. Dann ist auch ohne Kommentare klar, dass dies unsere Schlüssel sind (nicht Spring) und dass sie sich speziell auf das Katalogmodul beziehen.
Hier die „schlechte“ Form (machen Sie das nicht im Projekt, auch wenn Sie es sehr wollen):
spring:
application:
name: catalog-service
# Schlechtes Signal: Schlüssel hängen in der Wurzel „in der Luft“ – unklar, worauf sie sich beziehen
title: "Spring+ Catalog"
max-featured-count: 4
startup-report-enabled: true
Sieht eigentlich normal aus, aber title ist völlig unklar, wem es gehört. Überschrift von was? Katalog? Anwendung? Browserfenster? Die Person, die diese Datei in einer Woche öffnet (und diese Person werden Sie sein), wird raten.
Und hier die „gute“ Form – bereits wie ein Vertrag:
spring:
application:
name: catalog-service
app:
catalog:
# Expliziter Namespace: sofort ersichtlich, dass dies speziell die Katalogeinstellungen sind
title: "Spring+ Catalog"
max-featured-count: 4
startup-report-enabled: true
Wichtiges Detail: Innerhalb von Spring Boot gibt es keinen separaten „YAML-Baum“. YAML ist nur ein bequemes Aufzeichnungsformat. Unter der Haube wird alles in flache Schlüssel umgewandelt:
Das Präfix sollte den Fachbereich widerspiegeln, nicht eine zufällige Fantasie. Wenn Sie my.cool.super.config.title schreiben, wird es … lustig, aber nicht sehr nützlich. app.catalog.* – kurz, klar und nicht in Konflikt mit Spring selbst.
Kebab-case bei Schlüsseln
Es gibt einen seltenen Fall, in dem Bindestriche das Leben wirklich verbessern und nicht nur Schmerz hinzufügen: das sind Namen von Konfigurationsschlüsseln. In Spring Boot verwenden fast alle Dokumentationen und die meisten integrierten Eigenschaften kebab-case: server.port, spring.application.name, spring.jackson.* und so weiter. Wenn Sie für Ihre Schlüssel denselben Stil wählen, verringern Sie die Wahrscheinlichkeit, auf eine Mischung von Stilen in einer Datei zu stoßen.
Im catalog-service halten wir uns an eine einfache Regel: Schlüssel innerhalb von YAML – Kleinbuchstaben + Bindestriche, also kebab-case. Das sieht dann so aus:
app:
catalog:
# kebab-case: Kleinbuchstaben + Bindestriche, um keinen Zoo an Varianten zu erzeugen
maintenance-mode: false
max-featured-count: 4
default-published-only: true
Wenn Sie maxFeaturedCount schreiben, wird YAML das schlucken, Spring Boot wird es wahrscheinlich auch beim Lesen als „String“ schlucken, aber dann beginnen kleine lästige Probleme: Sie schreiben mal maxFeaturedCount, mal max-featured-count, mal versuchen Sie, den richtigen Varianten zu „erraten“. Und ja, solche Kleinigkeiten sind die Hauptquelle für Bugs „aus heiterem Himmel“, die dann aussehen wie „Spring liest meine Konfiguration nicht“ (obwohl Spring einfach den falschen Schlüssel liest, den Sie gemeint haben).
Es gibt noch einen weiteren Bonus: kebab-case harmoniert gut damit, wie Spring Boot normalerweise die Konfiguration an ein Java-Modell bindet (dort meist camelCase), aber heute müssen wir nicht tiefer darauf eingehen. Wir wählen einfach einen einheitlichen Stil, um nicht in einem „Schlüsselzoo“ zu leben.
3. Platzhalter ${...} und Wiederverwendung von Werten
Sobald Sie die Konfiguration strukturiert haben, tritt ein zweites typisches Problem auf: Duplizierung. Und sie ist tückischer, als es scheint. Denn solange Sie einen Wert zweimal duplizieren, funktioniert alles. Aber dann ändern Sie eine Stelle und vergessen die zweite, und Sie haben die klassische Geschichte „Warum sagt das Banner etwas anderes als der Titel“.
Spring Boot hat einen standardmäßigen Mechanismus zum „Einsetzen eines Werts in einen Wert“ – Platzhalter ${...}. Das bedeutet wörtlich: „Nimm den Wert eines bestimmten Schlüssels und füge ihn hier ein“.
Zum Beispiel möchten Sie eine freundliche Textnachricht einführen, die den Anwendungsnamen aus spring.application.name verwendet:
spring:
application:
name: catalog-service
app:
catalog:
# Platzhalter: Wert von spring.application.name in den String einfügen
banner: "Welcome to ${spring.application.name}"
Wenn Sie nun spring.application.name ändern, ändert sich auch das Banner, ohne manuelle Synchronisation. Das ist keine „Magie“, sondern einfache Zeichenkettenersetzung.
Platzhalter können nicht nur als „gesamter Wert“ eingefügt werden, sondern auch als Teil einer Zeichenkette:
spring:
application:
name: catalog-service
app:
catalog:
title: "Spring+ Catalog"
# Banner aus zwei Quellen zusammensetzen: Titel + Anwendungsname
banner: "${app.catalog.title} (${spring.application.name})"
Hier wird banner sauber aus zwei Quellen zusammengesetzt: dem menschenlesbaren title und dem technischen spring.application.name. In echten Projekten ist das oft nützlich für Logs und Diagnosemeldungen (nicht weil „es so sein muss“, sondern weil es später leichter zu verstehen ist, was überhaupt läuft).
Wenn man sich das als kleine Pipeline vorstellt, wird das Bild sehr klar:
flowchart LR A["spring.application.name"] --> C["app.catalog.banner"] B["app.catalog.title"] --> C["app.catalog.banner"]
Wichtig zu bedenken: Ein Platzhalter ist ein Verweis auf einen Wert, kein „Code“. Er führt keine Berechnungen durch, kann keine „Zahlen addieren“ und „wandelt keinen String in ein Datum um“. Das ist ein Plus: weniger Überraschungen. Und ein Minus: Komplexe Logik sollte man nicht in Platzhalter stopfen – sonst bekommt man ein Konfigurationsrätsel.
Und noch ein praktischer Hinweis: Wenn ein Wert „syntaxähnlich“ wird (geschweifte Klammern, Doppelpunkte), ist es besser, ihn in YAML explizit in Anführungszeichen zu setzen. Nicht weil „Spring es verlangt“, sondern weil YAML ein Format mit eigener Grammatik ist und Anführungszeichen Mehrdeutigkeiten beseitigen.
4. Standardwerte ${name:default} und Nutzungsgrenzen
Der Platzhalter ${key} ist gut, solange key tatsächlich existiert. Aber das Leben wirft gerne Situationen wie „Schlüssel vergessen“, „Schlüssel umbenannt“, „Schlüssel nicht erforderlich“ ein. Damit die Anwendung nicht bei jedem fehlenden Wert abstürzt, gibt es in Platzhaltern einen Mechanismus für Standardwerte: ${name:default}. Er wird so gelesen: „Nimm name, wenn es nicht existiert, verwende default“.
Das einfachste Beispiel sieht so aus:
app:
catalog:
# Wenn runtime-mode nicht gesetzt ist, nehmen wir "standard" (wichtig: Doppelpunkt → besser in Anführungszeichen)
mode-label: "${app.catalog.runtime-mode:standard}"
Wenn app.catalog.runtime-mode irgendwo definiert ist – nehmen wir ihn. Wenn nicht – verwenden wir standard. Und beachten Sie die Anführungszeichen: Aufgrund des : innerhalb des Strings ist dies einfach eine sicherere Schreibweise in YAML.
Standardwerte sind besonders nützlich für „weiche“ Einstellungen, die die Anwendung nicht lahmlegen. Zum Beispiel für eine Signatur, ein dekoratives Flag oder für ein Verhalten, das einen offensichtlichen sicheren Modus hat.
Dabei gibt es eine sehr wichtige technische Grenze: Man sollte nicht überall einen Standardwert setzen. Wenn ein Wert ein obligatorischer Teil des Anwendungsvertrags ist, sollte die Anwendung lieber beim Start abstürzen und ehrlich sagen „Ich kann ohne diesen Schlüssel nicht leben“, als mit einem stillen „Nun, ich habe den Standard genommen“ zu starten und sich unerwartet zu verhalten.
Das heißt, ein Standardwert ist nicht dazu da, „Fehler zu vermeiden“, sondern um „das Verhalten bei fehlendem Wert zu wählen“. Das sind verschiedene Dinge.
Ich zeige es an einem Beispiel. Angenommen, Sie möchten, dass die Moduskennzeichnung nur von außen kommt und die Anwendung ohne sie nicht startet. Dann könnte ein bewusst strenges Fragment so aussehen:
app:
catalog:
# Hier muss runtime-mode von außen kommen; sonst stoppt Boot ehrlich den Start
mode-label: "${app.catalog.runtime-mode}"
Wenn app.catalog.runtime-mode nirgendwo gesetzt ist und kein Standardwert angegeben ist, wird Spring Boot normalerweise beim Start mit einem Fehler „Kann Platzhalter nicht auflösen“ abstürzen. Das ist Fail-Fast und für obligatorische Dinge nützlich.
Ein sinnvolleres Beispiel für Standardwerte: Sie möchten das Basislimit für Featured-Kurse an einem Ort speichern und gleichzeitig einen sicheren Ersatzwert haben, falls dieser Konfigurationsknoten überhaupt nicht gesetzt ist.
app:
catalog:
defaults:
max-featured-count: 6
max-featured-count: "${app.catalog.defaults.max-featured-count:4}"
Wenn app.catalog.defaults.max-featured-count gesetzt ist, gewinnt er. Wenn nicht, verwenden wir 4. Ein solches Muster hilft, Zahlen nicht in der Datei zu vervielfältigen. Ja, es taucht ein zusätzlicher Knoten defaults auf, aber er dient genau als „ein Ort, an dem die Standardwerte liegen“. Dies ist ein Lernfragment auf der Basis des bereits bekannten app.catalog.* und keine zwingende neue Struktur des gesamten Projekts.
Ein weiteres Feature: Ein Standardwert kann selbst ein Platzhalter sein. Das ist praktisch, wenn Sie einen „allgemeineren“ und einen „spezielleren“ Schlüssel haben und der speziellere den allgemeinen erben soll, wenn er nicht gesetzt ist.
app:
defaults:
title: "Catalog Platform"
catalog:
display-title: "${app.catalog.title:${app.defaults.title}}"
Es liest sich so: Wenn es app.catalog.title gibt, nehmen wir es; sonst nehmen wir app.defaults.title. Das ist sauber und macht die Konfiguration nicht zu Copy-Paste, bleibt aber dennoch lesbar. Wichtig: Daraus nicht „drei Verschachtelungsebenen“ für eine einzelne Zeile bauen. Und das ist auch nur ein lokales Pattern, kein neuer verbindlicher Vertrag für catalog-service.
5. Konfigurationsvertrag für catalog-service
An dieser Stelle haben wir bereits die grundlegende application.yaml aus der vorherigen Vorlesung: spring.application.name und einen Satz app.catalog.*. Wichtig ist hier, keinen zweiten parallelen Projektvertrag zu erstellen, sondern zu sehen, wie Platzhalter und Standardwerte auf den bereits bekannten Schlüsseln aufbauen.
Der natürlichste abgeleitete Schlüssel (derived-key) für catalog-service ist ein Dienstbanner, das aus bereits vorhandenen Werten zusammengesetzt ist:
app:
catalog:
banner: "${app.catalog.title} (${spring.application.name})"
Dies ist ein lokales Fragment auf Basis der Basiskonfiguration: title und spring.application.name bleiben dort, wo sie waren, und banner verwendet sie einfach wieder, ohne Copy-Paste.
Wenn ein weicher Standardwert benötigt wird, fügen Sie einen separaten derived-key oder einen lokalen Knoten defaults nur dort hinzu, wo er tatsächlich Copy-Paste eliminiert. Aber die grundlegenden project-level Schlüssel app.catalog.title, maintenance-mode, max-featured-count, default-published-only und startup-report-enabled bleiben die gleichen, die wir bereits angelegt haben. Platzhalter und Standardwerte ersetzen diese Basis nicht, sondern helfen, sie ohne Copy-Paste zu erweitern.
6. Typische Fehler bei Namespacing, Platzhaltern und Standardwerten
An dieser Stelle entsteht normalerweise das Gefühl „Okay, ich hab's verstanden: Ich muss überall ${...} setzen und alles wird schön“. Und genau hier sollte man bremsen. Platzhalter und Standardwerte sind ein gutes Werkzeug, aber sie können zu Verwirrung führen, wenn man sie maßlos einsetzt.
Fehler Nr. 1: „Präfix um des Präfix willen“ ohne fachlichen Sinn.
Manchmal erfinden Leute Präfixe, die nichts erklären: my.settings.*, custom.props.*, project.*. Nach einem Monat hilft das weder beim Lesen noch beim Suchen. Ein guter Namespace beantwortet die Frage „Worauf bezieht sich das?“: app.catalog.* – auf den Katalog, app.* – auf die Anwendung insgesamt. Wenn ein Präfix nicht mit einem Satz erklärt werden kann, ist es wahrscheinlich schlecht.
Fehler Nr. 2: Mischung von Schlüsselstilen in einer Datei.
Wenn nebeneinander maxFeaturedCount, max-featured-count, max_featured_count und MaxFeaturedCount auftauchen, beginnt das Gehirn, Energie nicht auf das Verständnis der Anwendung zu verwenden, sondern auf „Wie ist das hier üblich?“. Der einfachste Weg, dies zu verhindern, ist, sich auf kebab-case für YAML-Schlüssel zu einigen und dies als Gewohnheit beizubehalten.
Fehler Nr. 3: Platzhalter werden zu einem „Rätsel“ aus Ketten.
${a:${b:${c:${d:42}}}} – das ist keine Konfiguration mehr, sondern ein Rätsel. Ja, technisch kann es funktionieren, aber ein Mensch wird es wie einen Code lesen. Wenn Sie sehen, dass die Platzhalterlogik zu clever wird, ist das normalerweise ein Signal, dass Sie versuchen, „Programmierung“ in die Konfiguration zu stecken, wofür sie nicht gedacht ist.
Fehler Nr. 4: Standardwerte werden für obligatorische Werte gesetzt, „damit es nicht abstürzt“.
Das ist ein sehr menschlicher Wunsch: Lass es irgendwie starten. Aber es führt zu den unangenehmsten Fehlern – stillen. Wenn title obligatorisch ist, soll die Anwendung abstürzen und ehrlich sagen. Standardwerte sind gut für weiche Einstellungen, aber schlecht für Teile, ohne die Ihre Logik sinnlos wird.
Fehler Nr. 5: ${name:default} wird ohne Anführungszeichen geschrieben und man fängt seltsame YAML-Fehler ein.
YAML ist empfindlich gegenüber Zeichen, und der Doppelpunkt ist ein „dienstliches“ Zeichen des Formats. Manchmal funktioniert das Schreiben ohne Anführungszeichen tatsächlich, manchmal erhalten Sie einen Parsing-Fehler oder eine unerwartete Interpretation des Werts. Wenn ein String ${...:...} enthält, ist die ruhigste und vorhersehbarste Option, den Wert einfach in Anführungszeichen zu setzen und nicht „Rate mal, wie YAML das heute liest“ zu spielen.
GO TO FULL VERSION