Teil 1: Environment Abstraction

Spring 3.1 – Was gibt’s Neues?

Tobias Flohre und Pascal Czollmann

Spring 3.1 steht vor der Tür – und mit ihm eine Reihe neuer, interessanter Features. Eines dieser Features stellen wir in diesem Artikel vor: die neue Environment Abstraction, die mit ihren Bean Definition Profiles umgebungsspezifische Bean-Definitionen ermöglicht. Im nächsten Teil geht es dann um die Cache Abstraction, die ein allgemeines API für Cache-Funktionalitäten mit Unterstützung verschiedener Implementierungen anbietet.

Teil 1: Environment Abstraction

Jede Anwendung durchläuft von der Entwicklung über die Testphase bis zum produktiven Einsatz unterschiedliche Lebensphasen. In diesen Phasen stehen in der Regel unterschiedliche Ressourcen zur Verfügung, seien es Hard- und Software, Persistenzmedien oder Fremdkomponenten. In der Konfiguration einer Anwendung bedeutet das zumindest unterschiedliche Konfigurationsdaten wie Username/Password bei einer DataSource. Es können sich in den Umgebungen aber auch Infrastrukturkomponenten unterscheiden. So kann es sein, dass man in einer Entwicklungsumgebung einen DataSourceTransactionManager verwendet, während in Produktion ein JTA-Transaktionsmanager Anwendung findet.

Umgebungsspezifische Spring-Konfigurationen werden meistens per PropertyPlaceholderConfigurer oder austauschbaren ApplicationContext-Dateien realisiert. PerPropertyPlaceholderConfigurer können einzelne Zeichenketten aus der Spring-Konfiguration in eine Properties-Datei ausgelagert werden. So können beispielsweise Verbindungsdaten für eine Datenbank für unterschiedliche Umgebungen konfigurierbar gehalten werden (Listing 1). Was aber, wenn man in der einen Umgebung die DataSource direkt erstellen möchte, sie in der anderen Umgebung aber per JNDI aus einem JEE-Container holen möchte? Hier enden die Möglichkeiten des PropertyPlaceholderConfigurers, da keine Bean-Definitionen ausgetauscht werden können. Mit austauschbaren ApplicationContext-Dateien ist das Problem lösbar, aber bei einer Zusteuerung per Build-Prozess ist das Anwendungsartefakt auf eine Umgebung spezialisiert. Das import-Tag kann Platzhalter auflösen, die entweder in JVM-Systemproperties oder in Umgebungsvariablen definiert sind:

<import resource="${env}-applicationContext.xml" />
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="${jdbc.dataSourceClassName}">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="password" value="${jdbc.password}"/>
<property name="username" value="${jdbc.username}"/>
</bean>

Spring 3.1 konsolidiert und erweitert die bisherigen Möglichkeiten des Umgebungsmanagements. Zunächst betrachten wir die neuen Bean Definition Profiles.

Bean Definition Profiles

Wir gehen im Folgenden von einer Anwendung aus, die im Infrastrukturbereich für den Datenzugriff einen Transaktionsmanager und eine DataSource benötigt. In der Entwicklungsumgebung läuft unsere Anwendung auf einem Tomcat. Eine lokale DataSource wird erzeugt, ein DataSourceTransactionManager regelt das Transaktionsverhalten. Als Integrations- und Produktionsumgebung läuft unsere Anwendung auf einem Application Server, daher werden Transaktionsmanager und DataSource per JNDI aus dem Verzeichnis des Java-EE-Containers geholt.

Natürlich können diese beiden Konfigurationen nicht gleichzeitig geladen werden, allein schon die identischen Bean-IDs schaffen Probleme. Andererseits wollen wir aber ein Anwendungsartefakt, sei es ein jar, ein war oder ein ear, schaffen, das in allen Umgebungen lauffähig ist. Wir brauchen also einen Mechanismus, der es erlaubt, bei Bedarf bestimmte Spring Beans zu aktivieren oder zu deaktivieren.

Genau dafür werden in Spring 3.1 die Bean Definition Profiles eingeführt. Das beans-Tag, also das Wurzel-Tag einer Spring-XML-Konfiguration, wird um das Attribut profile erweitert. Ist dieses Attribut gesetzt, werden die innerhalb des Tags definierten Bean-Definitionen nur hinzugefügt, wenn ein Umgebungsprofil mit dem entsprechenden Namen aktiviert ist.

Seit Spring 3.1 ist es auch möglich, beans-Tags ineinander zu schachteln. So können Spring-Beans verschiedener Umgebungen in einer Datei definiert werden. Die Konfiguration unserer Anwendung sieht nun so aus wie in Listing 2. Dabei haben wir jeweils für die Entwicklungs-, Integrations- und Produktionsumgebung ein Profil erstellt. Die Konfigurationen für Integrations- und Produktionsumgebung sind zwar identisch, trotzdem mag es außerhalb dieser Konfigurationsdatei Unterschiede in der Konfiguration geben.

<beans ...>
<beans profile="dev">
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="org.SomeDriverClassName"/>
<property name="url" value="someUrl"/>
<property name="password" value="somePassword"/>
<property name="username" value="someUsername"/>
</bean>
</beans>

<beans profile="int,prod">
<tx:jta-transaction-manager/>
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>

Mit der Annotation @Profile kann dieses Feature übrigens auch außerhalb von XML-Konfigurationen verwendet werden.

So weit, so gut. Aber wir haben keines der Profile aktiviert, also werden keine Spring Beans erzeugt. Wie aktiviert man also ein Umgebungsprofil?

[ header = Seite 2: Environment ]

Environment

Hier kommt die neue Umgebungsabstraktion ins Spiel. Ab Spring 3.1 bringt jeder ApplicationContext ein Objekt mit, das das Interface Environment implementiert. Dieses Objekt muss alle aktiven Profile kennen.

Schauen wir zuerst auf den programmatischen Ansatz (Listing 3). Haben wir den ApplicationContext direkt zur Hand, so können wir das aktive Profil direkt am Environment-Objekt setzen. Das Setzen des aktiven Profils muss vor dem Erzeugen der Beans, also vor dem Aufruf der refresh-Methode, stattfinden.

GenericXmlApplicationContext applicationContext =
new GenericXmlApplicationContext();
applicationContext.getEnvironment().setActiveProfiles("dev");
applicationContext.load("META-INF/applicationContext.xml");
applicationContext.refresh();

In der Regel wollen wir die Entscheidung für ein bestimmtes Profil nicht im Code treffen. Daher ist es möglich, Profile über die Property ’spring.profiles.active‘ zu aktivieren. Diese Property kann kommasepariert Profilnamen der zu aktivierenden Profile enthalten und kann über viele mögliche Quelle eingelesen werden. Hier kommt die zweite Aufgabe des Environments ins Spiel: das Management von PropertySources.

PropertySources

Eine PropertySource ist eine Quelle für Properties. Das Environment beinhaltet eine Hierarchie von PropertySources. Wenn unsere Anwendung eine Property anfragt (Listing 4), werden diePropertySources der Reihe nach gefragt, ob sie die Property kennen.

GenericXmlApplicationContext applicationContext =
new GenericXmlApplicationContext();
String propertyActiveProfiles = applicationContext.getEnvironment().getProperty("spring.profiles.active");

Das DefaultEnvironment registriert automatisch PropertySources für die JVM System Properties und die Systemumgebungsvariablen. Dabei steht die PropertySource für die JVM System Properties in der Hierarchie weiter oben, da sie in der Regel spezialisierter sind als die Umgebungsvariablen, die ja für alle JVMs auf der Maschine gelten.

Wollen wir also nun das Profil ‚dev‘ aktivieren, so können wir unsere JVM mit dem Parameter -Dspring.profiles.active=dev starten oder eine Umgebungsvariable mit dem Namen ’spring.profiles.active‘ und dem Wert ‚dev‘ erzeugen.

In einer Webumgebung werden automatisch weitere PropertySources für Daten aus der web.xml registriert. Wahlweise kann auch eine JndiPropertySource aktiviert und auch eigenePropertySources implementiert und der Umgebung hinzugefügt werden.

Nutzung der Properties

Bisher haben wir einen Anwendungsfall für Properties gesehen, nämlich die Property ’spring.profiles.active‘, die von Spring genutzt wird, um die aktiven Umgebungsprofile zu setzen. In Listing 1 haben wir gesehen, wie man in einer Spring-Konfiguration mithilfe des Tags property-placeholder Platzhalter mit Werten aus einer Properties-Datei ersetzen kann. Ab Spring 3.1 wird nun bei Verwendung des Tags der PropertySourcesPlaceholderConfigurer registriert, der zunächst in den definierten Dateien sucht und danach die PropertySources des Environments durchgeht. Auch bei der Platzhalterersetzung im import-Tag werden nun alle PropertySources durchsucht. Wir haben insgesamt also eine höhere Flexibilität, da die Werte aus beliebigen Quellen stammen können.

Fazit

Die Environment Abstraction fasst bereits bestehende und neue Funktionalitäten für das Umgebungsmanagement auf sinnvolle und erweiterbare Art und Weise zusammen. Die Bean Definition Profiles ermöglichen es, Beans bestimmten Profilen zuzuweisen. Nur wenn die Profile aktiviert werden, werden auch die Bean-Definitionen geladen und die Beans erstellt. Eine PropertySource ist eine beliebige Quelle von Properties. Jedes Environment beinhaltet eine Hierarchie von PropertySources, die frei konfigurierbar und erweiterbar ist, aber sehr sinnvolle Defaults enthält. Zusammenfassend ist festzustellen, dass dieses Feature eine bisher vorhandene Lücke in der Konfiguration von Spring-Anwendungen mit der für Spring typischen Offenheit für Erweiterungen schließt.

Weiter geht’s in Kürze mit Teil 2. Darin behandeln wir dann das Thema Cache Abstraction. Stay tuned!

Tobias Flohre arbeitet als Senior Software Engineer beim IT-Dienstleistungs- und Beratungsunternehmen adesso AG. Seine Schwerpunkte sind Java-Enterprise-Anwendungen und Architekturen mit JE /Spring.
Pascal Czollmann ist als Senior Software Engineer bei dem IT-Dienstleistungs- und Beratungsunternehmen adesso AG beschäftigt. Er arbeitet dort seit mehreren Jahren in unterschiedlichen Kundenprojekten im Java-Enterprise-Umfeld.
Geschrieben von
Tobias Flohre und Pascal Czollmann
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.