Skip to content

15. Spring IoC

15.1. Einführung

Wir wollen die Konfigurations- und Integrationsmöglichkeiten des Spring-Frameworks (http://www.springframework.org) erkunden sowie das Konzept der IoC (Inversion of Control), auch bekannt als Dependency Injection, definieren und anwenden.

Betrachten wir die 3-Tier-Anwendung, die wir gerade erstellt haben:

Um die Anfragen des Benutzers zu bearbeiten, muss der Controller [Application] die Schicht [service] ansprechen. In unserem Beispiel handelte es sich dabei um eine Instanz vom Typ [DaoImpl]. Der Controller [Application] hat in seiner Methode [init] (Absatz 14.8.3) eine Referenz auf die Schicht [service] erhalten:

@SuppressWarnings("serial")
public class Application extends HttpServlet {
...

    // Service
    ServiceImpl service=null;
...
    // Initialisierung
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
        // Instanziierung der Schicht [dao]
        DaoImpl dao = new DaoImpl();
        dao.init();
        // Instanziierung der Schicht [service]
        service = new ServiceImpl();
        service.setDao(dao);
    }
  • Zeile 6: Die Schicht [dao] wurde durch die explizite Erstellung einer Instanz [DaoImpl] instanziiert
  • Zeile 9: Die Schicht [service] wurde durch die explizite Erstellung einer Instanz [ServiceImpl] instanziiert

Zur Erinnerung: Die Klassen [DaoImpl] und [ServiceImpl] implementieren Schnittstellen, nämlich die Schnittstellen [IDao] bzw. [IService]. In zukünftigen Versionen wird die Schnittstelle [IDao] durch eine Klasse implementiert, die eine in der Datenbank gespeicherte Liste von Personen verwaltet. Nennen wir diese Klasse im Beispiel [DaoBD]. Das Ersetzen der Implementierung [DaoImpl] der Schicht [dao) durch die Implementierung [DaoBD] erfordert eine Neukompilierung der Schicht [web]. Denn die Zeile 6 oben, die die Schicht [dao] mit einem Typ [DaoImpl] instanziiert, muss diese nun mit einem Typ [DaoBD] instanziieren. Unsere Schicht [web] ist also von der Schicht [dao] abhängig. Zeile 9 oben zeigt, dass sie ebenfalls von der Schicht [service] abhängig ist.

Mit Spring IoC können wir eine 3-Tier-Anwendung erstellen, bei der die Schichten voneinander unabhängig sind (c.a.d), sodass die Änderung einer Schicht keine Änderung der anderen erfordert. Dies sorgt für große Flexibilität bei der Weiterentwicklung der Anwendung.

Die bisherige Architektur wird sich wie folgt weiterentwickeln:

Mit [Spring IoC] erhält der Controller [Application] die benötigte Referenz auf die Schicht [service] wie folgt:

  1. In seiner Methode [init] fordert er die Schicht [Spring IoC] auf, ihm eine Referenz auf die Schicht [service] zu liefern
  2. [Spring IoC] wird daraufhin eine Konfigurationsdatei XML auswerten, die angibt, welche Klasse instanziiert und wie sie initialisiert werden muss.
  3. [Spring IoC] gibt die Referenz der erstellten Schicht [service] an den Controller [Application] zurück.

Der Vorteil dieser Lösung besteht darin, dass die Namen der Klassen, die die verschiedenen Schichten instanziieren, nun nicht mehr fest in der Methode [init] des Controllers codiert sind, sondern lediglich in einer Konfigurationsdatei enthalten sind. Eine Änderung der Implementierung einer Schicht führt zu einer Änderung in dieser Konfigurationsdatei, nicht jedoch im Controller.

Lassen Sie uns nun die Möglichkeiten von [Spring IoC] anhand von Beispielen vorstellen.

15.2. Spring IoC in der Praxis

15.2.1. Spring

[Spring IoC] ist Teil eines größeren Projekts, das unter der URL [http://www.springframework.org/] (Mai 2006) verfügbar ist:

  • [1]: Spring nutzt verschiedene Technologien von Drittanbietern, die hier als dépendances bezeichnet werden. Es ist erforderlich, die Version mit dépendances herunterzuladen, um zu vermeiden, dass anschließend die Bibliotheken der Drittanbieter-Tools heruntergeladen werden müssen.
  • [2]: Die Verzeichnisstruktur der heruntergeladenen ZIP-Datei
  • [3]: Die Distribution [Spring], c.a.d. Die .jar-Dateien des Spring-Projekts selbst ohne dessen Abhängigkeiten. Das Erscheinungsbild von Spring ([IoC]) wird durch die Archive [spring-core.jar, spring-beans.jar] gewährleistet.
  • [4,5]: Die Archive der Tools von Drittanbietern

15.2.2. Eclipse-Projekte der Beispiele

Wir werden drei Beispiele erstellen, die die Verwendung von Spring IoC veranschaulichen. Sie werden alle im folgenden Eclipse-Projekt enthalten sein:

Image

Das Projekt [springioc-exemples] ist so konfiguriert, dass sich die Quelldateien und die kompilierten Klassen im Stammverzeichnis des Projektordners befinden:

  • [1]: Die Ordnerstruktur des Projekts [Eclipse]
  • [2]: Die Spring-Konfigurationsdateien befinden sich im Stammverzeichnis des Projekts, also im Verzeichnis Classpath der Anwendung
  • [3]: Die Klassen aus Beispiel 1
  • [4]: Die Klassen aus Beispiel 2
  • [5]: Die Klassen aus Beispiel 3
  • [6]: Die Bibliotheken des Projekts [spring-core.jar, spring-beans.jar] befinden sich im Ordner [dist] der Spring-Distribution und [commons-logging.jar] im Ordner [lib/jakarta-commons]. Diese drei Archive wurden in das Classpath der Anwendung aufgenommen.

15.2.3. Beispiel 1

Die Elemente aus Beispiel 1 wurden in das Paket [springioc01] des Projekts aufgenommen:

Image

Die Klasse [Personne] lautet wie folgt:

package istia.st.springioc01;

public class Personne {

    // Merkmale
    private String nom;
    private int age;

    // Anzeige „Person“
    public String toString() {
        return "nom=[" + this.nom + "], age=[" + this.age + "]";
    }

    // init-close
    public void init() {
        System.out.println("init personne [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy personne [" + this.toString() + "]");
    }

    // Getter-Setter
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

}

Die Klasse enthält:

  • Zeilen 6–7: zwei private Felder „nom“ und „age“
  • Zeilen 23–38: die Methoden zum Auslesen (get) und Schreiben (set) dieser beiden Felder
  • Zeilen 10–12: eine Methode „toString“, um den Wert des Objekts „[Personne]“ in Form einer Zeichenkette abzurufen
  • Zeilen 15–21: eine Methode „init“, die von Spring bei der Erstellung des Objekts aufgerufen wird, sowie eine Methode „close“, die bei der Löschung des Objekts aufgerufen wird

Um Objekte vom Typ [Personne] mit Spring zu instanziieren, verwenden wir die folgende Datei [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
</beans>
  • Zeilen 3, 12: Das <beans>-Tag ist das Stamm-Tag der Spring-Konfigurationsdateien. Innerhalb dieses Tags dient das <bean>-Tag dazu, die verschiedenen zu erstellenden Objekte zu definieren.
  • Zeilen 4–7: Definition eines Beans
  • Zeile 4: Die Bean heißt [personne1] (Attribut id) und ist eine Instanz der Klasse [istia.st.springioc01.Personne] (Attribut class). Die Methode [init] der Instanz wird nach deren Erstellung aufgerufen (Attribut init-method), und die Methode [close] der Instanz wird vor deren Löschung aufgerufen (Attribut destroy-method).
  • Zeile 5: Legt den Wert fest, der der Eigenschaft [nom] (Attribut name) der erstellten Instanz [Personne] zugewiesen werden soll. Um diese Initialisierung durchzuführen, verwendet Spring die Methode [setNom]. Diese Methode muss daher vorhanden sein. Dies ist hier der Fall.
  • Zeile 6: Gleiches gilt für die Eigenschaft [age].
  • Zeilen 8–11: Analoge Definition eines Beans namens [personne2]

Die Testklasse [Main] sieht wie folgt aus:

package istia.st.springioc01;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // Auswertung der Spring-Konfigurationsdatei
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
        // Abruf des Beans [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
        // Abruf des Beans [personne2]
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // Erneutes Abrufen des Beans [personne2]
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // Alle Beans werden gelöscht
        bf.destroySingletons();
    }
}

Anmerkungen:

  • Zeile 10: Um die in der Datei [spring-config-01.xml] definierten Beans abzurufen, verwenden wir ein Objekt vom Typ [XmlBeanFactory], mit dem die in einer Datei XML definierten Beans instanziiert werden können. Die Datei [spring-config-01.xml] wird in der [ClassPath] der Anwendung c.a.d abgelegt, und zwar in einem der Verzeichnisse, die von der Java-Virtual-Machine durchsucht werden, wenn sie nach einer von der Anwendung referenzierten Klasse sucht. Das Objekt [ClassPathResource] dient dazu, eine Ressource in der Datei [ClassPath] einer Anwendung zu suchen, in diesem Fall die Datei [spring-config-01.xml]. Mit dem erhaltenen Objekt [bf] (Bean Factory) lässt sich über die Anweisung bf.getBean("XX") die Referenz auf ein Bean namens „XX“ abrufen.
  • Zeile 12: Es wird eine Referenz auf die Bean mit dem Namen [personne1] in der Datei [spring-config-01.xml] angefordert.
  • Zeile 13: Der Wert des entsprechenden Objekts [Personne] wird angezeigt.
  • Zeilen 15–16: Das Gleiche wird für die Bean mit dem Namen [personne2] durchgeführt.
  • Zeilen 18–19: Die Bean mit dem Namen [personne2] wird erneut angefordert.
  • Zeile 21: Alle Beans mit den Namen [bf] und c.a.d werden gelöscht. Diese wurden aus der Datei [spring-config-01.xml] erstellt.

Die Ausführung der Klasse [Main] liefert folgende Ergebnisse:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Anmerkungen:

  • Zeile 1 wurde durch die Ausführung von Zeile 12 von [Main] erzeugt. Die Operation
Personne personne1 = (Personne) bf.getBean("personne1");

hat die Erstellung des Beans [personne1] erzwungen. Da in der Definition des Beans [personne1] „[init-method="init"]“ angegeben war, wurde die Methode [init] des erstellten Objekts [Personne] ausgeführt. Die entsprechende Meldung wird angezeigt.

  • Zeile 2: Zeile 13 von [Main] hat den Wert des erstellten Objekts [Personne] angezeigt.
  • Zeilen 3–4: Das gleiche Phänomen wiederholt sich für die Bean mit dem Namen [personne2].
  • Zeile 5: Die Operation in den Zeilen 18–19 von [Main]
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

hat nicht zur Erstellung eines neuen Objekts vom Typ [Personne] geführt. Wäre dies der Fall gewesen, wäre die Methode [init] angezeigt worden. Dies ist das Prinzip des Singletons. Spring erstellt standardmäßig nur eine einzige Instanz der Beans aus seiner Konfigurationsdatei. Es handelt sich um einen Objekt-Referenzdienst. Wird er nach der Referenz eines noch nicht erstellten Objekts gefragt, erstellt er dieses und gibt eine Referenz darauf zurück. Ist das Objekt bereits erstellt, gibt Spring lediglich eine Referenz darauf zurück. Da [personne2] hier bereits erstellt wurde, gibt Spring lediglich eine Referenz darauf zurück.

  • Die Ausgaben in den Zeilen 6–7 wurden durch Zeile 21 von [Main] ausgelöst, die die Löschung aller Beans anfordert, auf die das Objekt [XmlBeanFactory bf] verweist, also der Beans [personne1, personne2]. Da diese beiden Beans das Attribut [destroy-method="close"] besitzen, wird die Methode [close] beider Beans ausgeführt und löst die Anzeige der Zeilen 6–7 aus.

Da wir nun die Grundlagen einer Spring-Konfiguration verstanden haben, werden wir im weiteren Verlauf etwas schneller vorankommen.

15.2.4. Beispiel 2

Die Elemente aus Beispiel 2 befinden sich im Paket [springioc02] des Projekts:

Image

Das Paket [springioc02] wird zunächst durch Kopieren und Einfügen des Pakets [springioc01] erstellt, anschließend wird die Klasse [Voiture] hinzugefügt und die Klasse [Main] an das neue Beispiel angepasst.

Die Klasse [Voiture] lautet wie folgt:

package istia.st.springioc02;

public class Voiture {
    // Eigenschaften
    private String marque;
    private String type;
    private Personne propriétaire;

    // Konstruktoren
    public Voiture() {
    }

    public Voiture(String marque, String type, Personne propriétaire) {
        setMarque(marque);
        setType(type);
        setPropriétaire(propriétaire);
    }

    // toString
    public String toString() {
        return "Voiture : marque=[" + this.marque + "] type=[" + this.type
                + "] propriétaire=[" + this.propriétaire + "]";
    }

    // Getter-Setter
    public String getMarque() {
        return marque;
    }

    public void setMarque(String marque) {
        this.marque = marque;
    }

    public Personne getPropriétaire() {
        return propriétaire;
    }

    public void setPropriétaire(Personne propriétaire) {
        this.propriétaire = propriétaire;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    // init-close
    public void init() {
        System.out.println("init voiture [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy voiture [" + this.toString() + "]");
    }
}

Die Klasse enthält:

  • Zeilen 5–7: drei private Felder „Typ“, „Marke“ und „Besitzer“. Diese Felder können über die öffentlichen get- und set-Methoden der Beans in den Zeilen 26–48 initialisiert und ausgelesen werden. Sie können auch mithilfe des in den Zeilen 13–17 definierten Konstruktors „Auto(String, String, Person)“ initialisiert werden. Die Klasse verfügt außerdem über einen Konstruktor ohne Argumente, um dem Standard JavaBean zu entsprechen.
  • Zeilen 20–23: Eine Methode „toString“ zum Abrufen des Werts des Objekts „[Voiture]“ in Form einer Zeichenkette
  • Zeilen 51–57: eine init-Methode, die von Spring unmittelbar nach der Erstellung des Objekts aufgerufen wird, sowie eine close-Methode, die bei der Löschung des Objekts aufgerufen wird

Um Objekte vom Typ [Voiture] zu erstellen, verwenden wir die folgende Spring-Datei [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="voiture1" class="istia.st.springioc02.Voiture" init-method="init" destroy-method="close">
        <constructor-arg index="0" value="Peugeot" />
        <constructor-arg index="1" value="307" />
        <constructor-arg index="2">
            <ref local="personne2" />
        </constructor-arg>
    </bean>
</beans>

Diese Datei fügt den in [spring-config-01.xml] definierten Beans einen Bean mit dem Schlüssel „voiture1“ vom Typ [Voiture] hinzu (Zeilen 12–17). Um diesen Bean zu initialisieren, hätte man schreiben können:

1
2
3
4
5
6
7
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" init-method="init" destroy-method="close">
        <property name="marque" value="Peugeot"/>
        <property name="type" value="307"/>
        <property name="propriétaire">
            <ref local="personne2"/>
        </property>
</bean>

Anstatt diese bereits in Beispiel 1 vorgestellte Methode zu wählen, haben wir uns hier für die Verwendung des Konstruktors „Voiture(String, String, Personne)“ der Klasse entschieden.

  • Zeile 12: Definition des Namens der Bean, ihrer Klasse, der nach ihrer Instanziierung auszuführenden Methode sowie der nach ihrer Löschung auszuführenden Methode.
  • Zeile 13: Wert des ersten Parameters des Konstruktors [Voiture(String, String, Personne)].
  • Zeile 14: Wert des zweiten Parameters des Konstruktors [Voiture(String, String, Personne)].
  • Zeilen 15–17: Wert des dritten Parameters des Konstruktors [Voiture(String, String, Personne)]. Dieser Parameter ist vom Typ [Personne]. Als Wert wird ihm die Referenz (Tag ref) des Beans [personne2] übergeben, der in derselben Datei definiert ist (Attribut local).

Für unsere Tests verwenden wir die folgende Klasse [Main]:

package istia.st.springioc02;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // Verwendung der Spring-Konfigurationsdatei
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
        // Bean abrufen [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
        // Beans werden gelöscht
        bf.destroySingletons();
    }
}

Die Methode [main] fordert die Referenz der Bean [voiture1] an (Zeile 12) und gibt sie aus (Zeile 13). Die Ergebnisse lauten wie folgt:

1
2
3
4
5
init personne [nom=[Brigitte], age=[20]]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]
destroy voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
destroy personne [nom=[Brigitte], age=[20]]

Anmerkungen:

  1. Die Methode [main] fordert eine Referenz auf die Bean [voiture1] an (Zeile 12). Spring beginnt mit der Erstellung der Bean [voiture1], da diese Bean noch nicht erstellt wurde (Singleton). Da die Bean [voiture1] auf die Bean [personne2] verweist, wird diese wiederum erstellt. Die Bean [personne2] wurde erstellt. Daraufhin wird ihre Methode [init] ausgeführt (Zeile 1 der Ergebnisse). Anschließend wird die Bean [voiture1] instanziiert. Daraufhin wird ihre Methode [init] ausgeführt (Zeile 2 der Ergebnisse).
  2. Zeile 3 der Ergebnisse stammt aus Zeile 13 von [main]: Der Wert des Beans [voiture1] wird angezeigt.
  3. Zeile 15 von [main] fordert die Löschung aller vorhandenen Beans an, was zur Anzeige der Zeilen 4 und 5 der Ergebnisse führt.

15.2.5. Beispiel 3

Die Elemente aus Beispiel 3 befinden sich im Paket [springioc03] des Projekts:

Image

Das Paket [springioc03] wird zunächst durch Kopieren und Einfügen aus dem Paket [springioc01] erstellt, anschließend wird die Klasse [GroupePersonnes] hinzugefügt, die Klasse [Voiture] wird gelöscht und die Klasse [Main] wird an das neue Beispiel angepasst.

Die Klasse [GroupePersonnes] lautet wie folgt:

package istia.st.springioc03;

import java.util.Map;

public class GroupePersonnes {

    // Eigenschaften
    private Personne[] membres;
    private Map groupesDeTravail;

    // Getter – Setter
    public Personne[] getMembres() {
        return membres;
    }

    public void setMembres(Personne[] membres) {
        this.membres = membres;
    }

    public Map getGroupesDeTravail() {
        return groupesDeTravail;
    }

    public void setGroupesDeTravail(Map groupesDeTravail) {
        this.groupesDeTravail = groupesDeTravail;
    }

    // Anzeige
    public String toString() {
        String liste = "membres : ";
        for (int i = 0; i < this.membres.length; i++) {
            liste += "[" + this.membres[i].toString() + "]";
        }
        return liste + ", groupes de travail = "
                + this.groupesDeTravail.toString();
    }

    // Init-Close
    public void init() {
        System.out.println("init GroupePersonnes [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
    }
}

Ihre beiden privaten Elemente sind:

Zeile 8: Mitglieder: ein Array mit den Personen, die der Gruppe angehören

Zeile 9: groupesDeTravail: ein Dictionary, das eine Person einer Arbeitsgruppe zuordnet

Hier ist zu beachten, dass die Klasse [GroupePersonnes] keinen Konstruktor ohne Argumente definiert. Zur Erinnerung: Wenn kein Konstruktor vorhanden ist, gibt es einen „Standardkonstruktor“, der der Konstruktor ohne Argumente ist und nichts tut.

Hier soll gezeigt werden, wie Spring die Initialisierung komplexer Objekte ermöglicht, beispielsweise Objekte mit Feldern vom Typ Array oder Dictionary. Die Bean-Datei [spring-config-03.xml] aus Beispiel 3 lautet wie folgt:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="groupe1" class="istia.st.springioc03.GroupePersonnes" init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref local="personne1" />
                <ref local="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte" value="Marketing" />
                <entry key="Simon" value="Ressources humaines" />
            </map>
        </property>
    </bean>
</beans>
  • Zeilen 14–17: Mit dem Tag <list> lässt sich ein Feld vom Typ Array oder ein Feld, das die Schnittstelle List implementiert, mit verschiedenen Werten initialisieren.
  • Zeilen 20–23: Mit dem Tag <map> lässt sich dasselbe mit einem Feld erreichen, das die Schnittstelle „Map“ implementiert.

Für unsere Tests verwenden wir die folgende Klasse [Main]:

package istia.st.springioc03;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // Verarbeitung der Spring-Konfigurationsdatei
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
        // Abruf des Beans [groupe1]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
        // Beans werden gelöscht
        bf.destroySingletons();
    }
}
  • Zeilen 12–13: Wir fordern von Spring eine Referenz auf das Bean [groupe1] an und geben dessen Wert aus.

Die Ergebnisse lauten wie folgt:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}
destroy GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Anmerkungen:

  • In Zeile 12 von [Main] wird eine Referenz auf den Bean [groupe1] angefordert. Spring beginnt mit der Erstellung dieser Bean. Da die Bean [groupe1] auf die Beans [personne1] und [personne2] verweist, werden diese beiden Beans erstellt (Zeilen 1 und 2 der Ergebnisse). Anschließend wird die Bean [groupe1] instanziiert und ihre Methode [init] ausgeführt (Zeile 3 der Ergebnisse).
  • Zeile 13 von [Main] zeigt Zeile 4 der Ergebnisse an.
  • Zeile 15 von [Main] sorgt dafür, dass die Zeilen 5–7 der Ergebnisse angezeigt werden.

15.3. Konfiguration einer n-Tier-Anwendung mit Spring

Betrachten wir eine 3-Tier-Anwendung mit folgender Struktur:

utilisateurDonnéesCouche Fachbereich [metier] Datenzugriffsebene [dao] Benutzeroberflächenschicht [ui]

Wir möchten hier die Vorteile von Spring beim Aufbau einer solchen Architektur aufzeigen.

  • Die drei Schichten werden durch die Verwendung von Java-Schnittstellen voneinander unabhängig gemacht
  • Die Integration der drei Schichten erfolgt durch Spring

Die Struktur der Anwendung unter Eclipse könnte wie folgt aussehen:

  • [1]: die Schicht [dao]:
    • [IDao]: die Schnittstelle der Schicht
    • [Dao1, Dao2]: zwei Implementierungen dieser Schnittstelle
  • [2]: die Schicht [metier]:
    • [IMetier]: die Schnittstelle der Schicht
    • [Metier1, Metier2]: zwei Implementierungen dieser Schnittstelle
  • [3]: die Schicht [ui]:
    • [IUi]: die Schnittstelle der Schicht
    • [Ui1, Ui2]: zwei Implementierungen dieser Schnittstelle
  • [4]: die Spring-Konfigurationsdateien der Anwendung. Wir werden die Anwendung auf zwei Arten konfigurieren.
  • [5]: Die für die Anwendung erforderlichen Bibliotheken. Es handelt sich um dieselben, die in den vorherigen Beispielen verwendet wurden.
  • [6]: das Testpaket. [Main1] verwendet die Konfiguration [spring-config-01.xml] und [Main2] die Konfiguration [spring-config-02.xml].

Das Ziel dieses Beispiels ist es zu zeigen, dass wir die Implementierung einer oder mehrerer Schichten der Anwendung ändern können, ohne dass dies Auswirkungen auf die anderen Schichten hat. Alles geschieht in der Spring-Konfigurationsdatei.


Die Schicht [dao]


Die Schicht [dao] implementiert die folgende Schnittstelle [IDao]:

1
2
3
4
5
package istia.st.springioc.troistier.dao;

public interface IDao {
    public int doSomethingInDaoLayer(int a, int b);
}

Die Implementierung [Dao1] sieht wie folgt aus:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }
}

Die Implementierung [Dao2] sieht wie folgt aus:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }
}

Die Schicht [métier]


Die Schicht [métier] implementiert die folgende Schnittstelle [IMetier]:

1
2
3
4
5
package istia.st.springioc.troistier.metier;

public interface IMetier {
      public int doSomethingInBusinessLayer(int a, int b);
}

Die Implementierung [Metier1] lautet wie folgt:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier1 implements IMetier {

    // Schicht [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

Die Implementierung [Metier2] sieht wie folgt aus:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier2 implements IMetier {

    // Schicht [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a--;
        b--;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

Die Schicht [ui]


Die Schicht [ui] implementiert die folgende Schnittstelle [IUi]:

1
2
3
4
5
package istia.st.springioc.troistier.ui;

public interface IUi {
    public int doSomethingInUiLayer(int a, int b);
}

Die Implementierung [Ui1] lautet wie folgt:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui1 implements IUi {

    // Schicht [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

Die Implementierung [Ui2] sieht wie folgt aus:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui2 implements IUi {

    // Schicht [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b--;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

Die Spring-Konfigurationsdateien


Die erste: [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- die DAO-Klasse -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
    <!-- die Fachklasse -->
    <bean id="metier"     class="istia.st.springioc.troistier.metier.Metier1">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- die Klasse UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui1">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

Die zweite: [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- die DAO-Klasse -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
    <!-- die Fachklasse -->
    <bean id="metier"
        class="istia.st.springioc.troistier.metier.Metier2">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- die Klasse UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

Die Testprogramme


Das Programm [Main1] lautet wie folgt:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main1 {

    public static void main(String[] args) {
        // wird eine Implementierung der Schnittstelle abgerufen IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
        // die Klasse wird verwendet
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // das Ergebnis wird angezeigt
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

Das Programm [Main1] verwendet die Konfigurationsdatei [spring-config-01.xml] und somit die Implementierungen [Ui1, Metier1, Dao1] der Schichten. Die in der Eclipse-Konsole angezeigten Ergebnisse:

ui(10,20)=34

Das Programm [Main2] lautet wie folgt:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main2 {

    public static void main(String[] args) {
        // wird eine Implementierung der Schnittstelle abgerufen IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
        // wird die Klasse verwendet
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // das Ergebnis wird angezeigt
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

Das Programm [Main2] verwendet die Konfigurationsdatei [spring-config-02.xml] und somit die Implementierungen [Ui2, Metier2, Dao2] der Schichten. Die in der Eclipse-Konsole angezeigten Ergebnisse:

ui(10,20)=-10

15.4. Fazit

Die von uns erstellte Anwendung lässt sich sehr flexibel weiterentwickeln. Die Implementierung einer Schicht lässt sich durch einfache Konfiguration ändern. Der Code der anderen Schichten bleibt dabei unverändert. Dies wird durch das Konzept IoC erreicht, das eine der beiden Säulen von Spring darstellt. Die andere Säule ist AOP (Aspect Oriented Programming), die wir hier nicht vorgestellt haben. Sie ermöglicht es, einer Klassenmethode – ebenfalls per Konfiguration – „Verhalten“ hinzuzufügen, ohne deren Code zu ändern.