2. Artikel 1 – Spring IoC
Ziele dieses Dokuments:
- die Konfigurations- und Integrationsmöglichkeiten des Spring-Frameworks (http://www.springframework.org) zu untersuchen
- das Konzept von IoC (Inversion of Control), auch bekannt als Dependency Injection, zu definieren und anzuwenden
2.1. Konfiguration einer 3-Tier-Anwendung mit Spring
Betrachten wir eine klassische 3-Schichten-Anwendung:
![]() |
Wir gehen davon aus, dass der Zugriff auf die Geschäfts- und DAO-Schicht durch Java-Schnittstellen gesteuert wird:
- die Schnittstelle [IArticlesDao] für die Datenzugriffsebene
- die Schnittstelle [IArticlesManager] für die Geschäftsschicht
In der Datenzugriffsebene, auch DAO-Ebene (Data Access Object) genannt, ist es üblich, mit einem DBMS und somit mit einem JDBC-Treiber zu arbeiten. Betrachten wir das Grundgerüst einer Klasse, die auf eine Artikeltabelle in einem DBMS zugreift:
Um eine Operation im DBMS auszuführen, benötigt jede Methode ein [Connection]-Objekt, das die Verbindung zur Datenbank darstellt und über das Daten zwischen der Datenbank und dem Java-Code ausgetauscht werden. Um dieses Objekt zu erstellen, sind vier Informationen erforderlich:
der Name der JDBC-Treiberklasse des DBMS | |
die JDBC-URL der zu verwendenden Datenbank | |
die Anmeldedaten, die zum Herstellen der Verbindung verwendet werden | |
Das Passwort für diesen Benutzernamen |
Wie kann unsere vorherige Klasse [ArticlesDaoPlainJdbc] diese Informationen abrufen? Es gibt mehrere Möglichkeiten:
Lösung 1 – Die Informationen sind fest in der Klasse hinterlegt:
Der Nachteil dieser Lösung ist, dass Sie den Java-Code jedes Mal ändern müssen, wenn sich eine dieser Informationen ändert, beispielsweise wenn das Passwort geändert wird.
Lösung 2 – die Informationen werden dem Objekt bei seiner Erstellung übergeben:
Hier erhält das Objekt die für seine Funktion erforderlichen Informationen bereits bei seiner Erstellung. Das Problem verlagert sich somit auf den Code, der ihm die vier Informationen übergeben hat. Wie hat dieser sie erhalten? Die folgende Klasse [ArticlesManagerWithDataBase] in der Geschäftslogik könnte ein Objekt [ArticlesDaoPlainJdbc] aus der Datenzugriffsschicht erstellen:
![]() |
Wir sehen, dass erneut die zum Erstellen des [ArticlesDaoPlainJdbc]-Objekts benötigten Informationen an den Konstruktor des [ArticlesManagerWithDataBase]-Objekts übergeben werden. Wir können uns vorstellen, dass diese Informationen von einer höheren Schicht, beispielsweise der Benutzeroberflächenschicht, an dieses Objekt übergeben werden. So erreichen wir nach und nach die oberste Schicht der Anwendung. Aufgrund ihrer Position wird diese Schicht nicht von einer Schicht aufgerufen, die ihr die benötigten Konfigurationsinformationen übergeben könnte. Wir müssen daher eine Alternative zur konstruktbasierten Konfiguration finden. Der Standardansatz zur Konfiguration einer Anwendung auf ihrer obersten Schicht besteht darin, eine Datei zu verwenden, die alle Informationen enthält, die sich im Laufe der Zeit ändern können. Es kann mehrere solcher Dateien geben. Beim Start der Anwendung erstellt dann eine Initialisierungsschicht alle oder einen Teil der Objekte, die von den verschiedenen Schichten der Anwendung benötigt werden.
Es gibt eine Vielzahl von Konfigurationsdateien. Der aktuelle Trend geht zur Verwendung von XML-Dateien. Dies ist der von Spring gewählte Ansatz. Die Datei, die ein [ArticlesDaoPlainJdbc]-Objekt konfiguriert, könnte wie folgt aussehen:
Eine Anwendung ist eine Sammlung von Objekten, die Spring als „Beans“ bezeichnet, da sie dem JavaBean-Standard für die Benennung von Zugriffs- und Initialisierungsmethoden (Getter/Setter) der privaten Felder eines Objekts folgen. Objekte in einer Anwendung, die einem bestimmten Zweck dienen, werden oft als einzelne Instanz erstellt. Diese werden als Singletons bezeichnet. In unserem hier besprochenen Beispiel einer mehrschichtigen Anwendung wird der Zugriff auf die Artikeldatenbank daher von einer einzigen Instanz der Klasse [ArticlesDaoPlainJdbc] abgewickelt. Bei einer Webanwendung bedienen diese Serviceobjekte mehrere Clients gleichzeitig. Es wird nicht für jeden Client ein eigenes Serviceobjekt erstellt.
Die obige Spring-Konfigurationsdatei ermöglicht die Erstellung eines einzelnen Service-Objekts vom Typ [ArticlesDaoPlainJdbc] in einem Paket namens [istia.st.articles.dao]. Die vier Informationen, die der Konstruktor dieses Objekts benötigt, sind innerhalb eines <bean>...</bean>-Tags definiert. Es gibt so viele solcher <bean>-Tags, wie Singletons erstellt werden sollen.
Wann werden die in der Spring-Datei definierten Objekte instanziiert? Die Initialisierung der Anwendung kann über die main-Methode der Anwendung erfolgen, sofern eine solche vorhanden ist. Bei einer Webanwendung könnte dies die [init]-Methode des Haupt-Servlets sein. Jede Anwendung verfügt über eine Methode, die garantiert als erste ausgeführt wird. In der Regel erfolgt die Instanziierung von Singletons innerhalb dieser Methode.
Nehmen wir ein Beispiel. Angenommen, wir möchten die vorherige Klasse [ArticlesDaoPlainJdbc] mit einem JUnit-Test testen. Eine JUnit-Testklasse verfügt über eine [setUp]-Methode, die vor allen anderen Methoden ausgeführt wird. Hier werden wir das Singleton [ArticlesDaoPlainJdbc] erstellen.
Wenn wir den Ansatz verfolgen, Konfigurationsinformationen über den Konstruktor zu übergeben, erhalten wir die folgende Testklasse:
Die aufrufende Klasse [TestArticlesPlainJdbc] muss die vier Informationen kennen, die zur Initialisierung des zu erstellenden [ArticlesDaoPlainJdbc]-Singletons erforderlich sind.
Wenn wir den Ansatz verfolgen, Konfigurationsinformationen über eine Konfigurationsdatei zu übergeben, könnten wir die folgende Testklasse verwenden, die die oben beschriebene Spring-Datei nutzt.
Hier muss die aufrufende Klasse [TestSpringArticlesPlainJdbc] die Informationen, die zur Initialisierung des zu erstellenden Singletons erforderlich sind, nicht kennen. Sie muss lediglich Folgendes wissen:
- [springArticlesPlainJdbc.xml]: den Namen der oben beschriebenen Spring-Konfigurationsdatei
- [articlesDao]: den Namen des zu erstellenden Singletons
Eine Änderung an der Konfigurationsdatei, die über diese beiden Elemente hinausgeht, hat keine Auswirkungen auf den Java-Code. Diese Methode zur Konfiguration der Objekte einer Anwendung ist sehr flexibel. Um sich selbst zu konfigurieren, muss die Anwendung nur zwei Dinge wissen:
- den Namen der Spring-Datei, die die Definitionen der zu erstellenden Singletons enthält
- die Namen dieser Singletons, die der Java-Code verwendet, um über die Konfigurationsdatei eine Referenz auf die Objekte zu erhalten, denen sie zugeordnet wurden
2.2. Dependency Injection und Inversion of Control
Lassen Sie uns nun das Konzept der Dependency Injection vorstellen, das von Spring zur Konfiguration von Anwendungen verwendet wird. Der Begriff Inversion of Control (IoC) wird ebenfalls verwendet. Betrachten wir die Erstellung des Singletons [ArticlesManagerWithDataBase] in der Geschäftsschicht unserer Anwendung:
![]() |
Um auf Daten aus dem DBMS zuzugreifen, muss die Geschäftsschicht die Dienste eines Objekts nutzen, das die Schnittstelle [IArticlesDao] implementiert, beispielsweise ein Objekt vom Typ [ArticlesDaoPlainJdbc]. Der Code für die Klasse [ArticlesManagerWithDataBase] könnte wie folgt aussehen:
public class ArticlesManagerWithDataBase implements IArticlesManager {
// a data access instance
private IArticlesDao articlesDao;
....
public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
...
// creation of a data access service
articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
...
}
public ... doSomething(...){
...
}
}
Die Klasse [ArticlesDaoPlainJdbc] soll hier die Schnittstelle [IArticlesDao] implementieren:
Um das für die Funktion der Klasse erforderliche Singleton [IArticlesDao] zu erstellen, verwendet dessen Konstruktor explizit den Namen der Klasse, die die Schnittstelle [IArticlesDao] implementiert:
Wir haben daher eine fest codierte Abhängigkeit vom Klassennamen im Code. Sollte sich die Klasse, die die Schnittstelle [IArticlesDao] implementiert, ändern, müsste der Code im vorherigen Konstruktor angepasst werden. Zwischen den Objekten bestehen folgende Beziehungen:
![]() |
Die Klasse [ArticlesManagerWithDataBase] selbst übernimmt die Initiative, das benötigte Objekt [ArticlesDaoPlainJdbc] zu erstellen. Um auf den Begriff „Inversion of Control“ zurückzukommen, können wir sagen, dass sie diejenige ist, die die „Kontrolle“ darüber hat, das benötigte Objekt zu erstellen.
Wenn wir eine JUnit-Testklasse für die Klasse [ArticlesManagerWithDataBase] schreiben würden, könnte diese etwa so aussehen:
Die Testklasse erstellt eine Instanz der Business-Klasse [ArticlesManagerWithDataBase], die wiederum in ihrem Konstruktor eine Instanz der Datenzugriffsklasse [ArticlesDaoPlainJdbc] erstellt.
Die Spring-Lösung macht es überflüssig, dass die Geschäftsklasse [ArticlesManagerWithDataBase] den Namen [ArticlesDaoPlainJdbc] der von ihr benötigten Datenzugriffsklasse kennt. Dadurch kann die Klasse geändert werden, ohne den Java-Code der Geschäftsklasse zu modifizieren. Spring ermöglicht die gleichzeitige Erstellung beider Singletons: eines für die Datenzugriffsebene und eines für die Geschäftsebene. Die Spring-Konfigurationsdatei definiert einen neuen Bean:
Die neue Funktion ist die Bean, die das Singleton der zu erstellenden Business-Klasse definiert:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
- Die Klasse, die die [articlesManager]-Bean implementiert, ist definiert: [ArticlesManagerWithDataBase]
- Dem Feld [articlesDao] des Beans wird über das Tag <property name="articlesDao"> ein Wert zugewiesen. Dies ist das in der Klasse [ArticlesManagerWithDataBase] definierte Feld:
Damit das Feld [articlesDao] von Spring und seinem <property>-Tag initialisiert wird, muss das Feld dem JavaBean-Standard entsprechen und es muss eine [setArticlesDao]-Methode vorhanden sein, um das Feld [articlesDao] zu initialisieren. Beachten Sie, dass der Methodenname genau vom Feldnamen abgeleitet ist. Ebenso gibt es oft eine [get...]-Methode, um den Wert des Feldes abzurufen. Hier ist es die [getArticlesDao]-Methode. In dieser neuen Version hat die Klasse [ArticlesManagerWithDataBase] keinen Konstruktor mehr. Sie benötigt keinen mehr.
- Der Wert, den Spring dem Feld [articlesDao] zuweist, ist derjenige des Beans [articlesDao], der in seiner Konfigurationsdatei definiert ist:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
<bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
<constructor-arg index="0">
.............
</bean>
- Wenn Spring das Singleton [ArticlesManagerWithDataBase] erstellt, erstellt es auch das Singleton [ArticlesDaoPlainJdbc]:
- Spring erstellt einen Abhängigkeitsgraphen der Beans und stellt fest, dass die [articlesManager]-Bean von der [articlesDao]-Bean abhängt
- Es erstellt die [articlesDao]-Bean, d. h. ein Objekt vom Typ [ArticlesDaoPlainJdbc]
- anschließend erstellt es die [articlesManager]-Bean vom Typ [ArticlesManagerWithDataBase]
Stellen wir uns nun einen JUnit-Test für die Klasse [ArticlesManagerWithDataBase] vor. Dieser könnte wie folgt aussehen:
Verfolgen wir den Erstellungsprozess der beiden Singletons, die in der Spring-Datei namens [springArticlesManagerWithDataBase.xml] definiert sind.
- Die obige [setUp]-Methode fordert eine Referenz auf das Bean namens [articlesManager] an
- Spring konsultiert seine Konfigurationsdatei und findet die Bean [articlesManager]. Wenn diese bereits erstellt wurde, gibt es einfach eine Referenz auf das Objekt (Singleton) zurück; andernfalls erstellt es sie.
- Spring erkennt die Abhängigkeit der [articlesManager]-Bean von der [articlesDao]-Bean. Daher erstellt es das [articlesDao]-Singleton vom Typ [ArticlesDaoPlainJdbc], falls dieses noch nicht (als Singleton) erstellt wurde.
- Es erstellt das [articlesManager]-Singleton vom Typ [ArticlesManagerWithDataBase]
Dieser Mechanismus lässt sich wie folgt schematisch darstellen:
![]() |
Erinnern wir uns an das Grundgerüst der Klasse [ArticlesManagerWithDataBase]:
Sobald Spring die Singletons fertiggestellt hat, verfügen wir über ein Objekt vom Typ [ArticlesManagerWithDataBase], dessen Feld [articlesDao] initialisiert ist, ohne dass es weiß, wie. Wir sagen, dass wir eine Abhängigkeit in das [ArticlesManagerWithDataBase]-Objekt injiziert haben. Wir sagen auch, dass wir die Steuerung umgekehrt haben: Es ist nicht mehr das [ArticlesManagerWithDataBase]-Objekt, das die Initiative ergreift, um das Objekt zu erstellen, das die benötigte [IArticlesDao]-Schnittstelle implementiert; vielmehr ist es die oberste Anwendungsebene (bei ihrer Initialisierung), die sich um die Erstellung aller benötigten Objekte kümmert, indem sie deren gegenseitige Abhängigkeiten verwaltet.
Der Hauptvorteil der Konfiguration des [ArticlesManagerWithDataBase]-Singletons über eine Spring-Datei besteht darin, dass wir nun die Implementierungsklasse ändern können, die dem Feld [articlesDao] der Klasse [ArticlesManagerWithDataBase] entspricht, ohne deren Code zu ändern. Wir müssen lediglich den Klassennamen in der Definition des [articlesDao]-Beans in der Spring-Datei ändern:
wird beispielsweise zu:
Die Bean [ArticlesManagerWithDataBase] wird mit dieser neuen Datenzugriffsklasse arbeiten, ohne es überhaupt zu merken.
2.3. Spring IoC in der Praxis
2.3.1. Beispiel 1
Betrachten Sie die folgende Klasse:
Die Klasse hat:
- zwei private Felder, name und age
- Getter- und Setter-Methoden für diese beiden Felder
- eine toString-Methode, um den Wert des [Person]-Objekts als Zeichenkette abzurufen
- eine init-Methode, die von Spring beim Erstellen des Objekts aufgerufen wird, und eine close-Methode, die beim Löschen des Objekts aufgerufen wird
Um Objekte vom Typ [Person] zu erstellen, verwenden wir die folgende Spring-Datei:
Diese Datei erhält den Namen config.xml.
- Sie definiert zwei Beans mit den jeweiligen Schlüsseln „person1“ und „person2“ vom Typ [Person]
- Sie initialisiert die Felder [name, age] für jede Person
- Sie definiert die Methoden, die beim Erstellen des Objekts [init-method] und beim Löschen des Objekts [destroy-method] aufgerufen werden
Für unsere Tests verwenden wir eine einzige JUnit-Testklasse, der wir nach und nach Methoden hinzufügen werden. Die erste Version dieser Klasse sieht wie folgt aus:
Kommentare:
- Um die in der Datei [config.xml] definierten Beans abzurufen, verwenden wir ein Objekt vom Typ [ListableBeanFactory]. Es gibt weitere Objekttypen, die den Zugriff auf Beans ermöglichen. Das [ListableBeanFactory]-Objekt wird in der Methode [setUp] der Testklasse abgerufen und in einer privaten Variablen gespeichert. Es steht somit allen Testmethoden zur Verfügung.
- Die Datei [config.xml] wird im [ClassPath] der Anwendung abgelegt, d. h. in einem der Verzeichnisse, die von der Java Virtual Machine durchsucht werden, wenn sie nach einer von der Anwendung referenzierten Klasse sucht. Das [ClassPathResource]-Objekt wird verwendet, um im [ClassPath] einer Anwendung nach einer Ressource zu suchen, in diesem Fall nach der Datei [config.xml].
- Spring kann Konfigurationsdateien in verschiedenen Formaten verwenden. Das [XmlBeanFactory]-Objekt dient dazu, eine Konfigurationsdatei im XML-Format zu parsen.
- Die Verarbeitung einer Spring-Datei gibt ein Objekt vom Typ [ListableBeanFactory] zurück, hier das Objekt bf. Mit diesem Objekt wird über bf.getBean(C) ein Bean abgerufen, das durch den Schlüssel C identifiziert wird.
- Die Methode [test1] ruft die Werte der Beans mit den Schlüsseln „person1“ und „person2“ ab und zeigt sie an.
Die Struktur des Eclipse-Projekts unserer Anwendung sieht wie folgt aus:

Anmerkungen:
- Der Ordner [src] enthält den Quellcode. Der kompilierte Code wird in einen Ordner [bin] kopiert, der hier nicht dargestellt ist.
- Die Datei [config.xml] befindet sich im Stammverzeichnis des Ordners [src]. Beim Erstellen des Projekts wird sie automatisch in den Ordner [bin] kopiert, der Teil des [ClassPath] der Anwendung ist. Dort wird sie vom Objekt [ClassPathResource] gesucht.
- Der Ordner [lib] enthält drei Java-Bibliotheken, die von der Anwendung benötigt werden:
- commons-logging.jar und spring-core.jar für die Spring-Klassen
- junit.jar für die JUnit-Klassen
- Der Ordner [lib] ist ebenfalls Teil des [ClassPath] der Anwendung
Die Ausführung der Methode [test1] des JUnit-Tests liefert folgende Ergebnisse:
Kommentare:
- Spring protokolliert eine Reihe von Ereignissen mithilfe der Bibliothek [commons-logging.jar]. Diese Protokolle helfen uns, die Funktionsweise von Spring besser zu verstehen.
- Die Datei [config.xml] wurde geladen und anschließend verarbeitet
- Der Vorgang*
Dies führte zur Erstellung der [person1]-Bean. Wir können das entsprechende Spring-Protokoll einsehen. Da wir in der Definition der [person1]-Bean [init-method="init"] angegeben hatten, wurde die [init]-Methode des erstellten [Person]-Objekts ausgeführt. Die entsprechende Meldung wird angezeigt.
- Der Vorgang
zeigte den Wert des erstellten [Person]-Objekts an.
- Das gleiche Phänomen tritt bei der Schlüssel-Bean [person2] auf.
- Die letzte Operation
personne2 = (Personne) bf.getBean("personne2");
System.out.println("personne2=" + personne2.toString());
Dies führte nicht zur Erstellung eines neuen Objekts vom Typ [Person]. Wäre dies der Fall gewesen, wäre die Methode [init] angezeigt worden, was hier jedoch nicht der Fall ist. Dies ist das Prinzip des Singletons. Standardmäßig erstellt Spring nur eine einzige Instanz der Beans in seiner Konfigurationsdatei. Es handelt sich um einen Objektreferenzdienst. Wird nach der Referenz auf ein Objekt gefragt, das noch nicht erstellt wurde, erstellt es dieses und gibt eine Referenz zurück. Ist das Objekt bereits erstellt, gibt Spring einfach eine Referenz darauf zurück.
- Beachten Sie, dass von der [close]-Methode des [Person]-Objekts keine Spur zu sehen ist, obwohl wir [destroy-method=close] in die Bean-Definition geschrieben hatten. Es ist möglich, dass diese Methode nur ausgeführt wird, wenn der vom Objekt belegte Speicher vom Garbage Collector freigegeben wird. Zu diesem Zeitpunkt ist die Anwendung bereits beendet, und das Schreiben auf den Bildschirm hat keine Wirkung. Zu überprüfen.
Nachdem wir nun die Grundlagen einer Spring-Konfiguration behandelt haben, können wir unsere Erläuterungen etwas schneller durchgehen.
2.3.2. Beispiel 2
Betrachten Sie die folgende neue [Car]-Klasse:
Die Klasse verfügt über:
- drei private Felder: type, make und owner. Diese Felder können mithilfe der öffentlichen get- und set-Methoden initialisiert und ausgelesen werden. Sie können auch mithilfe des Konstruktors Car(String, String, Person) initialisiert werden. Die Klasse verfügt außerdem über einen Konstruktor ohne Argumente, um dem JavaBean-Standard zu entsprechen.
- eine toString-Methode, um den Wert des [Car]-Objekts als Zeichenkette abzurufen
- eine init-Methode, die von Spring unmittelbar nach der Erstellung des Objekts aufgerufen wird, eine close-Methode, die beim Löschen des Objekts aufgerufen wird
Um Objekte vom Typ [Car] zu erstellen, verwenden wir die folgende Spring-Datei [config.xml]:
Diese Datei fügt den vorherigen Definitionen eine Bean mit dem Schlüssel „car1“ vom Typ [Car] hinzu. Um diese Bean zu initialisieren, hätten wir schreiben können:
Anstelle der bereits vorgestellten Methode haben wir uns hier für die Verwendung des Konstruktors Car(String, String, Person) der Klasse entschieden. Darüber hinaus definiert die Bean [car1] die Methode, die während der anfänglichen Erstellung des Objekts aufgerufen werden soll [init-method], sowie die Methode, die während der Zerstörung des Objekts aufgerufen werden soll [destroy-method].
Für unsere Tests verwenden wir die bereits vorgestellte JUnit-Testklasse und fügen ihr die folgende [test2]-Methode hinzu:
Die Methode [test2] ruft die Bean [car1] ab und gibt sie aus.
Die Struktur des Eclipse-Projekts bleibt dieselbe wie im vorherigen Test. Die Ausführung der Methode [test2] des JUnit-Tests liefert folgende Ergebnisse:
Kommentare:
- Die Methode [test2] fordert eine Referenz auf die Bean [car1] an
- Zeile 4: Spring beginnt mit der Erstellung der [car1]-Bean, da diese Bean noch nicht erstellt wurde (Singleton)
- Zeile 6: Da die Bean [car1] auf die Bean [person2] verweist, wird diese wiederum instanziiert
- Zeile 7: Die Bean [person2] wurde erstellt. Anschließend wird ihre [init]-Methode ausgeführt.
- Zeile 9: Spring gibt an, dass es einen Konstruktor verwenden wird, um die Bean [car1] zu erstellen
- Zeile 10: Die Bean [car1] wurde erstellt. Anschließend wird ihre [init]-Methode ausgeführt.
- Zeile 11: Die Methode [test2] gibt den Wert der Bean [car1] aus
2.3.3. Beispiel 3
Wir führen die folgende neue Klasse [PersonGroup] ein:
Seine beiden privaten Mitglieder sind:
members: ein Array von Personen, die Mitglieder der Gruppe sind
workGroups: ein Wörterbuch, das eine Person einer Arbeitsgruppe zuordnet
Beachten Sie hierbei, dass die Klasse [PeopleGroup] keinen Konstruktor ohne Argumente definiert, um dem JavaBean-Standard zu entsprechen. Erinnern Sie sich daran, dass es bei Fehlen eines Konstruktors einen „Standard“-Konstruktor gibt, bei dem es sich um den Konstruktor ohne Argumente handelt, der nichts tut.
Das Ziel hierbei ist es, zu zeigen, wie Spring die Initialisierung komplexer Objekte ermöglicht, beispielsweise solcher mit Array- oder Dictionary-Feldern. Wir fügen der bisherigen Spring-Datei [config.xml] eine neue Bean hinzu:
- Mit dem <list>-Tag können Sie ein Feld vom Typ Array oder ein Feld, das die List-Schnittstelle implementiert, mit verschiedenen Werten initialisieren.
- Mit dem map-Tag können Sie dasselbe mit einem Feld tun, das die Map-Schnittstelle implementiert
Für unsere Tests verwenden wir die bereits vorgestellte JUnit-Testklasse und fügen ihr die folgende Methode [test3] hinzu:
Die Methode [test3] ruft die Bean [groupe1] ab und gibt sie aus.
Die Struktur des Eclipse-Projekts bleibt dieselbe wie im vorherigen Test. Die Ausführung der Methode [test3] des JUnit-Tests liefert folgende Ergebnisse:
Kommentare:
- Die Methode [test3] fordert eine Referenz auf die Bean [group1] an
- Zeile 4: Spring beginnt mit der Erstellung dieser Bean
- Da die [group1]-Bean auf die [person1]- und [person2]-Beans verweist, werden diese beiden Beans erstellt (Zeilen 6 und 9) und ihre init-Methoden ausgeführt (Zeilen 7 und 10)
- Zeile 11: Die [group1]-Bean wurde erstellt. Ihre [init]-Methode wird nun ausgeführt.
- Zeile 12: Anzeige, die von der Methode [test3] angefordert wurde.
2.4. Spring zur Konfiguration von dreischichtigen Webanwendungen
2.4.1. Allgemeine Anwendungsarchitektur
Wir möchten eine dreischichtige Anwendung mit folgender Struktur erstellen:
![]() |
- Die drei Schichten werden durch die Verwendung von Java-Schnittstellen voneinander unabhängig gemacht
- Die Integration der drei Schichten wird von Spring übernommen
- Wir werden für jede der drei Schichten separate Pakete erstellen, die wir Control, Domain und Dao nennen werden. Ein zusätzliches Paket wird die Testanwendungen enthalten.
Die Anwendungsstruktur in Eclipse könnte wie folgt aussehen:

2.4.2. Die DAO-Datenzugriffsschicht
Die DAO-Schicht implementiert die folgende Schnittstelle:
package istia.st.demo.dao;
public interface IDao1 {
public int doSometingInDaoLayer(int a, int b);
}
- Schreiben Sie zwei Klassen, Dao1Impl1 und Dao1Impl2, die die Schnittstelle IDao1 implementieren. Die Methode Dao1Impl1.doSomethingInDaoLayer gibt a+b zurück, und die Methode Dao1Impl2.doSomethingInDaoLayer gibt a-b zurück.
- Schreiben Sie eine JUnit-Testklasse, um die beiden vorherigen Klassen zu testen
2.4.3. Die Geschäftsschicht
Die Geschäftsschicht implementiert die folgende Schnittstelle:
package istia.st.demo.domain;
public interface IDomain1 {
public int doSomethingInDomainLayer(int a, int b);
}
- Schreiben Sie zwei Klassen, Domain1Impl1 und Domain1Impl2, die die Schnittstelle IDomain1 implementieren. Diese Klassen verfügen über einen Konstruktor, der einen Parameter vom Typ IDao1 entgegennimmt. Die Methode Domain1Impl1.doSomethingInDomainLayer erhöht a und b um eins und übergibt diese beiden Parameter anschließend an die Methode doSomethingInDaoLayer des empfangenen IDao1-Objekts. Die Methode Domain1Impl2.doSomethingInDomainLayer hingegen verringert a und b um eins, bevor sie dasselbe tut.
- Schreiben Sie eine JUnit-Testklasse, um die beiden vorangegangenen Klassen zu testen
2.4.4. Die Benutzeroberflächenschicht
Die Benutzeroberflächenschicht implementiert die folgende Schnittstelle:
package istia.st.demo.control;
public interface IControl1 {
public int doSometingInControlLayer(int a, int b);
}
- Schreiben Sie zwei Klassen, Control1Impl1 und Control1Impl2, die die Schnittstelle IControl1 implementieren. Diese Klassen verfügen über einen Konstruktor, der einen Parameter vom Typ IDomain1 entgegennimmt. Die Methode Control1Impl1.doSomethingInControlLayer erhöht a und b um eins und übergibt diese beiden Parameter anschließend an die Methode doSomethingInDomainLayer des empfangenen IDomain1-Objekts. Die Methode Control1Impl2.doSomethingInControlLayer hingegen verringert a und b um eins, bevor sie dasselbe tut.
- Schreiben Sie eine JUnit-Testklasse, um die beiden vorherigen Klassen zu testen
2.4.5. Integration mit Spring
- Schreiben Sie eine Spring-Konfigurationsdatei, die festlegt, welche Klassen jede der drei vorherigen Schichten verwenden soll
- Schreiben Sie eine JUnit-Testklasse unter Verwendung verschiedener Spring-Konfigurationen, um die Flexibilität der Anwendung hervorzuheben
- Schreiben Sie eine eigenständige Anwendung (main-Methode), die zwei Parameter an die IControl1-Schnittstelle übergibt und das von der Schnittstelle zurückgegebene Ergebnis anzeigt.
2.4.6. Eine Lösung
2.4.6.1. Das Eclipse-Projekt

Die Archive im Ordner [lib] wurden dem [ClassPath] des Projekts hinzugefügt.
2.4.6.2. Das Paket [istia.st.demo.dao]
Die Schnittstelle:
Eine erste Implementierungsklasse:
Eine zweite Implementierungsklasse:
2.4.6.3. Das Paket [istia.st.demo.domain]
Die Schnittstelle:
Eine erste Implementierungsklasse:
Eine zweite Implementierungsklasse:
2.4.6.4. Das Paket [istia.st.demo.control]
Die Schnittstelle
Eine erste Implementierungsklasse:
Eine zweite Implementierungsklasse:
2.4.6.5. [Spring] Konfigurationsdateien
Eine erste [springMainTest1.xml]:
Eine zweite [springMainTest2.xml]:
2.4.6.6. Das Testpaket [istia.st.demo.tests]
Ein [main]-Test:
In der Eclipse-Konsole angezeigte Ergebnisse:
Ein weiterer Test unter Verwendung der zweiten [Spring]-Konfigurationsdatei:
In der Eclipse-Konsole angezeigte Ergebnisse:
Zum Schluss noch ein JUnit-Test:
2.5. Fazit
Das Spring-Framework bietet echte Flexibilität sowohl bei der Anwendungsarchitektur als auch bei der Konfiguration. Wir haben das IoC-Konzept verwendet, eine der beiden Säulen von Spring. Die andere Säule ist AOP (Aspect-Oriented Programming), auf die wir nicht eingegangen sind. Damit können Sie einer Klassenmethode durch Konfiguration „Verhalten“ hinzufügen, ohne den Code der Methode zu ändern. Einfach ausgedrückt ermöglicht AOP Ihnen, Aufrufe bestimmter Methoden zu filtern:
![]() |
- Der Filter kann vor oder nach der Zielmethode M oder beides ausgeführt werden.
- Die Methode M hat keine Kenntnis von diesen Filtern. Sie werden in der Spring-Konfigurationsdatei definiert.
- Der Code der Methode M wird nicht verändert. Filter sind Java-Klassen, die implementiert werden müssen. Spring stellt vordefinierte Filter bereit, insbesondere für die Verwaltung von DBMS-Transaktionen.
- Filter sind Beans und werden als solche in der Spring-Konfigurationsdatei als Beans definiert.
Ein gängiger Filter ist der Transaktionsfilter. Betrachten wir eine Methode M in der Geschäftsschicht, die zwei untrennbare Operationen an Daten ausführt (eine Arbeitseinheit). Sie ruft zwei Methoden, M1 und M2, in der DAO-Schicht auf, um diese beiden Operationen auszuführen.
![]() |
Da sich die Methode M in der Geschäftsschicht befindet, abstrahiert sie den zugrunde liegenden Datenspeicher. Sie muss beispielsweise nicht davon ausgehen, dass die Daten in einem DBMS gespeichert sind oder dass sie die Aufrufe der Methoden M1 und M2 in eine DBMS-Transaktion einschließen muss. Es ist Aufgabe der DAO-Schicht, diese Details zu handhaben. Eine Lösung für das vorgenannte Problem besteht darin, in der DAO-Schicht eine Methode zu erstellen, die selbst die Methoden M1 und M2 aufruft und diese Aufrufe in eine DBMS-Transaktion einbindet.
![]() |
Die AOP-Filterlösung ist flexibler. Sie ermöglicht es Ihnen, einen Filter zu definieren, der vor dem Aufruf von M eine Transaktion startet und nach dem Aufruf je nach Bedarf ein Commit oder ein Rollback durchführt.
![]() |
Dieser Ansatz bietet mehrere Vorteile:
- Sobald der Filter definiert ist, kann er auf mehrere Methoden angewendet werden, beispielsweise auf alle, die eine Transaktion erfordern
- die gefilterten Methoden müssen nicht umgeschrieben werden
- da die zu verwendenden Filter über die Konfiguration definiert werden, können sie geändert werden
Zusätzlich zu den IoC- und AOP-Konzepten bietet Spring zahlreiche Hilfsklassen für dreischichtige Anwendungen:
- für JDBC, SQLMap (iBatis), Hibernate und JDO (Java Data Object) in der DAO-Schicht
- für das MVC-Modell in der Benutzeroberflächenschicht
Weitere Informationen: http://www.springframework.org.









