Spring RCP: Konfiguration
Spring-RCP - Konfiguration
Einer der vielen Vorteile des Spring-Frameworks ist der deklarative Ansatz, der mit Inversion of Control (IoC), Dependency Injection (DI) und Aspektorientierter Programmierung (AOP) den eigentlichen Code sauber, lesbar und weitgehend frei von Abhängigkeiten hält. IoC im Zusammenspiel mit DI ist ein Entwurfsmuster, bei dem die eigentlichen Objekte nichts über ihre Abhängigkeiten "wissen" müssen, sondern die Kontrolle wird umgekehrt und die Abhängigkeiten von "außen", über das Framework, verwaltet und zur Laufzeit den entsprechenden Objekten mitgeteilt - sie werden sozusagen "injiziert". Da somit die Objekte nichts voneinander wissen müssen, ist es sehr einfach, z.B. einzelne Implementationen auszutauschen und die einzige Änderung am restlichen Programm besteht darin, eine einzige Zeile in einer Konfigurationsdatei anzupassen. AOP ist im Zusammenspiel mit den beiden anderen sehr nützlich, wenn es um die Isolierung von Prozessen geht, die unter Umständen mehrere Objekte betreffen, wie z.B Sicherheit und Zugangskontrolle oder auch Transaktionen. AOP sorgt z.B. bei Sicherheit und Zugangskontrolle mittels des Spring-Unterprojektes Acegi-Security dafür, daß der eigentliche Code nichts von Sicherheit oder Benutzern wissen muß. Die gesamte Sicherheit wird über Aspekte realisiert, welche in einer Konfigurationsdatei deklariert werden, ohne daß eine einzige Zeile Java dafür geändert werden müßte.
1. Die grundlegende Struktur einer Spring-Konfigurationsdatei
Spring wird mit XML-Dateien konfiguriert. Auf den ersten Blick mag das seltsam erscheinen, da eine oder gar mehrere zusätzliche Dateien zu verwalten und zu pflegen sind, doch der Vorteil besteht darin, daß man die gesamte Konfiguration entweder auf einen Blick hat oder auch logisch unterteilt, in z.B. die Persistenz-Konfiguration, die Modell-Konfiguraion, die Konfiguration der Service-Klassen, die Sicherheit... Sobald man einmal auf den Geschmack gekommen ist, die Konfigurationen logisch zusammenzufassen und gesammelt zu verwalten, will man es nicht mehr missen. Wenn man z.B. für Unit-Tests eine andere Datenbank verwenden will, als für die Integrationstests und wieder eine andere für die endgültige Version, muß man nicht jedesmal zehn Stellen im Code verändern, sondern lediglich die jeweiligen DAOs implementieren und ein anderes Konfigurationsfragment einbinden. Die Theorie zu dem Thema ist aber langweilig. Die tatsächlichen und gewaltigen Vorteile zeigen sich am überzeugendsten in der Praxis.
Die Spring-Konfigurationsdateien unserer Anwendung findet sich unter src/main/resources/ctx/richclient-startup-context.xml und src/main/resources/ctx/richclient-application-context.xml. Sehen wir uns zuerst die richclient-startup-context.xml an.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Define the splash screen to be displayed during application startup -->
<!-- The SplashScreen class requires the location of the image to display -->
<bean id="splashScreen"
class="org.springframework.richclient.application.splash.SimpleSplashScreen"
singleton="false">
<property name="imageResourcePath">
<value>/images/splash-screen.jpg</value>
</property>
</bean>
</beans>
Die ersten drei Zeilen sind in jeder Spring-Konfigurationsdatei gleich. Das Format der Dateien ist XML und über die URL http://www.springframework.org/dtd/spring-beans.dtd wird auf eine DTD-Datei verwiesen, welche die konkrete Struktur der Spring-Konfiguration beschreibt, was besonders dann nützlich ist, wenn die verwendete IDE automatische Codehilfen auch bei XML bietet. Durch <beans>...</beans> werden die einzelnen Beans, Java-Objekte mit einer bestimmten Struktur, eingeschlossen und dieses Paar steht ebenfalls in jeder Spring-Konfigurationsdatei. Jede einzelne Bean wird durch <bean>...</bean> deklariert. Die obige Datei enthält nur eine einzige Bean und anhand dieser kann man die allgemeine Struktur gut beschreiben:
Jede Bean im Spring-Kontext muß mit einer projektweit eindeutigen Bezeichnung (ID) versehen werden, im obigen Beispiel splashScreen. Mit dieser ID kann sie jederzeit aus dem von Spring verwalteten Kontext abgerufen werden. Wenn man aus Versehen einmal zwei Beans mit der gleichen ID deklarieren sollte, bricht Spring den Startvorgang mit einer Fehlermeldung ab. Die Bean mit der ID splashSreen hat aber noch weitere Attribute, nämlich
class="org.springframework.richclient.application.splash.SimpleSplashScreen"
und
singleton="false"
Mit dem class="..."-Attribut teilt man Spring mit, welche Klasse instanziiert werden soll, in diesem Fall die Klasse SimpleSplashScreen und mit singleton="false" teilt man Spring mit, daß es mehrere Instanzen dieser Klasse geben kann. Ohne weitere Angabe sind alle von Spring verwalteten Beans Singletons, existieren also nur ein einziges Mal im Kontext und werden gegebenenfalls wiederverwendet. Nachdem man dem Famework also mitgeteilt hat, welche Klasse instanziiert werden soll, kommt nun der interessante Teil, die Dependency-Injection. Mit property name="imageResourcePath" teilt man Spring mit, daß die Klasse SimpleSplashScreen eine öffentliche (public) Setter-Methode namens public void setImageResourcePath(String path){...} besitzt und das Spring diese Methode benutzen soll, um der erzeugten Instanz der Klasse SimpleSplashScreen einen Wert zu übergeben. Normalerweise ist es so, daß es auch eine Klasseneigenschaft (Variable) namens imageResourcePath gibt und die Setter-Methode ist normalerweise einfach public void setImageResourcePath(String path){imageResourcePath = path;}, aber das ist nicht zwingend. Im obigen Beispiel wird der Wert mit /images/splash-screen.jpg deklariert. Wenn wir kurz auf unser Projekt in Eclipse schauen, sehen wir, daß im Verzeichnis src/main/resources ein Unterverzeichnis images existiert und daß dieses tatsächlich eine Datei namens splash-screen.jpg enthält, nämlich den niedlichen Laubfrosch, der uns beim Start des Programmes anschaut. Man kann also sehr einfach auf Resourcen, die im Verzeichnis scr/main/resources gespeichert sind zugreifen.
2. Die Startup-Konfiguration
Da wir nun wissen, wie die Bean für den SplashScreen aufgebaut ist, wird es Zeit, ein wenig damit herumzuspielen. Ein SplashScreen mit einem Fortschrittsbalken ist viel interessanter, als ein stillstehendes Bild. Netterweise haben die Programmierer des Frameworks schon eine Implementierung dafür geschrieben, den PogressSplashScreen, der sich im gleichen Paket befindet, wie der SimpleSplashScreen. Wir ersetzen also in der richclient-startup-context.xml SimpleSplashScreen durch ProgressSplashScreen, so daß die Zeile so aussieht: class="org.springframework.richclient.application.splash.ProgressSplashScreen". Wenn wir das Projekt jetzt starten, erscheint unter dem Frosch ein Fortschrittsbalken - nett!
Der Balken sagt so aber noch nichts aus, also fügen wir der splashScreen-Bean noch eine boolean Eigenschaft hinzu:
<property name="showProgressLabel"> <value>true</value> </property>
Und voilá, schon zeigt der Balken auch an, was gerade geladen wird. Nun ist das bei unserer Anwenung bis jetzt noch sehr wenig, so daß der Fortschrittsbalken sehr schnell verschwindet, aber wenn einmal ein Fehler auftritt oder ein bestimmter Abschnitt des Starts sehr viel Zeit verbraucht, fällt es gleich hier auf, wo ungefähr das Problem liegt. Bei einer Anwendung für normale Nutzer würde man das ProgressLabel ausschalten, weil diese sich nicht wirklich für die geladenen Beans interessieren.
Damit sieht dann die gesamte richclient-startup-context.xml folgendermaßen aus:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Define the splash screen to be displayed during application startup -->
<!-- The SplashScreen class requires the location of the image to display -->
<bean id="splashScreen"
class="org.springframework.richclient.application.splash.ProgressSplashScreen"
singleton="false">
<property name="imageResourcePath">
<value>/images/splash-screen.jpg</value>
</property>
<property name="showProgressLabel">
<value>true</value>
</property>
</bean>
</beans>
3. Die Hauptkonfiguration
Die grundlegende Konfiguration unserer kleinen Beispielanwendung findet sich in src/main/resources/ctx/richclient-application-context.xml. Die schon vorkonfigurierten Beans sind sehr gut dokumentiert, aber einige Fragen bleiben dennoch offen, weshalb ich hier darauf eingehen will.
Die grundlegendste und wichtigste Bean ist natürlich die Anwendung bzw. Application selbst:
<bean id="application" class="org.springframework.richclient.application.Application"> <constructor-arg index="0" ref="applicationDescriptor"/> <constructor-arg index="1" ref="lifecycleAdvisor"/> </bean>
Hier sieht man eine andere Art von Dependency Injection, welche nicht über Setter-Methoden arbeitet, sondern Argumente an den Konstruktor der zu instanziierenden Klasse übergibt. In diesem Fall erwartet der Konstruktor der Klasse Application zwei Argumente, zuerst einen ApplicationDescriptor (Index 0) und danach einen LifecycleAdvisor (Index 1). Mit ref="beanId" verweist man auf eine anderswo deklarierte Bean mit der ID beanId.
Der ApplicationDescriptor:
<bean id="applicationDescriptor" class="org.springframework.richclient.application.support.DefaultApplicationDescriptor"> <property name="version" value="1.0"/> <property name="buildId" value="20060408-001"/> </bean>
Die version und buildId sind frei wählbar. Wenn wir version 0.1 und das heutige Datum einsetzen sieht sie z.B. so aus:
<bean id="applicationDescriptor" class="org.springframework.richclient.application.support.DefaultApplicationDescriptor"> <property name="version" value="0.1"/> <property name="buildId" value="notetsr-20070503-001"/> </bean>
Der LifecycleAdvisor ist folgerndermaßen deklariert:
<bean id="lifecycleAdvisor" class="de.pija.notestr.SimpleLifecycleAdvisor"> <property name="windowCommandBarDefinitions" value="ui/commands-context.xml"/> <property name="startingPageId" value="initialView"/> </bean>
Die Dokumentation empfiehlt die zusätzliche Eigenschaft eines Eventhandlers, dies ist jedoch inzwischen überholt, da das Eventhandling komplett überarbeitet wurde und nun viel feiner möglich ist. Für unser Projekt ist dies jedoch unerheblich.
Änderungen: Da unsere Anwendung nicht SimpleApp, sondern Notestr heißt, bennen wir die Klassen noch mit den in Eclipse eingebauten Refactorungs um. Also in Eclipse mit Refactor -> Rename bei jeweils markierter Klasse unsere SimpleApp in Notestr und den SimpleLifecycleAdvisor in NotestrLifecycleAdvisor umbenennen.
Wenn man gleich alle Vorkommen ersetzen und anpassen lassen will, oben den neuen Namen eingeben, alle Checkboxen im Refactoring Dialog ankreuzen, auf Next und dann Finish klicken. Beim Umbenennen der SimpleApp gibt es noch eine Warnung, die aber getrost ignoriert werden kann.
Nun muß man natürlich jetzt auch obige Bean anpassen:
<bean id="lifecycleAdvisor" class="de.pija.zettelkasten.NotestrLifecycleAdvisor"> <property name="windowCommandBarDefinitions" value="ui/commands-context.xml"/> <property name="startingPageId" value="initialView"/> </bean>
Und als letztes noch die messages_de.properties
applicationDescriptor.title=Notestr
Wie man sieht, ist die Änderung sehr einfach. Nun bitte einmal testweise die Anwendung mit dem neuen Namen starten.
Großartig.
Um der Anwendung noch etwas Pfiff zu geben, noch einige Änderungen an folgender Bean:
<bean id="applicationServices"
class="org.springframework.richclient.application.support.DefaultApplicationServices">
<property name="imageSourceId">
<idref bean="imageSource" />
</property>
<property name="rulesSourceId">
<idref bean="rulesSource" />
</property>
<property name="formComponentInterceptorFactoryId">
<idref bean="formComponentInterceptorFactory"/>
</property>
<property name="applicationObjectConfigurerId">
<idref bean="applicationObjectConfigurer"/>
</property>
<property name="binderSelectionStrategyId">
<idref bean="binderSelectionStrategy"/>
</property>
</bean>
Damit erweitern wir die Anwendnung noch um einige, schon im Paket enthaltene, zusätzliche Fähigkeiten. Zuletzt fügen wir noch folgende drei Beans ein, die wir in applicationServices referenzieren:
<bean id="binderSelectionStrategy"
class="org.springframework.richclient.form.binding.swing.SwingBinderSelectionStrategy">
<property name="bindersForPropertyTypes">
<map>
<entry>
<key>
<value type="java.lang.Class">java.util.Date</value>
</key>
<bean
class="org.springframework.richclient.form.binding.swing.NachoCalendarDateFieldBinder"/>
</entry>
</map>
</property>
</bean>
<bean id="rulesSource"
class="de.pija.notestr.NotestrValidationRuleSource"/>
Zuallerletzt muß jetzt noch eine Klasse erstellt werden. NotestrValidationRuleSource, die in der letzten Bean instanziiert werden soll und vorläufig nur einen Rumpf darstellt.
package de.pija.zettelkasten;
import org.springframework.rules.Rules;
import org.springframework.rules.support.DefaultRulesSource;
public class NotestrValidationRuleSource extends DefaultRulesSource
{
public NotestrValidationRuleSource()
{
super();
addRules(createNoteRules());
}
private Rules createNoteRules()
{
return new Rules();
}
}
Achtung! Wenn man das Programm nun starten will, bricht der Startvorgang mit der Meldung ab, daß die Klasse Nachocalendar nicht gefunden werden kann. Woran das liegt und wie man diesen Fehler behebt ist Gegenstand des nächsten Abschnitts.
4. Konfiguration der Projektabhängigkeiten mit Maven
Am Ende des Abschnitts 3 scheitert der Start der Anwendung an einer fehlenden Klassenbibliothek, Nachocalendar. Wir könnten diese nun im Netz suchen, herunterladen und von Hand in den Classpath des Projektes einbinden, aber da wir das Projekt schon mit Maven erstellt haben, sollten wir bei der Konfiguration mittels Maven bleiben.
Wenn wir im Projektverzeichnis mvn install eingeben, startet Maven und sucht im aktuellen Verzeichnis nach einer Datei namens pom.xml. In dieser Datei stehen alle Daten, die Maven zur Erstellung unseres Projektes braucht. Da dies kein Tutorial zu Maven werden soll, kürzen wir das ganze ab und gehen direkt zum Abschnitt, der uns interessiert, den dependencies. Dependencies, also Abhängigkeiten sind die Klassenbibliotheken die für den Start und das Funktionieren einer Anwendung nötig sind, von denen sie also abhängt. Eine ganze Reihe dieser Bibliotheken ist dort schon eingetragen, nämlich Spring Rich selbst, dann Spring und einige Komponenten. Nachocalendar ist allerdings nicht dabei.
Um diese Bibliothek dem Projet zur Verfügung zu stellen fügen wir folgendes am Ende des Abschnitts <!-- Components --> ein:
<dependency> <groupId>net.sf.nachocalendar</groupId> <artifactId>nachocalendar</artifactId> <version>0.23</version> </dependency>
Als wir die Spring-Rich Bibliotheken erstellt haben, wurde auch der Nachocalendar schon heruntergeladen und in unserem lokalen Maven-Repository installiert.
Da wir schon einmal an der Konfiguration arbeiten, sollten wir auch gleich noch die Datenbank und Hibernate einbinden, indem wir den folgenden Text ans Ende der Datei kopieren.
<!-- Database --> <!-- Derby --> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.2.2.0</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbyclient</artifactId> <version>10.2.2.0</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbyLocale_de_DE</artifactId> <version>10.2.2.0</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbytools</artifactId> <version>10.2.2.0</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbynet</artifactId> <version>10.2.2.0</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.2.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.2.1.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.2.1.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-tools</artifactId> <version>3.2.0.beta9a</version> </dependency>
Nun müssen wir die neuen Abhängigkeiten noch im Projekt einbinden. Also wieder in einer Eingabeaufforderung in das Projektverzeichnis wechseln und dort
mvn clean install
gefolgt von
mvn eclipse:eclipse -DdownloadSources=true
eingeben. Das clean ist wichtig, da es ansonsten zu Konflikten mit schon erstellten Klassen kommt und der Build-Process mit einer Fehlermeldung abbricht.
- Anmelden oder Registrieren um Kommentare zu schreiben











