Skip to content

15. Spring IoC

15.1. Einführung

Unser Ziel ist es, die Konfigurations- und Integrationsmöglichkeiten des Spring-Frameworks (http://www.springframework.org) zu untersuchen sowie das Konzept von IoC (Inversion of Control), auch bekannt als Dependency Injection, zu definieren und anzuwenden

Betrachten wir die soeben erstellte 3-Tier-Anwendung:

Um auf Benutzeranfragen zu reagieren, muss der [Application]-Controller mit der [service]-Schicht kommunizieren. In unserem Beispiel handelte es sich dabei um eine Instanz vom Typ [DaoImpl]. Der [Application]-Controller hat in seiner [init]-Methode (Abschnitt 14.8.3) eine Referenz auf die [service]-Schicht abgerufen:

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

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

Erinnern Sie sich daran, dass die Klassen [DaoImpl] und [ServiceImpl] Schnittstellen implementieren, nämlich die Schnittstellen [IDao] bzw. [IService]. In zukünftigen Versionen wird die Schnittstelle [IDao] durch eine Klasse implementiert, die eine Liste von Personen verwaltet, die in einer Datenbank gespeichert sind. Nennen wir diese Klasse für dieses Beispiel [DaoBD]. Das Ersetzen der [DaoImpl]-Implementierung der [dao]-Schicht durch die [DaoBD]-Implementierung erfordert eine Neukompilierung der [web]-Schicht. Tatsächlich muss Zeile 6 oben, die die [dao]-Schicht mit einem [DaoImpl]-Typ instanziiert, diese nun mit einem [DaoBD]-Typ instanziieren. Unsere [web]-Schicht ist daher von der [dao]-Schicht abhängig. Zeile 9 oben zeigt, dass sie auch von der [service]-Schicht abhängig ist.

Spring IoC ermöglicht es uns, eine 3-Schichten-Anwendung zu erstellen, bei der die Schichten voneinander unabhängig sind, d. h., die Änderung einer Schicht erfordert keine Änderung der anderen. Dies bietet große Flexibilität bei der Weiterentwicklung der Anwendung.

Die bisherige Architektur wird sich wie folgt weiterentwickeln:

Mit [Spring IoC] bezieht der [Application]-Controller die benötigte Referenz wie folgt aus der [Service]-Schicht:

  1. In seiner [init]-Methode fordert er die [Spring IoC]-Schicht auf, eine Referenz auf die [Service]-Schicht bereitzustellen
  2. [Spring IoC] verwendet dann eine XML-Konfigurationsdatei, die angibt, welche Klasse instanziiert und wie sie initialisiert werden soll.
  3. [Spring IoC] gibt die Referenz auf die erstellte [Service]-Schicht an den [Application]-Controller zurück.

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

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

15.2. Spring IoC in der Praxis

15.2.1. Spring

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

  • [1]: Spring nutzt verschiedene Technologien von Drittanbietern, die hier als Abhängigkeiten bezeichnet werden. Sie müssen die Version mit den Abhängigkeiten herunterladen, um zu vermeiden, dass Sie die Tool-Bibliotheken von Drittanbietern später separat herunterladen müssen.
  • [2]: Die Verzeichnisstruktur der heruntergeladenen ZIP-Datei
  • [3]: Die [Spring]-Distribution, d. h. die .jar-Archive des Spring-Projekts selbst ohne dessen Abhängigkeiten. Der [IoC]-Aspekt von Spring wird durch die Archive [spring-core.jar, spring-beans.jar] bereitgestellt.
  • [4,5]: die Tool-Archive von Drittanbietern

15.2.2. Eclipse-Projekte für die 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-examples] ist so konfiguriert, dass sich die Quelldateien und kompilierten Klassen im Stammverzeichnis des Projektordners befinden:

  • [1]: Die Projektordnerstruktur in [Eclipse]
  • [2]: Die Spring-Konfigurationsdateien befinden sich im Stammverzeichnis des Projekts und somit im Klassenpfad der Anwendung
  • [3]: die Klassen aus Beispiel 1
  • [4]: die Klassen aus Beispiel 2
  • [5]: die Klassen aus Beispiel 3
  • [6]: Die Projektbibliotheken [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 den Klassenpfad der Anwendung aufgenommen.

15.2.3. Beispiel 1

Die Elemente aus Beispiel 1 wurden im Paket [springioc01] des Projekts abgelegt:

Image

Die Klasse [Person] sieht wie folgt aus:

package istia.st.springioc01;

public class Personne {

    // features
    private String nom;
    private int age;

    // person display
    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() + "]");
    }

    // getters-setters
    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, name und age
  • Zeilen 23–38: die get- und set-Methoden für diese beiden Felder
  • Zeilen 10–12: eine toString-Methode, um den Wert des [Person]-Objekts als Zeichenkette abzurufen
  • Zeilen 15–21: 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] 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 von Spring-Konfigurationsdateien. Innerhalb dieses Tags wird das <bean>-Tag verwendet, um die verschiedenen zu erstellenden Objekte zu definieren.
  • Zeilen 4–7: Definition eines Beans
  • Zeile 4: Die Bean heißt [person1] (id-Attribut) und ist eine Instanz der Klasse [istia.st.springioc01.Person] (class-Attribut). Die [init]-Methode der Instanz wird nach ihrer Erstellung aufgerufen (init-method-Attribut), und die [close]-Methode der Instanz wird vor ihrer Löschung aufgerufen (destroy-method-Attribut).
  • Zeile 5: Definiert den Wert, der der Eigenschaft [name] (Attribut „name“) der erstellten Instanz [Person] zugewiesen werden soll. Um diese Initialisierung durchzuführen, verwendet Spring die Methode [setName]. Diese Methode muss daher vorhanden sein. Dies ist hier der Fall.
  • Zeile 6: Wie oben für die Eigenschaft [age].
  • Zeilen 8–11: Ähnliche Definition eines Beans namens [person2]

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) {
        // operation spring configuration file
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
        // recovery of bean [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
        // recovery of the [personne2] bean
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // retrieve the [personne2] bean again
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // we delete all the beans
        bf.destroySingletons();
    }
}

Kommentare:

  • Zeile 10: Um die in der Datei [spring-config-01.xml] definierten Beans abzurufen, verwenden wir ein [XmlBeanFactory]-Objekt, mit dem wir die in einer XML-Datei definierten Beans instanziieren können. Die Datei [spring-config-01.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 [spring-config-01.xml]. Das resultierende [bf]-Objekt (Bean Factory) ermöglicht es uns, mit der Anweisung bf.getBean("XX") eine Referenz auf eine Bean namens „XX“ zu erhalten.
  • Zeile 12: Es wird eine Referenz für die Bean mit dem Namen [person1] in der Datei [spring-config-01.xml] angefordert.
  • Zeile 13: Der Wert des entsprechenden [Person]-Objekts wird angezeigt.
  • Zeilen 15–16: Wir verfahren ebenso für die Bean namens [person2].
  • Zeilen 18–19: Wir fordern die Bean mit dem Namen [person2] erneut an.
  • Zeile 21: Alle Beans in [bf] werden entfernt, d. h. diejenigen, die aus der Datei [spring-config-01.xml] erstellt wurden.

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]]

Kommentare:

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

erzwang die Erstellung des Beans [person1]. Da in der Definition des Beans [person1] [init-method="init"] angegeben war, wurde die Methode [init] des erstellten [Person]-Objekts ausgeführt. Die entsprechende Meldung wird angezeigt.

  • Zeile 2: In Zeile 13 von [Main] wurde der Wert des erstellten [Person]-Objekts angezeigt.
  • Zeilen 3–4: Der gleiche Vorgang wiederholt sich für den Bean namens [person2].
  • Zeile 5: Der Vorgang 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 [Person] geführt. Wäre dies der Fall gewesen, wäre die Methode [init] angezeigt worden. Dies ist das Singleton-Prinzip. Standardmäßig erstellt Spring nur eine einzige Instanz der Beans in seiner Konfigurationsdatei. Es handelt sich um einen Objektreferenzdienst. Wird eine Referenz auf ein Objekt angefordert, das noch nicht erstellt wurde, erstellt er es und gibt eine Referenz zurück. Ist das Objekt bereits erstellt, gibt Spring einfach eine Referenz darauf zurück. Da [person2] hier bereits erstellt wurde, gibt Spring einfach eine Referenz darauf zurück.

  • Die Ausgabe in den Zeilen 6–7 wurde durch Zeile 21 von [Main] ausgelöst, die die Zerstörung aller Beans anfordert, auf die das Objekt [XmlBeanFactory bf] verweist, nämlich die Beans [person1] und [person2]. Da diese beiden Beans das Attribut [destroy-method="close"] haben, wird die Methode [close] beider Beans ausgeführt, wodurch die Zeilen 6–7 angezeigt werden.

Nachdem wir nun die Grundlagen einer Spring-Konfiguration behandelt haben, können wir unsere Erläuterungen etwas schneller durchgehen.

15.2.4. Beispiel 2

Die Elemente von 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, dann wird die Klasse [Car] hinzugefügt und die Klasse [Main] an das neue Beispiel angepasst.

Die Klasse [Car] sieht wie folgt aus:

package istia.st.springioc02;

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

    // manufacturers
    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 + "]";
    }

    // getters-setters
    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: type, make und owner. Diese Felder können mithilfe der öffentlichen get- und set-Methoden in den Zeilen 26–48 initialisiert und ausgelesen werden. Sie können auch mithilfe des in den Zeilen 13–17 definierten Konstruktors Car(String, String, Person) initialisiert werden. Die Klasse verfügt außerdem über einen Konstruktor ohne Argumente, um dem JavaBean-Standard zu entsprechen.
  • Zeilen 20–23: eine toString-Methode, um den Wert des [Car]-Objekts als String abzurufen
  • Zeilen 51–57: eine `init`-Methode, die von Spring unmittelbar nach der Erstellung des Objekts aufgerufen wird, und eine `close`-Methode, die beim Löschen des Objekts aufgerufen wird

Um Objekte vom Typ [Car] 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 (Zeilen 12–17) eine Bean mit dem Schlüssel „car1“ vom Typ [Car] hinzu. Um diese Bean zu initialisieren, hätten wir 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 die bereits in Beispiel 1 vorgestellte Methode zu wählen, haben wir uns hier für den Konstruktor Car(String, String, Person) der Klasse entschieden.

  • Zeile 12: Definition des Namens des Beans, seiner Klasse, der nach seiner Instanziierung auszuführenden Methode und der nach seiner Zerstörung auszuführenden Methode.
  • Zeile 13: Wert des ersten Parameters des Konstruktors [Car(String, String, Person)].
  • Zeile 14: Wert des zweiten Parameters des Konstruktors [Car(String, String, Person)].
  • Zeilen 15–17: Wert des dritten Parameters des Konstruktors [Car(String, String, Person)]. Dieser Parameter ist vom Typ [Person]. Wir übergeben ihm die Referenz (ref-Tag) der Bean [person2], die in derselben Datei definiert ist (lokales Attribut).

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

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) {
        // operation spring configuration file
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
        // recovery of bean [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
        // delete the beans
        bf.destroySingletons();
    }
}

Die Methode [main] ruft die Referenz auf die Bean [car1] ab (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]]

Kommentare:

  1. Die [main]-Methode fordert eine Referenz auf die [car1]-Bean an (Zeile 12). Spring beginnt mit der Erstellung der [car1]-Bean, da diese Bean noch nicht erstellt wurde (Singleton). Da die [car1]-Bean auf die [person2]-Bean verweist, wird diese wiederum erstellt. Die [person2]-Bean wurde erstellt. Anschließend wird ihre [init]-Methode ausgeführt (Zeile 1 der Ergebnisse). Dann wird die [car1]-Bean instanziiert. Anschließend wird ihre [init]-Methode ausgeführt (Zeile 2 der Ergebnisse).
  2. Zeile 3 der Ergebnisse stammt aus Zeile 13 von [main]: Der Wert der Bean [car1] wird angezeigt.
  3. Zeile 15 von [main] fordert die Zerstörung aller vorhandenen Beans an, was dazu führt, dass die Zeilen 4 und 5 der Ergebnisse angezeigt werden.

15.2.5. Beispiel 3

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

Image

Das Paket [springioc03] wird zunächst durch Kopieren und Einfügen des Pakets [springioc01] erstellt, dann wird die Klasse [GroupePersonnes] hinzugefügt, die Klasse [Voiture] entfernt und die Klasse [Main] an das neue Beispiel angepasst.

Die Klasse [GroupePersonnes] sieht wie folgt aus:

package istia.st.springioc03;

import java.util.Map;

public class GroupePersonnes {

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

    // getters - setters
    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;
    }

    // display
    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() + "]");
    }
}

Seine beiden privaten Mitglieder sind:

Zeile 8: members: ein Array von Personen, die Mitglieder der Gruppe sind

Zeile 9: workingGroups: ein Wörterbuch, das eine Person einer Arbeitsgruppe zuordnet

Beachten Sie hier, dass die Klasse [PersonGroup] keinen Konstruktor ohne Argumente definiert. 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 hier ist es, zu zeigen, wie Spring es Ihnen ermöglicht, komplexe Objekte zu initialisieren, beispielsweise solche mit Array- oder Dictionary-Feldern. Die Bean-Datei [spring-config-03.xml] für 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 <list>-Tag können Sie ein Feld vom Typ Array oder ein Feld, das die List-Schnittstelle implementiert, mit verschiedenen Werten initialisieren.
  • Zeilen 20–23: Mit dem <map>-Tag können Sie dasselbe mit einem Feld tun, das die Map-Schnittstelle implementiert.

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

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) {
        // operation spring configuration file
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
        // bean retrieval [group1]]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
        // delete the beans
        bf.destroySingletons();
    }
}
  • Zeilen 12–13: Wir fordern von Spring eine Referenz auf die Bean [group1] an und zeigen deren Wert an.

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]]

Kommentare:

  • In Zeile 12 von [Main] wird eine Referenz auf die Bean [group1] angefordert. Spring beginnt mit der Erstellung dieser Bean. Da die Bean [group1] auf die Beans [person1] und [person2] verweist, werden diese beiden Beans erstellt (Zeilen 1 und 2 der Ergebnisse). Anschließend wird die Bean [group1] 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] zeigt die Zeilen 5–7 der Ergebnisse an.

15.3. Konfigurieren einer n-Tier-Anwendung mit Spring

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

UserDataBusinessLayer [business]DataAccessLayer [dao]UserInterfaceLayer [ui]

Hier möchten wir die Vorteile der Verwendung 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 wird von Spring übernommen

Die Anwendungsstruktur in Eclipse könnte wie folgt aussehen:

  • [1]: die [DAO]-Schicht:
    • [IDao]: die Schnittstelle der Schicht
    • [Dao1, Dao2]: zwei Implementierungen dieser Schnittstelle
  • [2]: die [Business]-Schicht:
    • [IMetier]: die Schnittstelle der Schicht
    • [Business1, Business2]: zwei Implementierungen dieser Schnittstelle
  • [3]: die [UI]-Schicht:
    • [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 von der Anwendung benötigten Bibliotheken. Dies sind die in den vorherigen Beispielen verwendeten.
  • [6]: das Testpaket. [Main1] verwendet die Konfiguration [spring-config-01.xml] und [Main2] die Konfiguration [spring-config-02.xml].

Der Zweck 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 [dao]-Schicht


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

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 [Business]-Schicht


Die [Business]-Schicht implementiert die folgende [IMetier]-Schnittstelle:

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

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

Die Implementierung von [Business1] sieht wie folgt aus:

package istia.st.springioc.troistier.metier;

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

public class Metier1 implements IMetier {

    // layer [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 [Metier2]-Implementierung sieht wie folgt aus:

package istia.st.springioc.troistier.metier;

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

public class Metier2 implements IMetier {

    // layer [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 [ui]-Ebene


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

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

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

Die Implementierung [Ui1] sieht wie folgt aus:

package istia.st.springioc.troistier.ui;

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

public class Ui1 implements IUi {

    // business] layer
    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 {

    // business] layer
    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);
    }

}

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>
    <!-- the dao class -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
    <!-- the business class -->
    <bean id="metier"     class="istia.st.springioc.troistier.metier.Metier1">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- the UI class -->
    <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>
    <!-- the dao class -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
    <!-- the business class -->
    <bean id="metier"
        class="istia.st.springioc.troistier.metier.Metier2">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- the UI class -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

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) {
        // we retrieve an implementation of the IUi interface
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
        // we use the
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // the result is displayed
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

Das Programm [Main1] verwendet die Konfigurationsdatei [spring-config-01.xml] und damit 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) {
        // we retrieve an implementation of the IUi interface
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
        // we use the
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // the result is displayed
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

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

ui(10,20)=-10

15.4. Fazit

Die von uns entwickelte Anwendung ist hochgradig skalierbar. Wir können die Implementierung einer Schicht einfach durch Konfiguration ändern. Der Code für die anderen Schichten bleibt unverändert. Dies wird durch das IoC-Konzept erreicht, das eine der beiden Säulen von Spring darstellt. Die andere Säule ist AOP (Aspect-Oriented Programming), auf die wir hier nicht eingegangen sind. Es ermöglicht Ihnen, einer Klassenmethode – ebenfalls über Konfiguration – „Verhalten“ hinzuzufügen, ohne den Code der Methode zu ändern.