D) Dependency Injection mit Spring
Scopes
Ein Objektnetz zeichnet sich neben dem Typ seiner Komponenten auch durch die Kardinalität der enthaltenen Objekte aus. Auf gut Deutsch heißt das, dass Spring eine Möglichkeit braucht, herauszufinden, ob immer das selbe Objekt benutzt werden soll oder doch lieber immer ein Neues.
Die Standardlösung für dieses Problem ist es, immer dasselbe Objekt zu benutzen. Diese Strategie ist jedoch nicht immer ausreichend, etwa, wenn man mehrere Objekte einer Klasse benötigt, diese Objekte aber nicht alle einzeln deklarieren kann oder will. Im Beispielcode muss beispielsweise jedes Auto mit vier unabhängigen Rädern ausgestattet werden, da diese über eine Instanzvariable verfügen, mit deren Hilfe die zurückgelegte Wegstrecke gemessen wird.
Die Instanziierungsstrategie, die Spring für eine Bean benutzt, wird durch den „Scope“ der Bean definiert. Spring bringt eine Vielzahl verschiedener Scope-Implementierungen mit, für Anwendungen außerhalb des Webbereichs sind vor allem der singleton
und der prototype
-Scope interessant.
Der Singleton-Scope ist die Standardeinstellung, die dafür sorgt, dass pro ApplicationContext
nur eine Instanz einer deklarierten Bean vorhanden ist. Dagegen wird im Prototype-Scope für jede Referenz auf eine Bean eine eigene Instanz erzeugt. Die Felgen im Beispiel besitzen keine Instanzvariablen und dürfen daher ruhig im Singleton-Scope verbleiben. Die Räder hingegen müssen mit dem Attribut scope
als Prototypen gekennzeichnet werden:
<bean id="aluFelge" class="de.jbb.springtut.auto.AluFelge" /> <bean id="bmwRad" class="de.jbb.springtut.auto.PkwRad" scope="prototype"> <constructor−arg ref="aluFelge" /> </bean>
Die Auswirkungen dieser simplen Änderung im XML können mit folgendem Testprogramm gut nachvollzogen werden:
ApplicationContext context = new ClassPathXmlApplicationContext("de/jbb/springtut/xml/bsp2Scopes.xml") ; PkwRad bmwRad = context.getBean("bmwRad", PkwRad.class); PkwRad bmwRad2 = context.getBean("bmwRad" , PkwRad.class); System.out.println(bmwRad); System.out.println(bmwRad2); System.out.println("Räder gleich ?" + (bmwRad == bmwRad2)); System.out.println("Felgen gleich ?" + (bmwRad . getFelge( ) == bmwRad2.getFelge()));
Die Ausgabe ergibt erwartungsgemäß, dass die beiden Räder zwei verschiedene Objekte sind, die Felgen dagegen ein und dasselbe.
Integration von Factories
Zwar braucht Spring-basierter Code dank Dependency Injection keine Factories mehr, doch manchmal kann man nicht auf sie verzichten – etwa wenn man Code verwenden will, der nicht mit Blick auf Spring erstellt wurde. Factories kommen im Wesentlichen in zwei verschiedenen Ausprägungen vor: In Form statischer Klassenmethoden oder in Form von dezidierten Klassen, deren Instanzen zur Erzeugung von anderen Objekten dienen.
Als Beispiel für beide Factory-Arten wollen wir zur Produktion von Autos eine Fabrik benutzen, statt selbst zu schrauben. Die Fabriken für eine bestimmte Marke werden durch eine statische Klassenmethode geliefert. Anschließend kann man an der erhaltenen Fabrikinstanz einen Autotyp anfordern, indem man eine Autokonfiguration an eine Produktionsmethode übergibt. Eine Autokonfiguration ist eine Art Bauplan für das gewünschte Auto. Doch betrachten wir zunächst, wie man eine Fabrik für eine bestimmte Marke erhält:
<bean id="autoFabrik" class="de.tobiasdemuth.springtut.auto.fabrik.AutoFabrik" factory-method="getFabrik"> <constructor-arg value="VW" /> </bean>
Als class
gibt man die Klasse mit der statischen Factory-Methode an. Der Name der Methode wird im Attribut factory-method
gesetzt. Eventuelle Parameter werden als Konstruktorargumente übergeben. Unter der Id autoFabrik
kann nun der Rückgabewert der angesprochenen statischen Methode abgerufen werden – in diesem Fall eine VWFactory
.
Nun kann man diese Fabrik dazu benutzen, um ein Auto herzustellen. Dazu brauchen wir einen Bauplan (eine Instanz von AutoKonfiguration), dessen Konstruktion aber mit den Ausführungen der vorhergehenden Abschnitte leicht zu verstehen ist und daher hier nicht näher erläutert werden soll. Der Bauplan erhält die Id spezielleKonfiguration
.
<bean id="jbbSpezialtrecker" factory−bean="autoFabrik" factory−method="produziere"> <constructor−argref="spezielleKonfiguration" /> </bean>
Die Konstruktion des Autos mit einer Factory erfolgt ähnlich der Erzeugung der Fabrik selbst, nur dass jetzt eine Instanzmethode aufgerufen wird. Deshalb geben wir nicht eine Klasse, sondern die Id der konkreten zu nutzenden Instanz im Attribut factory-bean
an. Der Name der aufzurufenden Methode steht wieder im Attribut factory-method
, eventuell zu übergebende Argumente werden als Konstruktorparameter übergeben.
Ein Aufruf von getBean("jbbSpezialtrecker", Auto.class)
an einem ApplicationContext
mit dieser Konfiguration gibt ein Auto zurück, dass durch den Aufruf der Methode produziere()
an der durch getBean("autoFabrik", AutoFabrik.class)
zu erhaltenden Instanz erzeugt wird.
Lifecycle-Methoden
Nachdem nun alle Autos durch eine Fabrik zentral hergestellt werden, möchte das Management regelmäßig Berichte über die produzierten Fahrzeuge bekommen. Dafür wird vor Start der Produktion eine Map angelegt, in der für jeden übergebenen Bauplan einzeln die Anzahl der davon gebauten Fahrzeuge festgehalten wird. Wenn die Fabrik dann heruntergefahren wird, soll dieser Bericht auf der Konsole ausgegeben werden.
Die beiden Ereignisse „Vor Beginn der Produktion“ und „Beim Herunterfahren der Fabrik“ liegen jedoch außerhalb des Einflussbereichs der Fabrik selbst. Es wird also ein Weg benötigt, wie Spring die Fabrik über das Auftreten dieser Events benachrichtigen kann. Zu diesem Zweck gibt es die Lifecycle-Methoden.
Die Initialisierung des Produktionszählers soll in einer Methode initialisiereFabrik()
stattfinden, die Ausgabe des Produktionsberichts in schreibeProduktionsBericht()
. Übergibt man nun die Namen dieser Methoden in den Attributen init-method
bzw destroy-method
, so weiß Spring, dass diese Methoden unmittelbar nach der Erzeugung der Instanz bzw beim Herunterfahren des ApplicationContext
-Objekts aufgerufen werden sollen.
<bean id="autoFabrik" class="de.jbb.springtut.auto.fabrik.AutoFabrik" factory−method="getFabrik" init−method="initialisiereFabrik" destroy−method="schreibeProduktionsBericht"> <constructor−arg value="VW" /> </bean>
Natürlich funktioniert dieser Mechanismus auch mit Beans, die ganz normal per Konstruktor erzeugt werden. In Nicht-Webanwendungen muss allerdings noch eine Vorkehrung getroffen werden, um Spring selbst über das Abschalten der JVM zu informieren. Wenn man das versäumt, werden die destroy
-Methoden nicht aufgerufen!
AbstractApplicationContextcontext = new ...; context.registerShutdownHook();
Die Methode registerShutdownHook()
stellt sicher, das der ApplicationContext
davon erfährt, wenn die JVM abgeschaltet wird. Die Methode gehört zur Klasse AbstractApplicationContext
, so dass man diese anstatt des bisher benutzten Interfaces als Typ angeben muss. Das ist aber kein Problem, weil die von Spring mitgelieferten ApplicationContext
– Implementierungen sowieso alle von AbstractApplicationContext
abgeleitet sind.
Hilfsmittel
Innere Beans
Mit den vier bis hierher behandelten Konfigurationselementen lassen sich die allermeisten Aufgaben bei der Erzeugung eines Objektnetzes schon ganz gut lösen. In diesem Abschnitt soll es um ein paar Elemente und Konstrukte gehen, die besondere Fälle abdecken oder einfach dabei helfen, die Konfigurationsarbeit angenehmer zu gestalten.
Ein wesentlicher Fortschritt in Sachen Schreibkomfort stellt zum Beispiel die Konstruktion der Inner Beans dar. Durch sie ist es möglich, Beans, die nur einmal gebraucht werden, direkt innerhalb des Konstruktorarguments bzw Properties zu deklarieren. Da Spring sich diese Beans nicht merkt, sondern nur einmalig eine Instanz davon anlegt, sind innere Beans allerdings immer im Scope Prototype
!
<bean id="treckerRad" class="de.jbb.springtut.auto.TreckerRad"> <constructor−arg> <bean class="de.jbb.springtut.auto.AluFelge" /> </constructor−arg> </bean>
Für jedes Rad, das seine Felge wie oben deklariert, wird also eine eigene Felgeninstanz geschaffen, auch wenn der Scope
nicht ausdrücklich aufgeschrieben wurde.
Collections
Der Konstruktor des Autos nimmt eine Liste von Rad-Instanzen entgegen. Diese kann am schnellsten mit Hilfe des list
-Elements erzeugt werden:
<bean id="vwRad" class=" de.jbb.springtut.auto.PkwRad" scope="prototype"> [...] </ bean> <bean id="simplesAuto" class="de.jbb.springtut.auto.Auto"> <constructor−arg value="VW" /> <constructor−arg ref="viertakter" /> <constructor−arg> <list> <ref bean="vwRad" /> <ref bean="vwRad" / <ref bean="vwRad" /> <ref bean="vwRad" /> </list> </constructor−arg> </bean>
Durch das in das constructor-arg-Tag geschachtelte List
-Element wird an dieser Stelle eine Liste mit vier unterschiedlichen (Prototype-Scope
) Rädern übergeben. Ähnliche Konstruktionen gibt es auch für Maps
, Sets
und Properties
:
<bean id="listeMitPrimitivenWerten" class="..."> <constructor−arg> <list> <value>primitiverWert</value> </list> </constructor−arg> </bean> <bean id="map" class="..."> <constructor−arg> <map> <entry key="key1" value="primitiv" /> <entry key="key2" value−ref="referenz" /> </map> </constructor−arg> </bean> <bean id="set" class="..."> <property> <set> <ref bean="referenz" /> <value>primitiv</value> </set> </property> </bean> <bean id="properties" class="..."> <property> <props> <prop key="key1"> primitiverWert </prop> <prop key="key2"> Referenzen nicht erlaubt </prop> </props> </property> </bean>
Konstanten
Viele Klassen kapseln „magische“ Werte in Konstanten. Magische Werte sind feststehende Bezeichner (meist Strings oder Integer), die einen bestimmten Sachverhalt ausdrücken. Die Klasse BufferedImage kapselt zum Beispiel den Typ des repräsentierten Bildes in Konstanten wie TYPE_INT_RGB
oder TYPE_CUSTOM
. Es ist guter Stil nicht die Klarwerte (also den tatsächlichen, durch die Konstante gekapselten Integer) sondern die entsprechenden Konstanten zu benutzen.
In Spring ist das mit Bordmitteln etwas schwierig und erfordert näheres Wissen über die Interna des Frameworks als bis hierher vorhanden. Glücklicherweise gibt es den util
-Namensraum, der ein XML-Element mitbringt, mit dem die Absicht, eine Konstante zu benutzen, ausgedrückt werden kann. Dazu muß der Namensraum zunächst deklariert werden:
<?xml version="1.0" encoding="UTF−8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema−instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring−beans−3.0.xsd http://http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring−util−3.0.xsd"> <!−−Beandefinitionen hier−−> </beans>
Gegenüber der schon bekannten Deklaration des Rootelements ist die Zeile 4 und die zusätzliche Schema-Location in Zeile 6 hinzugekommen. Nun können die Elemente des util
-Namensraums benutzt werden. Der Zugriff auf Konstanten bzw. genauer als static final
deklarierte Eigenschaften ist dadurch mit util:constant
möglich:
<bean class="de.jbb.springtut.auto.Farbe"> <constructor−arg value="blau" /> <constructor−arg> <util:constant static−field="java.awt.Color.BLUE" /> </constructor−arg> </bean>
Für JDK5-Enums ist obiger Weg übrigens unnötig. Diese können einfach wie primitive Werte direkt übergeben werden.
Vererbung
Wenn in einer Konfiguration viele gleichartige Beans vorkommen, findet man sich schnell in der Situation wieder, dass man per Copy&Paste Teile der Konfiguration dupliziert. Damit handelt man sich dieselben Probleme wie beim „normalen“ Programmieren ein: Soll ein Wert einmal geändert werden, muß man ihn an dutzenden Stellen in der Konfiguration suchen und anpassen – und meistens erwischt man doch nicht alle auf Anhieb. In dieser Situation bietet sich die Verwendung von Konfigurationstemplates an. Das sind Bean-Deklarationen, die nur Werte enthalten, selbst aber nicht instanziiert werden. Dadurch kann man bei vielen gleichartigen Beans an einer Stelle die Grundkonfiguration hinschreiben und an den anderen Stellen nur noch die Unterschiede zur Basis notieren. Als Beispiel möchten wir ein kleines Programm betrachten, dass die Beschleunigung eines Zweitakter-Motors mit der eines Viertakters vergleicht. Der Java-Code ist trivial, er besteht nur aus der bekannten Instanziierung der Beans über den ApplicationContext und ein paar Ausgaben und soll daher hier nicht weiter betrachtet werden – er steht aber natürlich im Beispielcode zur Verfügung. Die Springkonfiguration wird mit Hilfe eines Templates aufgebaut, denn Zwei- und Viertakter sollen natürlich mit den selben Werten für Hubraum und PS-Zahl initialisiert werden. Diese Werte sollen an einer Stelle gesammelt werden, damit man nicht zuviel tippen muss und sie später leicht ändern kann.
<bean id="motor" abstract="true"> <constructor−arg value="100" /> <constructor−arg value="100" /> </bean> <bean id="viertakter" parent="motor" class="de.jbb.springtut.auto.Viertakter" scope="prototype" /> <bean id="zweitakter" parent="motor" class="de.jbb.springtut.auto.Zweitakter" scope="prototype" />
Die Bean motor
ist das Template für den Vier- und den Zweitakter. Es enthält die für die beiden abgeleiteten Beans viertakter
und zweitakter
identischen Inhalte und ist als abstract
gekennzeichnet. Die Kennzeichnung als abstract
ist notwendig, da das Template kein class
-Attribut enthält und somit nicht instanziiert werden kann. Die beiden Kind-Beans verweisen durch das Attribut parent
auf das Template und notieren die Unterschiede zu diesem – in diesem Fall handelt es sich dabei nur um das ergänzte class
-Attribut und den Scope. Der Scope gehört zu einer kleinen Gruppe von spezielleren Attributen, die nicht über ein Template ausgelagert werden können.
Wollte man in einer der Kind-Beans eines der vorgegebenen Konstruktorargumente überschreiben (etwa um im anstehenden Vergleich zu mogeln), so kann man das sehr leicht tun:
<bean id="motor" abstract="true"> <constructor−arg index="0" value="100" /> <constructor−arg index="1" value="100" /> </bean> <bean id="zweitakter" parent="motor" class="de.jbb.springtut.auto.Zweitakter" scope="prototype"> <constructor−arg index="0" value="200" /> </bean>
In obiger Deklaration überschreibt die Kind-Bean zweitakter
das erste Konstruktorargument mit dem Wert 200. Das neue Attribut index
der Konstruktorargumente steuert die Reihenfolge in der die Argumente dem Konstruktor übergeben werden. Es ist hier notwendig, da Spring Änderungen der Kinddefinition gegenüber dem Template grundsätzlich als Ergänzung zur Elterndefinition auffasst und deshalb hier einen Zweitakter-Konstruktor mit drei (!) Parameter suchen würde. Über den gleichen vergebenen Index (bei Properties über den gleichen Property-Namen) kann Spring feststellen, dass es sich in Wahrheit um ein Überschreiben eines Wertes aus der Elterndefinition handelt.
Der Index der Konstruktorargumente ist immer nullbasiert.
Dieser Artikel wurde uns von Tobias Demuth zur Verfügung gestellt. Tobias Demuth ist Dipl.-Wirtschaftsinformatiker (FH) und arbeitet als Anwendungsentwickler bei der Viessmann IT Service GmbH in Nordhessen.
„Integration von Factories“ kurz daruter ist der Quellcode verschwunden da steht nur ne Leerezeile mit Zeilennummer 1
Hallo Marcel,
stimmt. Da ist irgendwie, irgendwo was schief gelaufen. Ich werde das asap ausbessern. Kann aber noch ein bisschen dauern, da ich zuerst den Originalartikel von Tobias Demuth raussuchen muss.
Grüße
Stefan
Transitiv hat übrigens nichts mit Transistor zu tun.
Danke für den Hinweis. Habe ich ausgebessert.
Gruß
Stefan
Seite 2: Beim ersten Beispiel fehlt die XML-Formatierung.