Wenn ihr Java Applications schreibt, wollt ihr diese bestimmt auch irgendwo deployen. Mal angenommen es existiert ein Test Server, ein Produktiv Server und auch ein Integration Environment, dann müsst ihr die verschiedenen Configs für Datenbanken, Logging, Services, usw. usf. ja irgendwo unter bekommen. Das Spring Framework bietet dafür mit den Spring Profiles einen sehr eleganten Weg die Configs zu organisieren. Damit erreicht ihr, dass alle eure Konfigurationen direkt mit der Applikation verpackt sind und ihr ein und das selbe Package für alle Environments verwenden könnt.

Aktive Spring Profiles könnt ihr mit Hilfe des JVM Argument Parameters spring.profiles.active setzen.

Habt ihr ein fat jar - beispielsweise jenes, welches aus meinem verlinkten Spring Boot Projekt entsteht - könnt ihr beim Ausführen die Profiles in den JAVA_OPTS setzen:

$ JAVA_OPTS=-Dspring.profiles.active=test ./spring-profiles.jar

Habt ihr euch für ein Servlet Container Deployment entschieden, so könnt ihr auch dort die JAVA_OPTS entsprechend anpassen.

Es ist auch möglich mehrere Profile mit Beistrich getrennt anzugeben. Spring verwendet dann die Konfiguration der Profiles von links nach rechts und überschreibt die voran gestellten Werte, sollten welche vorhanden sein.
Das bedeutet, wenn ihr beispielsweise als Profiles prod,dude angebt und ihr euch für eine YAML Konfiguration entschieden habt, dann werden zuerst alle “default” Werte aus dem resources/application.yml, anschließend alle Werte aus dem resources/application-prod.yml und zuletzt alle Werte aus dem resources/application-dude.yml gelesen.
Es gibt auch die Möglichkeit die Profiles direkt aus einem YAML File zu lesen, doch ich tendiere eher zu eigenen YAML Files für die Profile.
Sollte für ein Profil kein YAML File existieren, ist das auch kein Problem.

Da wir nun wissen wie wir die Spring Profile an unsere Applikation übergeben, organisieren und schachteln können, sollten wir auch etwas damit machen. Dazu habe ich ein kleines Gradle / Java / Spring Boot Projekt geschrieben, welches oben im Header des Posts verlinkt ist.

Ihr könnt die Profiles nun beispielsweise in eurer Logback Konfiguration verwenden. Als Beispiel ein Auszug aus dem logback-spring.xml vom verlinkten Projekt, in welchem ich für die Profile test und prod einen File Appender einrichte:

<springProfile name="test,prod">
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_PATH}/${LOG_FILENAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${LOG_FILENAME}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>7</maxHistory>
            <maxFileSize>100MB</maxFileSize>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
</springProfile>

Wird die Applikation nun mit dem Profil test oder prod gestartet, so wird nicht mehr in die console (wie beim default und ide Profil - siehe logback-spring.xml) geloggt, sondern in das angegebene File des Appenders.

Wenn ihr nun jedoch beides haben wollt - also Console und File Output - dann könnt ihr ganz einfach das default Profile zusätzlich selber angeben.

Im Normalfall verwendet Spring das default Profil nur wenn kein Profil angegeben ist.

Ihr könnt jedoch auch Spring Beans (beispielsweise Services) an bestimmte Profiles binden:

@Service
@Profile({"test", "prod"})
public class StatusLoggingService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingConfig.class);

    @Scheduled(fixedRate = 10000)
    public void logStatus() {
        LOGGER.info("all fine.");
    }
}

In diesem Fall wird das Service nur erzeugt, wenn das Profil test oder prod gesetzt ist und da wir auch eine Scheduled Annotation auf der Methode haben, loggt es alle 10 Sekunden all fine. Da dieses Logging aber nicht sehr sinnvoll ist, könnte hier beispielsweise auch ein Speiseplan (kleiner Insider - liebe Grüße an Tanja) per eMail versendet werden - das würde ich jedoch nicht unbedingt alle 10 Sekunden machen. Geiler Scheiss, oder?

Das verlinkten Spring Boot Projekt startet einen Web Server und liefert dann unter der URL http://localhost:8080/status/show folgendes JSON:

{
  "code": 17,
  "message": "all fine",
  "activeSpringProfiles": "default,prod,dude",
  "applicationConfig": {
    "size": 17,
    "environmentMessage": "this is the dude environment"
}

Die Werte der ApplicationConfig sind in den verschiedenen YAML Files der Profile definiert. Wenn ihr also die gebaute Applikation mit verschiedenen Profilen, wie oben angegeben, startet, dann könnt ihr euch ansehen wie sich die Werte ändern.

Das Projekt verwendet Lombok, d.h. ihr müsst Annotation Processing in IntelliJ IDEA aktivieren und eventuell das Lombok Plugin installieren, solltet ihr das Projekt in IDEA öffnen und ausführen wollen.

Wenn ihr keine IDE sondern nur Java installiert habt, könnt ihr das Projekt auch direkt über den gradle wrapper bauen. Das funktioniert mit ./gradlew build bzw. gradlew.bat build. Das ausführbare jar befindet sich dann in build/libs/. Mit Windows würde ich es jedoch mit java -jar spring-profiles.jar starten, da ihr es sonst nur über den Task Manager beenden könnt :D

Das Projekt erstellt das Verzeichnis /var/log/blog wenn es mit dem Profil test oder prod gestartet wird. Sorgt bitte dafür, dass die nötigen Berechtigungen vorhanden sind, um das Verzeichnis zu erstellen. Sollte ihr Windows benutzen, dann wird das Verzeichnis auf dem verwendeten Laufwerk erstellt. Gegebenenfalls könnt ihr das Verzeichnis später auch natürlich wieder löschen oder ihr ändert den Pfad im logback-spring.xml auf euer Environment.

Hoffentlich hat euch mein Beitrag gefallen. Über Feedback würde ich mich sehr freuen!

kleine Info noch: es wird bald einen Artikel über die generelle Struktur meiner Beispiel Projekte und deren Verwendung geben, dann muss ich das nicht immer bei den Themen Artikeln mitnehmen :)

so long, and thanks for all the fish.