© Maxim Gaigul/Shutterstock.com
Spring Boot: Die Magie verstehen - Teil 2

Externalized Configuration


Eine Anwendung soll sich während der Entwicklung mit der lokalen Datenbank, in der Produktion jedoch mit einer anderen Datenbank verbinden. Bei klassischen Java-EE-Anwendungen wird auf die Mittel eines Application Servers zurückgegriffen, um diese Verbindungen zu konfigurieren. Spring Boot bietet hierfür das „Externalized Configuration“-Konzept.

Nehmen wir ein ganz konkretes Beispiel. Eine Spring-Boot-Webapplikation startet den eingebetteten Application Server üblicherweise mit dem Port 8080. Es ist jedoch nicht unüblich, dass der Port geändert werden muss – beispielsweise, weil bereits eine andere Applikation auf Port 8080 lauscht. Bei Spring Boot ist es sehr einfach, den Port zu ändern, indem man die server.port Property auf den Wert 7081 setzt. Damit sehen wir bereits ein wichtiges Element der Spring-Boot-Konfiguration: Die Konfiguration basiert auf Properties – einfachen Key-Value-Paaren.

Spring Boot bietet eine Vielzahl verschiedener Möglichkeiten, wo die Property angegeben werden kann. Beispielsweise können Umgebungsvariablen, Kommandozeilenparameter, Properties-Dateien und YAML-Dateien genutzt werden. Aber auch Quellen wie z. B. JNDI oder der Servlet Context sind möglich.

Experimentieren wir mit den wichtigsten Möglichkeiten, eine Konfiguration vorzunehmen. Als Basis für die Experimente dient die Spring-Boot-Applikation aus Listing 1. Das vollständige Codebeispiel findet sich im GitHub Repository [1].

Listing 1

@SpringBootApplication public class SpringEnvironmentExplorationApplication { public static void main(String[] args) { final ConfigurableApplicationContext context = SpringApplication.run(SpringEnvironmentExplorationApplication.class, args); PrintUtil.printCaption("Spring Environment Exploration"); final ConfigurableEnvironment environment = context.getEnvironment(); PrintUtil.printKeyValue("server.port", environment.getProperty("server.port")); // print all currently activated profiles PrintUtil.printKeyValue("active profiles:", environment.getActiveProfiles());  // print all property sources final String[] sources = environment.getPropertySources() .stream() .map(Object::toString) .toArray(String[]::new); PrintUtil.printKeyValue("property sources:", sources);  // close the spring application System.out.println("-----------------------------"); System.exit(SpringApplication.exit(context)); } }

Die genaue Funktionsweise des Beispiels ist für die folgende Betrachtung zweitrangig. Wichtig ist, dass wir den gesuchten Wert über environment.getProperty("server.port") auslesen werden. Zudem wird diese Klasse wie jede typische Spring-Boot-Anwendung gestartet. Spring Boot kapselt das Wissen über die Konfiguration einer Anwendung im sogenannten Environment, über das wir auch Zugriff auf alle Spring Boot zur Verfügung stehenden Properties erhalten.

Für die folgenden Beispiele betrachten wir immer den Output der Anwendung, wie in Abbildung 1 zu sehen – speziell die Zeile mit dem Wert für die Property server.port.

fernandes_spring_1.tif_fmt1.jpgAbb. 1: Ausgabe der SpringEnvironmentExplorationApplication

Konfiguration über die Kommandozeile

Für die Konfiguration über die Kommandozeile (oder beispielsweise in Shellskripten oder Dockerfiles) gibt es drei Möglichkeiten:

  • als klassische System Property -Dserver.port=7081

  • von Spring Boot interpretierter Anwendungsparameter --server.port=7081

  • als Umgebungsvariable

Bei den Umgebungsvariablen wird eine besondere Form des Spring Boot „Relaxed Binding“ angewendet [2]. Das Prinzip ist recht einfach: Buchstaben werden in Großbuchstaben umgewandelt, Punkte durch ein Underscore ersetzt und Bindestriche entfernt. Damit ergibt sich für unser Beispiel die Umgebungsvariable SERVER_PORT. Starten wir die Anwendung aus Listing 1 mit --server.port=7081, sehen wir in der Ausgabe, dass diese Konfiguration übernommen wurde.

Konfigurationsdateien

Neben den genannten Möglichkeiten über die Kommandozeile bietet Spring Boot die Möglichkeit, Konfigurationsdateien zu verwenden. Die meisten sind hierbei bereits über die application.properties gestolpert, denn bei einer über [3] erstellten Anwendung wird diese automatisch generiert. Einstellungen wie beispielsweise server.port lassen sich in der application.properties setzen. Neben den Properties-Dateien gibt es die Möglichkeit, Konfigurationsdateien im YAML-Format zu verwenden. Spring Boot führt dabei eine Konvertierung von YAML zu Properties durch (Kasten: „YAML zu Properties Mapping“).

Beim Anwendungsstart sucht Spring Boot an zwei unterschiedlichen Stellen nach den application.properties (respektive application.yaml). Zum einen direkt im Klassenpfad der Anwendung selbst. Daher generiert [3] eine application.properties im resources-Verzeichnis. Zusätzlich zu den Konfigurationen im Klassenpfad sucht Spring Boot in einem config/-Verzeichnis unterhalb des aktuellen Ausführungsverzeichnisses.

Spring Boot 2.3 beschränkt sich zudem nicht auf das config/-Verzeichnis, sondern beachtet auch dessen Unterverzeichnisse. Das erlaubt es, die Anwendungskonfiguration zu modularisieren, beispielsweise um Kubernetes ConfigMaps einfacher verwenden zu können. Hat man eine Anwendung, die sowohl eine Datenbank- als auch eine ElasticSearch-Verbindung benötigt, könnte man die Konfiguration entsprechend aufteilen. Abbildung 2 zeigt eine exemplarische Struktur für diesen Anwendungsfall. Ein Mischen von Properties und YAML-Dateien ist dabei problemlos möglich. Versuchen Sie es selbst, indem Sie server.port in verschiedenen Konfigurationsdateien setzen.

fernandes_spring_2.tif_fmt1.jpgAbb. 2: Strukturiertes Konfigurationsverzeichnis

Profile

Wir haben gesehen, wie Konfigurationswerte einfach bereitgestellt werden können. Was ist jedoch, wenn sich die Konfiguration einer Anwendung zwischen Entwicklung und Betrieb unterscheidet? Typischerweise sollte kein Entwickler während der Entwicklung direkt auf die Produktionsdatenbank zugreifen, sondern eine dedizierte Entwicklungsinstanz einer Datenbank verwenden. Genau an dieser Stelle (neben weiteren) können die Profile helfen.

Betrachten wir ein einfaches Beispiel: Während der Integrationstests soll unsere Anwendung über den Port 9080 erreichbar sein, in der Produktionsumgebung jedoch unter 7080. In beiden Fällen müssen wir die Konfigurationsoption server.port verwenden. Nun könnte man für die Entwicklung und Produktion jeweils unterschiedliche Anwendungsparameter (--server.port=7080) verwenden. Untersch...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang