Viele verschiedene Lösungen, aber kein Standard

Vielfalt ja, Standard nein: Konfigurationslösungen in Java

Thorben Janssen, Anatole Tresch

© Shutterstock.com / McIek

Konfiguration ist ein wichtiger Bestandteil moderner Anwendungen und erlaubt es, das Verhalten ohne Neubauen zu verändern. Wie vielfältig und unterschiedlich die dabei auftretenden Anforderungen sind, haben wir im vorherigen Teil dieser Reihe betrachtet. Diesmal wollen wir uns einige bestehende Lösungen ansehen.

Wie nicht anders zu erwarten, sind die bestehenden Konfigurationslösungen genauso vielfältig wie die an sie gestellten Anforderungen. Eine vollständige Betrachtung aller Lösungen ist aufgrund ihrer großen Anzahl leider nicht möglich. Daher haben wir eine Reihe verbreiteter Lösungen ausgewählt, um einen möglichst breiten Überblick über die unterschiedlichen Designansätze geben zu können. Dies sind:

  • Java Properties
  • Apache Commons Configuration
  • Apache DeltaSpike
  • Spring
  • ConfigBuilder
  • Owner
  • Java EE

Aus Platzgründen nicht berücksichtigt haben wir u. a. Netflix Archaia und Akka Configuration, obwohl auch sie einige interessante Konzepte aufweisen. Mit jeder dieser Lösungen wollen wir versuchen, die config.properties-Datei aus Listing 1 einzulesen, die wir im Wurzelverzeichnis des Klassenpfads ablegen.

db.port: 5432
db.host: 127.0.0.1
db.user: admin
db.pass: geheim

Wenn das verwendete Framework die Kombination verschiedener Konfigurationsquellen unterstützt, wird der Konfigurationsparameter db.pass mithilfe einer System-Property mit dem Wert my$ecret überschrieben.

Java-Properties

Mit der Properties-Klasse bietet Java eine einfache Möglichkeit, um Konfigurationsdaten aus einer Properties-Datei zu lesen und auf diese in Form von Schlüssel-/Wertpaaren zuzugreifen.

Properties prop = new Properties();
prop.load(new FileReader("config.properties"));

int dbPort = Integer.parseInt(prop.getProperty("db.port", "5432"));
String dbHost = prop.getProperty("db.host", "127.0.0.1");
String dbUser = prop.getProperty("db.user");
String dbPass = prop.getProperty("db.pass");

Wie Listing 2 zeigt, erfolgt das Lesen der Properties-Datei und der Zugriff auf die Konfigurationsparameter ausschließlich programmatisch. Annotationen können nicht verwendet werden.

Ein weiterer Nachteil dieses Ansatzes ist die geringe Anzahl unterstützter Konfigurationsquellen. Das API ist auf das Einlesen von genau einer Properties-Datei ausgelegt, und auch die Kombination verschiedener Konfigurationsquellen zu einer Konfiguration muss vom Entwickler selbst implementiert werden. Darauf haben wir in diesem Beispiel verzichtet.

Der Zugriff auf die Parameter erfolgt nicht typisiert. Die Methode getProperty(String key) gibt ein Objekt vom Typ String zurück, das anschließend, wie beim Parameter db.port zu sehen, programmatisch in den Zieltyp umgewandelt werden muss. Wie bei den Parametern db.port und db.host zu sehen, besteht die Möglichkeit, einen Standardwert anzugeben. Dieser wird verwendet, wenn die Parameter nicht in der Konfigurationsdatei enthalten sind.

Auch wenn durch das Format der Properties-Dateien genau genommen keine hierarchische Struktur modelliert wird, wäre in vielen praktischen Anwendungen ein Zugriff auf alle Parameter mit demselben Namenspräfix wünschenswert, z. B. db. Dies wird jedoch nicht unterstützt.

Zusammenfassend lässt sich feststellen, dass die Verwendung von Java-Properties eine einfache und aufgrund ihrer Zugehörigkeit zum JDK naheliegende Möglichkeit zum Zugriff auf Konfigurationsdaten in jeder Laufzeitumgebung darstellt. Der Funktionsumfang ist jedoch stark begrenzt, und bereits einfache Anforderungen müssen vollständig selbst entwickelt werden. Besonders der ungetypte Zugriff auf Konfigurationsparameter und die fehlende Unterstützung mehrerer Konfigurationsquellen schmerzen dabei in der Praxis sehr.

Apache Commons Configuration

Wie alle Apache-Commons-Projekte erfreut sich auch Commons Configuration einer großen Beliebtheit in der Entwicklergemeinde. Im Rahmen dieses Artikels betrachten wir die Version 1.10, da sich die überarbeitete Version 2.0 noch im Alphastadium befindet.
Im Vergleich zum vorher betrachteten Zugriff mittels Java-Properties verfügt Commons Configuration über einen deutlich größeren Funktionsumfang, über den wir hier nur einen Überblick geben können. Dazu zählen unter anderem die Unterstützung verschiedener Konfigurationsquellen, wie Properties-Dateien, XML-Dokumente, System-Properties und JNDI sowie deren Kombination zu einer gemeinsamen Konfiguration. Eine vollständige Liste der unterstützten Konfigurationsquellen und -formate kann dem Userguide entnommen werden. Alle Konfigurationsquellen implementieren das Configuration-Interface, mit dem ein einheitlicher Zugriff auf die Konfigurationsdaten möglich ist. Dies nutzen wir in Listing 3, um den Konfigurationsparameter db.port aus der config.properties-Datei mit einem Wert aus den System-Properties zu überschreiben. Dabei werden die zuerst geladenen Parameter vorrangig behandelt. Ein Zugriff auf die Konfigurationsdaten ist auch hier ausschließlich programmatisch möglich.

CompositeConfiguration config = new CompositeConfiguration();
config.addConfiguration(new SystemConfiguration());
config.addConfiguration(new PropertiesConfiguration("config.properties"));
int dbPort = config.getInt("db.port");
String dbHost = config.getString("db.host");
String dbUser = config.getString("db.user");
String dbPass = config.getString("db.pass");

Wie in Listing 3 dargestellt, unterstützt Commons Configuration auch den typisierten Zugriff auf die Konfigurationsparameter. Dabei werden u. a. int, float, double, boolean und String unterstützt. Eine vollständige Liste kann dem Userguide entnommen werden.

Für den hierarchischen Zugriff auf Konfigurationsdaten bietet das API unterschiedliche Möglichkeiten. Zum einen können mithilfe der Methode subset(String prefix) des Configuration-Interface alle Konfigurationsparameter mit einem bestimmten Präfix ausgewählt werden. Wir könnten also mittels subset(„db“) die Parameter db.port, db.host, db.user und db.pass auswählen. Zum anderen stehen für die Verwendung echter hierarchischer Konfigurationsquellen, wie z. B. XML-Dateien, verschiedene spezialisierte Implementierungen des Configuration-Interface zur Verfügung, die zusätzliche Funktionen anbieten. Dadurch besteht die Möglichkeit, große Konfigurationen zu strukturieren und innerhalb der Anwendung einfach auszuwerten.

Neben den genannten Funktionalitäten bietet Commons Configuration auch ein Eventsystem, um zur Laufzeit auf Konfigurationsänderungen zu reagieren. Des Weiteren wird die Konfiguration von Mehrmandantensystemen unterstützt. Damit können auch die im ersten Teil der Serie genannten Anforderungen komplexerer Systeme umgesetzt werden. Eine genauere Beschreibung dieser Funktionalitäten würde allerdings den Umfang dieses Artikels überschreiten.

Insgesamt bietet Commons Configuration einen deutlich größeren Funktionsumfang als die vorher betrachteten Java-Properties. Dabei stellen vor allem die Kombination verschiedener Konfigurationsquellen und der typisierte Zugriff auf die Konfigurationsparameter eine deutliche Erleichterung dar. Durch das Eventsystem und die Unterstützung von Systemumgebungen mit mehreren Mandanten können auch komplexere Anwendungsszenarien realisiert werden.

Apache DeltaSpike

DeltaSpike ist ein weiteres sehr verbreitetes Projekt der Apache Software Foundation. Hierbei liegt der Fokus allerdings nicht auf der Verarbeitung von Konfigurationsdaten, sondern auf der Bereitstellung verschiedener portabler CDI Extensions. Dabei handelt es sich um den Erweiterungsmechanismus der CDI-Spezifikation, mit dem zusätzliche Funktionalität zum Container hinzugefügt werden kann. DeltaSpike nutzt dies, um u. a. einen Konfigurationsmechanismus bereitzustellen, den wir uns im Folgenden genauer ansehen wollen.

Es werden verschiedene Konfigurationsquellen unterstützt, die mit unterschiedlicher Priorität verarbeitet werden. Dies bietet die Möglichkeit, Konfigurationsdaten aus verschiedenen Quellen miteinander zu kombinieren und einzelne Parameter gezielt zu überschreiben. Dazu werden die Konfigurationsquellen System Properties, Environment Properties, JNDI und Properties-Dateien unterstützt. Zusätzliche Konfigurationsquellen können über den ClassLoader hinzugefügt oder selbst implementiert werden, um z. B. Konfigurationsdaten aus einer Datenbank zu lesen. Die Reihenfolge, in der die Konfigurationsquellen ausgewertet werden, wird durch den Wert deltaspike_ordinal bestimmt, der von jeder ConfigSource abgefragt werden kann. Jede Properties-Datei wird mit einer eigenen PropertiesConfigSource eingelesen und kann mit einem individuellen deltaspike-ordinal-Wert konfiguriert werden. Dies ermöglicht es, beliebige Konfigurationsdateien miteinander zu kombinieren.

Der Zugriff auf die Konfigurationsdaten kann entweder programmatisch oder mithilfe von Dependency Injection erfolgen. Der programmatische Zugriff ist unabhängig von CDI und kann dadurch z. B. auch für die interne Konfiguration von DeltaSpike selbst verwendet werden. Dependency Injection (oder besser Configuration Injection) erzeugt aber weitaus weniger Codeabhängigkeiten und vermeidet vor allem auch unnötigen Code und ist somit häufig die bessere Alternative. Daher betrachten wir diese hier näher.

DeltaSpike und CDI erlauben die typsichere Injection von Konfigurationsdaten in eine Bean. Dazu müssen die jeweiligen Properties der Bean mit @Inject und @ConfigProperty annotiert werden (Listing 4). Zur Ausführungszeit der Anwendung werden die Konfigurationsdaten dann mithilfe von CDI-Producer-Methoden bereitgestellt. DeltaSpike selbst stellt leider nur Producer für den Datentyp String zur Verfügung. Dieser Mechanismus kann durch die Implementierung eigener Qualifier- und Producer-Methoden erweitert werden, sodass auch die Unterstützung anderer Datentypen möglich ist.

@ApplicationScoped
public class MyConfiguredBean {
    @Inject
    @ConfigProperty(name = "db.port")
    private Integer dbPort;

    @Inject
    @ConfigProperty(name = "db.host")
    private String dbHost;

    @Inject
    @ConfigProperty(name = "db.user")
    private String dbUser;

    @Inject
    @ConfigProperty(name = "db.pass")
    private String dbPassword;

    ...
 }

Wie wir gesehen haben, überzeugt Apache DeltaSpike vor allem durch die Verwendung von Injection und die einfache Erweiterbarkeit. Der Funktionsumfang ist nicht so hoch wie im zuvor betrachteten Commons-Configuration-Projekt, allerdings durch die mögliche Kombination verschiedener Konfigurationsquellen und den typsicheren Zugriff auf die Konfigurationsdaten für viele praktische Anwendungen ausreichend.

Spring

Auch das Spring Framework stellt einen Konfigurationsmechanismus zur Verfügung, der es erlaubt, Konfiguration aus beliebigen Datenquellen zu beziehen. Die Basis bildet dabei der PropertyResourceConfigurer, der einen dynamischen Auflösungsmechanismus von Schlüssel-/Wertpaaren zur Verfügung stellt, und für den Spring verschiedene Implementierungen anbietet. Die Klasse PropertySourcesPlaceholderConfigurer erlaubt es, mehrere PropertySource-Instanzen in geordneter Weise einzubinden. Unser Beispiel könnte also in Spring wie in Listing 5 dargestellt aussehen.

<bean id="applicationProperties"
    class="org.springframework.beans.factory.config.
           PropertyPlaceholderConfigurer”>
  <property name="locations">
    <list>
      <value>classpath:config.properties</value>
    </list>
  </property>
  <property name=”systemPropertiesMode” 
            value=”SYSTEM_PROPERTIES_MODE_OVERRIDE”/>
</bean>

Der konfigurierte PropertyPlaceholderConfigurer wird dann vom Spring-Konfigurationsmechanismus verwendet, um entsprechende Ersetzungen in der Spring-Konfiguration zu ermöglichen (Listing 6).

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.user}"/>
    <property name="password" value="${db.pass}"/>
</bean>

Neben den genannten Mechanismen, bietet der Spring-Container auch Injection-Mechanismen an, die ganz ohne XML auskommen (ein Beispiel zeigt Listing 7). Basis ist dabei ein so genannter BeanPostProcessor, der bei Bedarf auch sehr einfach selbst implementiert, erweitert oder angepasst werden kann. Somit bietet Spring ein flexibles, erweiterbares und uniform anwendbares Konfigurationssystem, das sich in der Praxis bewährt hat.

@Configuration
@SystemPropertiesValueSource
@PropertiesValueSource("classpath:config.properties")
public class DbConfig {
    @ExternalValue("db.driver") String dbDriver;
    @ExternalValue("db.url") String dbDriver;
    @ExternalValue("db.user") String dbUser;
    @ExternalValue("db.pass") String dbPassword;
}

ConfigBuilder

Auf GitHub findet sich mit ConfigBuilder ein weiteres Java-SE-Framework, das Annotationen verwendet, um auf Konfigurationswerte zuzugreifen.

Um die Konfiguration aus unserer config.properties-Datei zu verwenden, wird eine einfache Klasse mit je einer entsprechend typisierten Eigenschaft für jeden Konfigurationsparameter verwendet (Listing 8). Mitfilfe der Annotation @PropertiesFiles wird die Konfigurationsdatei benannt. Dabei können auch mehrere Dateien referenziert werden. Zusätzlich werden Kommandozeilenparameter, Environment und System-Properties als Konfigurationsquellen unterstützt.

Die Parameter der Klasse werden mit @PropertyValue annotiert, um die Zuordnung zu den Parametern aus der Properties-Datei herzustellen. Um einen Parameter mit einer System-Property zu überschreiben, wird zusätzlich die Annotation @SystemPropertyValue verwendet. Standardwerte können mitfilfe von @DefaultValue definiert werden. Des Weiteren wird eine Validierung der Konfigurationsparameter auf Basis der Bean-Validation-Spezifikation unterstützt.

@PropertiesFiles(“config.properties”)
public class MyConfiguredBean {
    @PropertyValue("db.port")
    @DefaultValue("5432")
    private int dbPort;

    @PropertyValue("db.host")
    private String dbHost;

    @PropertyValue("db.user")
    private String dbUser;

    @PropertyValue("db.pass")
    @SystemPropertyValue("db.pass") 
    private String dbPassword;

    ...
 }

Der Zugriff auf die Konfiguration erfolgt dann über eine Factory-Methode (Listing 9) und den entsprechenden typsicheren getter-Methoden.

Config myConfig = ConfigBuilder.on(Config.class).build();
int dbPort = myConfig.getDbPort();

Wie auch die anderen auf Annotationen basierenden Konfigurationslösungen, überzeugt ConfigBuilder durch die einfache Verwendung. Der Funktionsumfang scheint uns aber etwas reduziert im Vergleich zu anderen, bereits hier betrachteten Lösungen.

Owner

Auch das Framework Owner arbeitet mit deklarativen Annotationen. Allerdings wird hier nicht eine Klasse, sondern ein Interface als Template verwendet, um die Konfiguration zu modellieren (Listing 10). Das Interface muss dabei das Markerinterface Config erweitern.

Ohne weitere Annotationen sucht Owner im Package des Interface nach einer Properties-Datei mit demselben Namen, im Beispiel also MyConfig.properties. Wir wollen allerdings die Datei config.properties laden und müssen dazu den Namen und Pfad mithilfe der Annotation @Sources definieren. Dabei können auch mehrere Properties-Dateien kombiniert werden.

Die in den Dateien definierten Konfigurationsparameter werden anhand ihres Namens mit dem Template verknüpft oder mit @Key annotiert. Auch Standardwerte und System-Properties werden mittels @DefaultValue und @SystemPropertyValue an den jeweiligen Properties vorgegeben.

@Sources("classpath:config.properties")
public interface MyConfig extends Config {
    @Key("db.port")
    @DefaultValue("5432")
    int dbPort();

    @Key("db.host")
    String dbHost();

    @Key("db.user")
    String dbUser();

    @Key("db.pass")
    @SystemPropertyValue("db.pass") 
    String dbPassword();

}

Ähnlich wie beim zuvor betrachteten ConfigBuilder wird mithilfe der ConfigFactory ein entsprechendes Konfigurationsobjekt erzeugt:

MyConfig cfg = ConfigFactory.create(MyConfig.class, System.getProperties());

Owner ist allerdings eher schwierig zu erweitern oder anzupassen. Die Verwendung und die Möglichkeit, mehrere Properties-Dateien und System-Properties zu einer Konfiguration zu kombinieren, sind jedoch für viele Anwendungen ausreichend.

Java EE

Java-EE-Applikationen werden mithilfe von diversen XML-Konfigurationen konfiguriert. Alle hier aufzulisten, würde den Rahmen des Artikels sprengen, deshalb geben wir nur eine kurze Übersicht über die wichtigsten Erweiterungspunkte:

  • JSR 339 JAX RS: Ein javax.ws.rs.ext.RuntimeDelegate kann registriert werden.
  • JSR 352 Batch: META-INF/batch.xml, META-INF/batch-jobs/*.xml. Hierbei ist es möglich, mit Placeholdern zu arbeiten, die z. B. durch System-Properties ersetzt werden.
  • JSR 338 JPA: Die PersistenceUnits werden in META-INF/persistence.xml konfiguriert, zusätzlich können auch ein PersistenceProvider oder PersistenceProviderResolver registriert werden.
  • JSR 342 EE7 definiert META-INF/application.xml, META-INF/MANIFEST.mf, META-INF/application-client.xml.
  • JSR 345 EJB 3.2: Definiert META-INF/ejb-jar.xml; es ist möglich, eine eigene javax.jms.QueueConnectionFactory zu registrieren.
  • JSR 346 CDI: META-INF/beans.xml, CDI Extensions lassen sich unter META-INF/services/javax.enterprise.inject.spi.Extension registrieren.
  • JSR 349 Bean Validation: META-INF/validation.xml; zusätzlich lassen sich verschiedene SPIs registrieren (ConstraintValidatorFactory, MessageInterpolator, ParameterNameProvider, TraversableResolver).
  • JSR 315 Servlets: web.xml.
  • JSR 372 JSF: Es kann eine Klasse vom Typ javax.faces.application.ApplicationConfigurationPopulator registriert werden, die Zugriff auf das JSR-Konfigurationsdokument ermöglicht, bevor JSF die Konfiguration auswertet.

Zusammenfassend kann festgehalten werden, dass die Vielfalt an Konfigurationsformaten und -lokationen sehr groß ist. Hinzu kommt, dass die Konfiguration zum Deployment-Zeitpunkt einer Java-EE-Applikation gelesen wird und dass es praktisch keine standardisierten Möglichkeiten gibt, diese auf einfache Weise von außen beizusteuern. Eine Ausnahme bildet hier lediglich Java Batch, das Placeholder in sehr eingeschränktem Rahmen zulässt. JPA bietet zwar ein umfangreiches Metamodell an, dieses ist aber leider nur lesbar. Dasselbe gilt auch für das Java-EE-Management-API. CDI und Bean Validation sind positiv hervorzuheben, da sie sehr flexible SPIs mitbringen, die eine Konfigurationsanbindung ohne technische Kniffe ermöglichen. Trotzdem muss eine Anbindung entsprechend selbst programmiert werden.

Zusammenfassend muss die applikatorische EE-Konfiguration bereits zum Bauzeitpunkt einer Applikation weitgehend bekannt sein. Daran ändert auch der so genannte altdd-Mechanismus nicht viel, der es erlaubt, mittels einer System-Property auf alternative Deployment-Deskriptoren zuzugreifen, da dieser Mechanismus auf den Klassenpfad beschränkt ist.

Damit eine EE-Applikation lauffähig ist, muss aber auch der Container entsprechend konfiguriert werden (administrative Ressourcen), damit die Applikation die gewünschten Ressourcen vorfindet. Dieser Bereich wird von den Spezifikationen überhaupt nicht abgedeckt und ist somit produktspezifisch.

Fazit

Es existiert eine Reihe von Konfigurationslösungen, die sich in Umfang und Komfort teilweise erheblich unterscheiden. In Java EE hat sich über die Jahre ordentlich Staub angesammelt, und die bestehenden Möglichkeiten scheinen uns im Zeitalter von Cloud- und Microservices-Architekturen doch eher bescheiden zu sein. Im Bereich Java SE bringen praktisch alle Lösungen interessante Konzepte mit, die sich wie folgt umschreiben lassen:

  • Typsicherheit: Die textuellen Schlüssel-/Wertepaare können bei Bedarf mit Konvertermechanismen in die verlangten Zieltypen konvertiert werden.
  • Default-Werte: Fehlende Werte können mit Standardwerten ergänzt werden.
  • Unterstützung verschiedener Konfigurationsquellen (und -formate)
  • Kombinationen und Überschreiben: Konfigurationen können zu neuen Konfigurationen kombiniert werden und erlauben so das kontrollierte Überschreiben von Werten. Das Kombinieren von verschiedenen Konfigurationsquellen geschieht dabei grundsätzlich in zwei Varianten: 1. einem Builder-/Factory-Ansatz, bei dem die finale Konfiguration explizit programmatisch definiert wird; 2. mit Registrierung und Ordnung von Konfigurationsquellen mittels Ordinalwerten oder Konfiguration, wo das Framework eine flexible und erweiterbare Logik zur Verfügung stellt.
  • Der Zugriff auf die konfigurierten Werte erfolgt ebenfalls nach zwei Mustern: 1. Configuration Injection oder 2. explizite Abfrage der Werte von einem Configuration-Objekt (entweder definiert durch das Framework-API oder als benutzerdefiniertes Template).
  • Laufzeitänderungen an der Konfiguration werden nicht durchgängig unterstützt. Wenn diese möglich sind, werden sie meist als Events an die registrierten Observer weitergegeben.

Die meisten hier vorgestellten Lösungen bieten jedoch nur wenige oder keine höherwertigen Dienste, z. B. für kontextabhängige Konfiguration, dynamische Auflösung innerhalb der Konfiguration, Filter und Staging-Mechanismen etc. Bereits bei vielen relativ einfachen Einsatzszenarien muss oft selbst Hand angelegt werden. Oftmals ist auch ein Einsatz in Java EE nicht im Grunddesign berücksichtigt, und viele der diskutierten Lösungen scheinen uns nicht immer einfach erweiter- und anpassbar.

Apache Tamaya kombiniert nun genau diese Aspekte und Defizite zu einem flexiblen und modularen Konfigurationsframework. Dabei stellen einige Funktionen absolut essenzielle Aspekte dar, andere wiederum lassen sich problemlos auf der Basis eines minimalen, funktionalen Fundaments als Erweiterungsmodule realisieren. Auch Java-EE-Applikationen können, wenn die entsprechenden Konfigurationspunkte ausgenutzt werden, zumindest teilweise effizient und flexibel konfiguriert werden. Wie das genau funktioniert, werden wir uns in den folgenden Teilen dieser Serie ansehen.

Aufmacherbild: Software developer programming code via Shutterstock.com / Urheberrecht: McIek

Geschrieben von
Thorben Janssen
Thorben Janssen
Thorben Janssen arbeitet als Seniorentwickler und Architekt für die Qualitype GmbH und entwickelt seit mehr als zehn Jahren Anwendungen auf Basis von Java EE. Er ist Mitglied der JSR 365 Expert Group und bloggt über Themen rund um Java Enterprise auf http://www.thoughts-on-java.org. Twitter: @thjanssen123
Anatole Tresch
Anatole Tresch
A​natole Tresch studierte Wirtschaftsinformatik und war anschließend mehrere Jahre lang als Managing Partner und Berater aktiv. ​Die letzten Jahre war ​Anatole Tresch als technischer Architekt und Koordinator bei der Credit Suisse​ tätig. Aktuell arbeitet Anatole ​als Principal Consultant für die Trivadis AG, ist Specification Lead des JSR 354 (Java Money & Currency) und PPMC Member von Apache Tamaya. Twitter: @atsticks
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Vielfalt ja, Standard nein: Konfigurationslösungen in Java"

avatar
4000
  Subscribe  
Benachrichtige mich zu:
Rusi Filipov
Gast

Auch einen Blick wert – Typesafe Config https://github.com/typesafehub/config