Features: Dependency Injection


... [ Seminar BS, WWW und PS ] ... [ Thema Spring Framework ] ... [ Aspektorientierte Programmierung ] ...

Features: Dependency Injection


Konzept

Inversion of Control (IoC) ist ein Umsetzungsparadigma das beschreibt, wie der Kontrollfluss in einer Anwendung umgekehrt wird. Die Objekte haben dabei nicht mehr selber die Kontrolle über Ihre Abhängigkeiten, sondern werden von einer anderen Instanz kontrolliert. Dieses Konzept wird auch als Hollywood-Prinzip ("Don't call us, we call you") bezeichnet. Es gibt zwei Umsetzungen dieses Konzepts: Dependency Lookup und Dependency Injection. Bei Dependency Lookup suchen sich die Objekte ihre Abhängigkeiten über einen ihnen zur Verfügung gestellten Dienst. Da den Objekten dieser Dienst allerdings bekannt sein muss, damit sie ihn ansprechen können, bleiben sie von diesem Dienst abhängig. Die andere Umsetzung ist Dependency Injection bei der die Objekte ihre Abhängigkeiten von einer anderen Instanz zugewiesen (injeziert) bekommen. Das Spring Framework benutzt Dependency Injection, da die Objekte hierbei nichts von der anderen Instanz wissen und von Spring unabhängig bleiben. Die Rolle der zuweisenden Instanz übernimmt dabei der Container. Der Container baut so gemäß seiner Konfiguration ein Objektnetz auf und übernimmt auch die Kontrolle über den Lebenszyklus der Objekte. Die Konfiguration des Containers geschieht in Spring mit Hilfe einer XML-Datei. Für die Erzeugung eines Containers in Spring und die Möglichkeit auf die Objekte in diesem Container zuzugreifen gibt es verschiedene Möglichkeiten. Für standalone-Anwendungen gibt es die BeanFactory und den ApplicationContext, in einem Webumfeld übernimmt dies ein von Spring zur Verfügung gestelltes DispatcherServlet. Die BeanFactory ist dabei die schmalere Implementierung und stellt nur den Container zur Verfügung, während im ApplicationContext weitere Features wie Aspektorientierte Programmierung möglich werden. Der Dispatcher für das Webumfeld ähnelt dabei dem ApplicationContext. Allgemein sollte solange wie möglich die BeanFactory genutzt werden und erst wenn diese nicht mehr ausreicht auf den ApplicationContext zurückgegriffen werden. In der Spring-Terminolgie heißen die von einem Spring-Container verwalteten Objekte Beans.

Konfiguration: Erzeugung von Beans

Bei der klassischen Programmierung erzeugen Objekte ihre Abhängigkeiten zu anderen Objekten durch Defaultkonstruktoren, gegebenenfalls mit späterem Aufruf von setter-Methoden, parametrisierten Kontruktoren oder Factory-Methoden. Spring unterstützt jede dieser Methoden, so dass es auch möglich vorhandene Objekte mit Spring verwalten zu lassen. Die EJBs bieten eine solche Möglichkeit nicht.
Eine XML-Konfigurationsdatei für Spring beginnt mit einem XML-Header, der den Dokumententyp festlegt und die Validierung ermöglicht:
<:?xml version="1.0" encoding="UFT-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
 "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
...
</beans>
Die Konfiguration der einzelnen Beans erfolgt in dem beans-Tag.
Eine Bean, die über einen Defaultkonstruktor sowie setter-Methoden verfügt erfolgt folgendermaßen:
<bean id="dataSource" class="...ClientDataSource">
  <property name="serverName" value="localhost">
  <property name="portNumber" value="1527">
</bean>
Hierdurch wird eine Bean von der Klasse ClientDataSource per Defaultkonstruktor erzeugt und danach die Methoden setServerName und SetPortNumber ausgeführt. Den Datentyp in den der value-Wert gewandelt werden muss erkennt Spring dabei am Typ der Felder.
Wenn eine Bean eine Referenz auf eine andere Bean benötigt, kann diese folgendermaßen gesetzt werden:
<bean id="kundeDAO" class="KundeDAO">
  <property name="dataSource" ref="dataSource">
</bean>
In diesem Fall würde die dataSource wie oben beschrieben erzeugt werden und der Bean kundeDAO per setDataSource zugewiesen. Die Typ-Überprüfung passiert hierbei erst bei der Erzeugung des Containers, also zur Lauf- und nicht zur Kompilierzeit.
Falls die referenzierte Bean nur in diesem einen Kontext benötigt wird, ist es auch möglich sie inline zu definieren:
<bean id="kundeDAO" class="KundeDAO">
  <property name="dataSource">
    <bean class="...ClientDataSource">
      <property name="serverName" value="localhost">
      <property name="portNumber" value="1527">
    </bean>
  </property>
</bean>
Da die innere Bean dabei keinen Namen bekommt, kann sie von keiner anderen Bean referenziert werden und ihr Lebenszyklus entspricht dem der äußeren Bean.
Die nächste Möglichkeit Objekte zu erzeugen ist über einen parametrisierten Konstruktor. Dies ähnelt sehr dem Erzeugen per Default-Konstruktor:
<bean id="dataSource" class="...ClientDataSource">
  <constructor-arg value="localhost">
  <constructor-arg value="1527">
</bean>
<bean id="kundeDAO" class="KundeDAO">
  <constructor-arg ref="dataSource">
</bean>
Spring untersucht in diesem Fall die Parameter des Konstruktors und probiert die gegebenen Werte umzusetzen. In diesem Fall würde "localhost" als String, "1527" als int und dataSource als ClientDataSource an die Konstruktoren übergegeben werden. Dies passiert ebenfalls zur Laufzeit. Auch bei Konstruktoren ist es möglich Beans die vom Konstruktor referenziert werden inline anzugeben.
In diesem Fall kann es zu verschiedenen Problemen kommen. Zum Beispiel wenn der Konstruktor einen String und einen Integer erwartet, als value aber in beiden Fällen Zahlen angegeben werden. Spring ist dadurch nicht mehr in der Lage zu erkennen welcher Wert an welche Stelle des Konstruktors gehört. Um dies beheben zu können gibt es zwei Möglichkeiten.
Die erste ist es den Typ des Wertes anzugeben:
<bean id="dataSource" class="...ClientDataSource">
  <constructor-arg type="String" value="localhost">
  <constructor-arg type="int" value="1527">
</bean>
Hier werden die Typen der Werte mit angegeben und Spring weiss so in welchen Typ er die Argumente umwandeln soll. Diese Lösung funktioniert allerdings nur, wenn ein Konstruktor jeden Typ nur einmal erwartet. Erwartet der Konstruktor zwei Strings bietet Spring als zweite Möglichkeit folgende Lösung:
<bean id="dataSource" class="...ClientDataSource">
  <constructor-arg index="0" value="localhost">
  <constructor-arg index="1" value="1527">
</bean>
Hier werden Spring die Positionen angegeben, an denen es die Werte dem Konstruktor übergeben soll. Hier ist darauf zu achten, dass das erste Argument den Index 0 hat. Spring versucht dann bei der Erzeugung der Bean zur Laufzeit den ersten Wert auf den vom Konstruktor als erstes Argument erwarteten Typ zu wandeln.
Die letzte Möglichkeit Objekte zu erzeugen ist mittels Factory-Methoden. Hier gibt es zwei Möglichkeiten. Erstens die Erzeugung mit einer statischen Factory-Methode in einer Klasse:
<bean id="exampleBean" class="ExampleBean2" factory-method="createInstance"/>
In diesem Fall wird Spring in der Klasse ExampleBean2 die Methode createInstance aufrufen und das zurückgegebene Objekt als Bean exampleBean behandeln.
Die zweite Möglichkeit ist es die Bean über eine Instanz-Factory zu erzeugen:
<bean id="myFactoryBean" class="MyFactory"/>
<bean id="exampleBean" factory-bean="myFactoryBean" factory-method="createInstance"/>
Spring erstellt hier zuerst die Bean myFactoryBean von der Klasse MyFactory und wird danach auf der Bean myFactoryBean die Methode createInstance aufrufen. Das zurückgegebene Objekt dieses Aufrufes wird dann als Bean exampleBean gespeichert.
Wenn die Factory-Methode Parameter erwartet, können diese wie bei einem Konstruktor über <constructor-arg ...> gesetzt werden.

Konfiguration: Collections übergeben

Es ist möglich Collections als Parameter für setter-Methoden, Konstruktoren oder Factories zu übergeben. Möglich sind dabei Lists, Sets, Maps und Propertys:
<bean id="myList" class="MyList">
  <property name="list">
    <list>
      <value>Erster Listeneintrag</value>
      <value>Zweiter Listeneintrag</value>
    </list>
  </property>
</bean>
<bean id="myMap" class="MyMap">
  <constructor-arg>
    <map>
      <entry key="1" value="Hallo"/>
      <entry key="2" value="Welt"/>
    </map>
  </constructor-arg>
</bean>
Für Sets und Propertys sind die Tags entsprechend <set> und <props>

Konfiguration: Autowiring

Spring bietet die Möglichkeit die Konfiguration der Abhängigkeiten zu vereinfachen. Bei dem sogenannten Autowiring versucht Spring benötigte Referenzen auf andere Beans herauszufinden und zu setzen. Autowiring wird folgendermaßen aktiviert: <bean id="kundeDAO" class="KundeDAO" autowire="byName"/> Das Attribut autowire kann dabei fünf verschiedene Werte annehmen:
no kein Autowiring (Default)
byName Bean mit dem gleichen Namen wie das Feld im Objekt wird gesucht. Gibt es keine Bean mit diesem Namen bleibt dass Feld unbelegt.
byType Bean mit dem gleichem Typ wie das Feld im Objekt wird gesucht und entsprechende Setter-Methode ausgeführt. Ist keine Bean dieses Typs vorhanden wird das Feld nicht gesetzt, sind mehrere Beans dieses Typs vorhanden wird zur Laufzeit ein Fehler ausgegeben.
constructor Wie byType, allerdings wird hier in den Parametern des Konstuktor gesucht. Wenn kein passender Typ gefunden wird probiert Spring einen anderen Konstruktor auszuführen, wenn dieser nicht vorhanden ist oder mehrere Beans des gesuchten Typs vorhanden sind erfolgt zur Laufzeit eine Fehlerausgabe.
autodetect Mischung aus byType und constructor. Spring versucht zuerst den Default-Konstruktor aufzurufen und die Felder mit Setter-Methoden zu füllen. Ist dies nicht möglich, da kein Default-Konstruktor zur Verfügung steht, wird versucht die Argumente des Konstruktors mit den wenigsten Argumenten zu finden und eventuell übrige Felder per Setter zu setzen.

Konfiguration: Init- und Destory-Methoden

Falls es nötig ist eine Bean nach ihrer Erzeugung noch zu initialisieren oder vor ihrer Zerstörung noch Aktionen vorzunehmen, bietet Spring die Möglichkeit init- und destroy-Methoden festzulegen.
<bean id="kundeDAO" class="KundeDAO" init-method="init" destroy-method="cleanup"/>
Die zugehörige Klasse würde wie folgt aussehen:
public class KundeDAO {
  public void init() {..}
  public void destroy() {..}
}
Beide Attribute sind optional und auch einzeln einsetzbar. Die init- und destroy-Methoden werden dann von Spring an den Stellen 6 (init) bzw. 9 (destroy) der folgenden Grafik ausgeführt.

Lebenszyklus einer Bean

Anhand der Grafik lässt sich der Lebenszyklus einer Bean mit Standard-Konstruktor und Setter-Methoden beschreiben:
  1. Anhand der Definition wird der Default-Konstruktor aufgerufen, die Bean gilt nun als Pre-Initalized.
  2. Falls Autowiring aktiviert ist, sucht Spring nach passenden Beans.
  3. Es wird überprüft ob alle Abhängigkeiten zur Verfügung stehen und vom Typ her passen.
  4. Die Eigenschaften der Beans werden über die Setter-Methoden gesetzt.
  5. Falls die Bean das Interface InitializingBean implementiert wird die Methode afterPropertiesSet ausgeführt. Dies war bis Version 2.0 die Vorgehensweise um Beans zu initialisieren. Allerdings sind die Beans dadurch von dem Interfacce abhängig. Um die Beans unabhängig von Spring zu halten, sollte daher das init-method-Attribut verwendet werden.
  6. Die im init-method-Attribut definierte Methode wird aufgerufen.
  7. Die erzeugte Bean wird der BeanFactory zugewiesen und gilt als Ready.
  8. Falls die Bean das Interface DisposableBean implementiert wird die Methode destroy aufgerufen. Dies ist wie bei 5. für die Versionen von 2.0 vorgesehen und sollte aus den gleichen Gründen mit Version 2.0 nicht mehr benutzt werden.
  9. Die im destroy-method-Attribut definierte Methode wird aufgerufen. Danach gilt die Bean als Destroyed.

Konfiguration: Prototype

Wenn Spring eine Bean erzeugt und diese einer anderen als Eigenschaft zuweist wird Spring die Beans immer wie statische Objekte behandeln. Es wird also nur eine Instanz erzeugt und diese an jeder Stelle wo die Bean referenziert wird verwendet. Diese Beans heißen singleton. Das Vorgehen bei singleton-Beans ist in der folgenden Grafik gut zu erkennen:

singelton-Bean

Alternativ besteht die Möglichkeit bei jeder Referenz auf die Bean eine neue Instanz zu erzeugen. Dieses Vorgehen heisst prototype und lässt sich in der folgenden Grafik erkennen:

Prototype-Bean

Ob eine Bean als singleton oder prototype behandelt werden soll gibt das Attribut scope des bean-Tags an. Hier gibt es folgende Werte:
singleton Bean wird nur einmal erzeugt und bei jeder Referenz die gleiche Bean verwendet. (Default)
prototype Bei jeder Referenz wird eine neue Instanz der Bean erzeugt und verwendet.
request Wird nur im Web-Kontext benötigt. Für jeden Request wird eine neue Instanz erzeugt.
session Wird nur im Web-Kontext benötigt. Für jede Session wird eine neue Instanz erzeugt.
globalSession Wird nur für portlet-based Webanwendungen benötigt. Für jede Global Portlet Session wird eine Bean erzeugt.
Bis Version 2.0 gab es das scope-Attribut noch nicht. Bei der Verwendung von früheren Versionen muss das Attribut singleton verwendet werden dass entweder true für eine singleton-Bean (Default) oder false für eine prototype-Bean sein kann.

Erzeugung des Containers und Zugriff auf die Beans

Um auf die oben konfigurierten Beans zugreifen zu können, gibt es zwei grundlegende Möglichkeiten. Entweder in einem Anwendungs- oder Webkontext. Im Anwendungskontext kann der Container über eine BeanFactory oder einen ApplicationContext erzeugen:
public void main(Object[] args) {
  Ressource resource = new FileSystemRessource("beans.xml");
  BeanFactory factory = new XmlBeanFactory(ressource);

  Object o = factory.getBean("myBean");
}
bzw.
public void main(Object[] args) {
  ApplicationContext context = new FileSystemXmlApplicationContexxt("beans.xml");  

  Object o = context.getBean("myBean");
}
Der Zugriff auf den Container sollte dabei nur einmal erfolgen, da die Bean myBean einen Einstiegspunkt darstellen und alle weiteren Beans als Abhängigkeiten dieser Bean erreichbar sein sollten. Der Container stellt nur wenige Methoden zur Verfügung. Das Ergebnis eines getBean ist immer ein Objekt und muss zur korrekten Verwendung gecastet werden. Die Verwendung in einem Web-Context folgt im übernächsten Kapitel.
... [ Seminar BS, WWW und PS ] ... [ Thema Spring Framework ] ... [ Features: Dependency Injection ] ... [ Features: Aspektorientierte Programmierung ] ...