5. [Kurs]: Einführung in das Spring Framework
Stichworte: mehrschichtige Architektur, Spring, Dependency Injection.
Spring erschien erstmals 2004 als Objektcontainer. Seitdem hat es sich in mehrere Zweige weiterentwickelt: Spring MVC, Spring Data, Spring Batch, ... [http://spring.io]. In diesem Kapitel konzentrieren wir uns ausschließlich auf den Objektcontainer. Hier sind einige wichtige Punkte:
- Eine Anwendung besteht aus mehreren Klassen, von denen einige Objekte gemeinsam nutzen, die eindeutig sein müssen (Singletons). Spring erstellt und verwaltet diese Singletons;
- Spring platziert diese Singletons in einer Struktur, die als Kontext bezeichnet wird;
- Klassen greifen auf die Singletons der Anwendung zu, indem sie diese über ihren Namen, ihren Typ oder beides bei Spring anfordern;
- Spring erstellt die Singletons und verwaltet alle Abhängigkeiten, die sie möglicherweise haben: Ein Singleton kann tatsächlich Referenzen auf ein oder mehrere andere Singletons enthalten. Wenn Spring ein Singleton erstellt, erstellt es auch dessen Abhängigkeiten;
- Wenn eine auf Spring basierende Anwendung gestartet wird, kann sie Spring auffordern, alle Singletons der Anwendung zu erstellen. Diese stehen dann im Spring-Kontext zur Verfügung;
- Spring vereinfacht die Verwendung von mehrschichtigen Architekturen und die schnittstellenbasierte Programmierung. In einfachen Fällen wird jede Schicht als Singleton implementiert und implementiert eine Schnittstelle. Wenn die Anwendung mit den Schnittstellen der Schichten statt mit deren Implementierungsklassen arbeitet, entsteht eine skalierbare Architektur, die es Ihnen dank der folgenden zwei Funktionen ermöglicht, die Implementierung einer Schicht zu ändern, ohne die anderen zu beeinträchtigen:
- Die Anwendung erhält über den Namen eine Referenz auf die Schicht. Spring stellt ihr eine Referenz auf die Klasse bereit, die die Schicht implementiert;
- die Anwendung verwendet diese Referenz als die der Schnittstelle der Schicht und nicht als die einer Klasse;
Singletons können auf drei Arten deklariert werden, die kombiniert werden können:
- innerhalb einer XML-Datei,
- in einer speziellen Konfigurationsklasse;
- mit einer beliebigen Klasse unter Verwendung von Annotationen;
Im Folgenden stellen wir drei Konfigurationsbeispiele vor:
- [Beispiel-01]: Zentralisierte Konfiguration in einer einzigen XML-Datei;
- [Beispiel-02]: Zentralisierte Konfiguration in einer einzigen Java-Klasse;
- [Beispiel-03]: Konfiguration verteilt auf mehrere Java-Klassen;
Das letzte Beispiel [Beispiel-04] konzentriert sich auf die Spring-Konfiguration einer mehrschichtigen Architektur. Dies ist das wichtigste Beispiel. Es wird durchgehend verwendet, um die Architekturen in diesem Dokument zu konfigurieren.
Diese vier Beispiele bilden die Grundlage für das Folgende:
- Spring-Konfiguration und Dependency Injection;
- Verwendung von Maven zur Verwaltung der Abhängigkeiten eines Projekts;
- Verwendung von JUnit zum Testen von Projekten;
5.1. Support
![]() | ![]() |
Der Ordner [support / chap-05] enthält die Eclipse-Projekte für dieses Kapitel.
5.2. Beispiel-01
5.2.1. Das Eclipse-Projekt
![]() |
5.2.2. Die Klasse [Person]
![]() |
package istia.st.spring.core;
public class Personne {
// fields
private String nom;
private String prenom;
private int age;
// manufacturers
public Personne() {
}
public Personne(String nom, String prénom, int âge) {
this.nom = nom;
this.prenom = prénom;
this.age = âge;
}
// toString
public String toString() {
return String.format("Personne[%s, %s,%d]", prenom, nom, age);
}
// getters and setters
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Hinweis: Getter und Setter können wie folgt automatisch generiert werden [1-2]:
![]() |
5.2.3. Die Klasse [Apartment]
![]() |
package istia.st.spring.core;
public class Appartement {
// fields
private Personne proprietaire;
private int surface;
// getters and setters
public Personne getProprietaire() {
return proprietaire;
}
public void setProprietaire(Personne proprietaire) {
this.proprietaire = proprietaire;
}
public int getSurface() {
return surface;
}
public void setSurface(int surface) {
this.surface = surface;
}
// toString
public String toString() {
return String.format("Appartement[%s, %s]", proprietaire, surface);
}
}
Hinweis: Diese Klasse verfügt über keinen expliziten Konstruktor. In diesem Fall existiert immer der Standardkonstruktor ohne Parameter, der keine Aktion ausführt. Wenn Sie Konstruktoren erstellen, existiert dieser Standardkonstruktor nicht mehr implizit. Sie müssen ihn dann explizit definieren:
5.2.4. Die Spring-Konfigurationsdatei
![]() |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- Person 01 -->
<bean id="personne_01" class="istia.st.spring.core.Personne">
<constructor-arg index="0" value="dubois" />
<constructor-arg index="1" value="paul" />
<constructor-arg index="2" value="34" />
</bean>
<!-- Person 02 -->
<bean id="personne_02" class="istia.st.spring.core.Personne">
<property name="nom" value="martin" />
<property name="prenom" value="micheline" />
<property name="age" value="18" />
</bean>
<!-- a list of people -->
<util:list id="club">
<ref bean="personne_01" />
<ref bean="personne_02" />
</util:list>
<!-- an apartment -->
<bean id="appartement" class="istia.st.spring.core.Appartement">
<property name="surface" value="100" />
<property name="proprietaire" ref="personne_01" />
</bean>
</beans>
- Zeilen 2, 27: Singletons werden innerhalb eines <beans>-Tags definiert;
- Zeilen 6–10: Jedes Singleton wird durch ein <bean>-Tag definiert;
- Zeile 6: [id] ist die Kennung des Singletons. [class] ist der vollständige Name der zu instanziierenden Klasse;
- Zeilen 7–9: die drei Werte, die an den Konstruktor der Klasse [Person] übergeben werden sollen;
- Zeilen 12–16: Die Klasse [Person] wird zunächst mit ihrem Standardkonstruktor [new Person()] erstellt. Anschließend wird für jedes [property]-Tag eine Setter-Methode der Klasse verwendet. In Zeile 13 wird beispielsweise die Methode [setName("martin")] ausgeführt. Die Methode [setName] muss daher vorhanden sein. Dies ist ein wichtiger Punkt, den man beachten sollte;
- Zeilen 18–21: Das <util:list>-Tag wird verwendet, um ein Singleton zu definieren, das eine Liste ist;
- Zeile 19: verweist auf das in Zeile 6 definierte Singleton [person_01]. Dies wird als Dependency Injection bezeichnet. Zwei Attribute können verwendet werden, um das Feld eines Singletons zu initialisieren:
- [value]: um dem Feld einen primitiven Wert (Zeichenkette, Zahl, Datum usw.) zuzuweisen,
- [ref]: um dem Feld die Referenz eines Spring-Objekts zuzuweisen;
Hinweis: Die Spring-Konfigurationsdatei kann wie folgt generiert werden [1-4]:
![]() |
5.2.5. Die ausführbare Klasse
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo01 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// spring context retrieval
ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
// beans are recovered
Personne p01 = ctx.getBean("personne_01", Personne.class);
Personne p02 = ctx.getBean("personne_02", Personne.class);
List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
Appartement appart01 = ctx.getBean(Appartement.class);
// we display them
System.out.println("personnes--------");
System.out.println(p01);
System.out.println(p02);
System.out.println("club--------");
for (Personne p : club) {
System.out.println(p);
}
System.out.println("appartement--------");
System.out.println(appart01);
// recovered beans are singletons
// they can be requested several times, but the same bean is always retrieved
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- Zeile 14: Erstellt den Spring-Kontext. Alle in der Datei [config-01.xml] definierten Singletons werden anschließend instanziiert;
- Zeile 16: Fordert eine Referenz auf das durch [person_01] identifizierte Singleton vom Typ [Person] an. Dieser zweite Parameter ist optional, wird er jedoch weggelassen, wird eine Referenz vom Typ [Object] zurückgegeben, die dann in den Typ [Person] umgewandelt werden muss;
- Zeile 19: Wir verwenden nicht den Namen des Beans, sondern nur dessen Typ, da es nur ein Singleton vom Typ [Apartment] gibt;
- Zeile 18: Sowohl der Bezeichner als auch der Typ des gewünschten Singletons werden verwendet. Der Bezeichner ist redundant, da es nur ein Singleton vom Typ [new ArrayList<Person>().getClass()] gibt;
- Zeilen 32–33: zeigen, dass wir, wenn wir dasselbe Singleton mehrmals anfordern, immer dieselbe Referenz erhalten, was belegt, dass es sich tatsächlich um ein Singleton handelt. Dieser Punkt ist wichtig zu verstehen;
Hinweis: Eine ausführbare Klasse kann wie folgt generiert werden [1–6]:
![]() |
![]() |
- Durch das Aktivieren von [6] wird sichergestellt, dass die generierte Klasse eine statische Methode [main] enthält, wodurch sie ausführbar wird;
5.2.6. Projektabhängigkeiten
![]() |
- Spring-Abhängigkeiten: [spring-core, spring-beans, spring-context, spring-expression, commons-logging];
Die Abhängigkeiten werden wie folgt zum Projekt hinzugefügt:
![]() |
- in [1]: Rechtsklick auf das Projekt / [Build Path] / [Configure Build Path];
![]() |
- in [2]: [JARs hinzufügen], wenn sich die hinzuzufügenden JARs in einem Projektordner befinden. Andernfalls [Externe JARs hinzufügen];
![]() |
- Wählen Sie in [3] die JARs aus, die dem ClassPath des Projekts hinzugefügt werden sollen (hier befinden sie sich im Ordner [lib] innerhalb des Projekts);
Definition: Der [ClassPath] eines Projekts ist die Menge der Ordner, die von der JVM (Java Virtual Machine), die das Projekt ausführt, durchsucht werden, um eine von diesem Projekt referenzierte Klasse zu finden. Bei einem Eclipse-Projekt besteht der [ClassPath] aus den folgenden Elementen:
- dem [bin]-Ordner des Projekts;
- die Elemente des [Build Path] des Projekts;
Der Ordner [bin] ist der Ordner, der durch die Kompilierung des Ordners [src] entsteht. Daher ist alles, was sich im Ordner [src] befindet, automatisch Teil des [ClassPath] (auch wenn es sich nicht um eine .java-Datei handelt). Somit ist im vorherigen Projekt die Spring-Konfigurationsdatei [config-01.xml], die sich im Ordner [src] befindet, zur Laufzeit Teil des [ClassPath] des Projekts.
5.2.7. Ergebnisse
5.3. Beispiel-02
5.3.1. Das Eclipse-Projekt
![]() |
5.3.2. Die Spring-Konfigurationsklasse
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public Personne personne_01() {
return new Personne("Paul", "Dubois", 34);
}
@Bean
public Personne personne_02() {
return new Personne("Martin", "Micheline", 18);
}
@Bean
public List<Personne> club(Personne personne_01, Personne personne_02) {
List<Personne> personnes = new ArrayList<Personne>();
personnes.add(personne_01);
personnes.add(personne_02);
return personnes;
}
@Bean
public Appartement appartement(Personne personne_01) {
Appartement appartement = new Appartement();
appartement.setSurface(200);
appartement.setPropriétaire(personne_01);
return appartement;
}
}
- Zeile 9: Die Annotation [@Configuration] ist eine Spring-Annotation. Sie gibt an, dass die mit ihr versehene Klasse Singletons definiert. Diese werden mithilfe der Annotation [@Bean] definiert. Spring führt alle mit [@Bean] annotierten Methoden aus. Diese erstellen die Singletons der Anwendung;
- Zeilen 12–15: Definiert ein Singleton, das durch [person_01] identifiziert wird, d. h. den Methodennamen.
- Zeile 23: Die Parameter [person_01, person_02] tragen die Namen von Singletons. Spring initialisiert sie automatisch mit den Referenzen dieser Singletons. Dies wird als Parameterinjektion bezeichnet;
Diese Art der Konfiguration von Singletons ist expliziter als diejenige, die die XML-Datei verwendet. Tatsächlich replizieren wir hier, was Spring implizit auf Basis der XML-Datei getan hat.
5.3.3. Die ausführbare Klasse
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo02 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// spring context retrieval
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
// beans are recovered
Personne p01 = ctx.getBean("personne_01", Personne.class);
Personne p02 = ctx.getBean("personne_02", Personne.class);
List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
Appartement appart01 = ctx.getBean(Appartement.class);
// we display them
System.out.println("personnes--------");
System.out.println(p01);
System.out.println(p02);
System.out.println("club--------");
for (Personne p : club) {
System.out.println(p);
}
System.out.println("appartement--------");
System.out.println(appart01);
// recovered beans are singletons
// they can be requested several times, but the same bean is always retrieved
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- Zeile 13 bewirkt, dass alle in der Klasse [Config] definierten Beans instanziiert werden;
- der Rest des Codes bleibt unverändert;
5.3.4. Projektabhängigkeiten
![]() | ![]() |
Die Abhängigkeiten werden durch die folgende [pom.xml]-Datei definiert:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-core-02</name>
<description>Introduction à Spring</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Die manuelle Verwaltung der Abhängigkeiten eines Projekts wird zu einer Herausforderung, wenn Java-Bibliotheken verwendet werden, deren Abhängigkeiten unbekannt sind. Das [Hibernate]-Framework, das den Datenbankzugriff verwaltet, hat beispielsweise Dutzende von Abhängigkeiten. Das [Maven]-Projekt löst dieses Problem. Man gibt die benötigte Abhängigkeit an. Diese wird automatisch in den über das Internet verteilten Maven-Repositorys gesucht. Wenn die angeforderte Abhängigkeit selbst Abhängigkeiten hat, werden diese ebenfalls automatisch heruntergeladen. Diese heruntergeladenen Abhängigkeiten werden in einem lokalen Repository auf dem Rechner gespeichert. Wenn eine andere Anwendung später dieselbe Abhängigkeit benötigt, wird sie nicht erneut heruntergeladen, sondern aus dem lokalen Repository abgerufen. Eine Abhängigkeit ist durch die folgenden Elemente gekennzeichnet:
- Zeile 17: ein <dependency>-Tag;
- Zeile 18: ein [groupId]-Attribut, das in der Regel das Unternehmen identifiziert, das die Abhängigkeit erstellt hat;
- Zeile 19: ein [artifactId]-Attribut, das die Abhängigkeit identifiziert;
- Zeile 20: ein [version]-Attribut, das die gewünschte Version identifiziert;
Durch das Generieren des Projekts wird ein Maven-Artefakt erzeugt, das durch die Zeilen 4–8 definiert ist:
- Zeilen 4–6: die Attribute [groupId, artifactId, version], die wir gerade beschrieben haben;
- Zeilen 7–8: sind optionale Attribute;
Wir werden etwas später auf die Rolle der Zeilen 24–40 zurückkommen. Um ein Standard-Eclipse-Projekt in ein Maven-Projekt umzuwandeln, müssen Sie zwei Dinge tun:
- die oben gezeigte [pom.xml]-Datei erstellen;
- erklären, dass das Projekt nun ein Maven-Projekt ist [1–4]:
![]() |
Das Symbol für ein Maven-Projekt zeigt ein M [4]. Das S weist darauf hin, dass das Projekt Spring-Komponenten enthält. Es wird nicht empfohlen, ein Eclipse-Projekt (wie wir es gerade getan haben) in ein Maven-Projekt zu konvertieren, da dem Projekt dann die für ein Maven-Projekt erwartete Struktur fehlt, was manchmal zu unerwarteten Problemen führen kann.
5.3.5. Erstellen des Maven-Artefakts des Projekts
Wir bezeichnen das in den Zeilen 4–6 der Datei [pom.xml] definierte Element als das Maven-Artefakt des Projekts:
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
Um dieses Artefakt zu generieren, müssen die folgenden Zeilen 3–7 in der Datei [pom.xml] enthalten sein:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
Diese definieren das Maven-Plugin, das das Projekt-Artefakt generieren kann. Wir gehen dann wie folgt vor:
![]() |
Das auf diese Weise generierte Artefakt wird in das lokale Maven-Repository aufgenommen. Sein Speicherort ist in der Eclipse-Konfiguration zu finden:
![]() |
Anschließend können Sie überprüfen, ob das Maven-Artefakt korrekt installiert wurde:
![]() |
Von nun an kann ein anderes lokales Maven-Projekt dieses Archiv verwenden.
5.4. Beispiel-03
5.4.1. Das Eclipse-Projekt
Dieses Mal erstellen wir ein Maven-Projekt [1-8]:
![]() |
![]() |
- in [3b]: Geben Sie einen leeren Ordner an, in dem das Projekt erstellt werden soll;
![]() |
- in [4]: die Kennung der Maven-Gruppe, zu der das Projekt gehören soll;
- in [5]: den Namen des generierten Maven-Artefakts:
- in [6]: dessen Version;
- in [7]: sein Verpackungsformat (andere Formate sind z. B. war, ear, apk usw.);
- in [8]: das so erstellte Projekt;
Standardmäßig hat ein Maven-Projekt eine bestimmte Verzeichnisstruktur:
- [src/main/java]: der Quellcode des Projekts. Die kompilierte Ausgabe dieser Quellen wird in den Ordner [target/classes] des Projekts verschoben;
- [src/main/resources]: Ressourcen, die im Klassenpfad des Projekts enthalten sein müssen, aber keine Java-Quelldateien sind. Sie werden unverändert in den Ordner [target/classes] des Projekts kopiert;
- [src/test/java]: der Quellcode für die Tests des Projekts. Die kompilierten Ergebnisse dieser Quellen werden in den Ordner [target/test-classes] des Projekts verschoben. Diese Elemente sind nicht im Maven-Archiv des Projekts enthalten;
- [src/test/resources]: Ressourcen, die sich für Testzwecke im Klassenpfad des Projekts befinden müssen, aber keine Java-Quelldateien sind. Sie werden unverändert in den Ordner [target/test-classes] des Projekts kopiert;
Wir führen das Projekt wie folgt durch:
![]() |
5.4.2. Die Maven-Konfiguration
Standardmäßig wird eine [pom.xml]-Datei generiert. Wir ändern sie wie folgt:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-core-03</name>
<description>Introduction à Spring</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<!-- maven parent project -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- logs -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<!-- to generate the project archive with its dependencies -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- to install the project artifact in the local Maven repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- Zeile 11: Das Projekt ist in UTF-8 kodiert;
- Zeile 12: Zum Kompilieren des Projekts wird JDK 1.8 verwendet;
- Zeilen 16–20: Für Projekte, die Spring-Bibliotheken verwenden, ist es praktisch, ein übergeordnetes Maven-Projekt namens [spring-boot-starter-parent] zu verwenden. Dieses definiert die Versionen verschiedener Spring-Bibliotheken sowie deren Abhängigkeiten. Dadurch entfällt die Notwendigkeit, diese im Abschnitt „dependencies“ zu definieren. Daher geben wir in den Zeilen 24–27 die gewünschte Version von [spring-context] nicht an. Es wird diejenige sein, die vom übergeordneten Projekt [spring-boot-starter-parent] definiert wurde. Diese Vorgehensweise macht es überflüssig, sich um mögliche Versionsinkompatibilitäten zwischen den Abhängigkeiten zu sorgen. Die vom übergeordneten Projekt definierten Versionen sind untereinander kompatibel;
- Zeilen 29–32: Spring schreibt über eine Logging-Bibliothek eine beträchtliche Menge an Informationen in die Konsole. Diese Bibliothek wird hier importiert;
- Zeilen 40–47: ein Maven-Plugin, auf das wir später noch eingehen werden;
- Zeilen 50–52: das Plugin zur Generierung des Maven-Artefakts des Projekts;
5.4.3. Die Spring-Konfigurationsklasse
![]() |
Die [Config]-Klasse sieht wie folgt aus:
package istia.st.spring.core.config;
import istia.st.spring.core.entities.Personne;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {
@Bean
public Personne personne_01() {
return new Personne("Paul", "Dubois", 34);
}
@Bean
public Personne personne_02() {
return new Personne("Martin", "Micheline", 18);
}
@Bean
public List<Personne> club(Personne personne_01, Personne personne_02) {
List<Personne> personnes = new ArrayList<Personne>();
personnes.add(personne_01);
personnes.add(personne_02);
return personnes;
}
@Bean
public int mySurface() {
return 200;
}
}
- Hier sehen wir bereits kommentierten Code mit zwei neuen Ergänzungen:
- Zeile 13: gibt an, dass es weitere Beans im Paket [spring.core.entities] gibt, die instanziiert werden müssen,
- Zeilen 34–37: eine [mySurface]-Bean;
5.4.4. Die Klasse [Apartment]
![]() |
package spring.core.entities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class Appartement {
// fields injected by Spring
@Autowired
@Qualifier("personne_01")
private Personne propriétaire;
@Autowired
@Qualifier("mySurface")
private int surface;
// getters and setters
public Personne getPropriétaire() {
return propriétaire;
}
public void setPropriétaire(Personne propriétaire) {
this.propriétaire = propriétaire;
}
public int getSurface() {
return surface;
}
public void setSurface(int surface) {
this.surface = surface;
}
// toString
public String toString() {
return String.format("Appartement[%s, %s]", propriétaire, surface);
}
}
- Zeile 7: Die Annotation [@Component] teilt Spring mit, dass es sich bei der Klasse um ein Singleton handelt, das das Framework instanziieren und verwalten muss. Dieses Singleton wird gefunden, da wir in der Klasse [Config] [@ComponentScan({ "istia.st.spring.core.entities" })] geschrieben haben;
- Zeile 11: Fordert Spring auf, die Referenz eines der Singletons in das Feld zu injizieren. Dies kann auf zwei Arten definiert werden:
- über seine Kennung (Zeilen 12, 16),
- über seinen Typ, wenn es nur ein Singleton dieses Typs gibt;
5.4.5. Ausführen des Projekts
Das Ausführen des Projekts erzeugt die folgende Ausgabe in der Konsole:
- Zeilen 1–3: Spring erzeugt eine sehr große Anzahl von Log-Einträgen, mehrere Dutzend Zeilen. Diese Log-Einträge können bei der Fehlersuche in einem Projekt, das nicht funktioniert, sehr hilfreich sein. Wenn das Projekt funktioniert, können Sie die Log-Einträge wie folgt reduzieren:
![]() |
Erstellen Sie im Ordner [src/main/resources] die folgende Datei [logback.xml]:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- log level control -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
- Zeile 12 legt die Protokollstufe fest. [debug] ist eine sehr detaillierte Stufe, [info] hingegen deutlich weniger;
Hier sind die Ergebnisse mit [level=info]:
Es gibt jetzt nur noch eine Zeile im Protokoll.
5.4.6. Erstellen des Projektarchivs mit seinen Abhängigkeiten
Das im vorherigen Projekt erstellte Archiv kann auch von einem Nicht-Maven-Eclipse-Projekt verwendet werden. Manche Projekte nutzen viele Bibliotheken, und es kann schwierig sein, keine davon zu vergessen. Hier leistet Maven hervorragende Arbeit, da man nur die oberste Abhängigkeit benennen muss, damit die untergeordneten automatisch zum Classpath des Projekts hinzugefügt werden. Wenn ein nicht-Maven-Eclipse-Projekt die Archive eines Maven-Projekts verwenden muss, ist es möglich, das Artefakt des letzteren mit all seinen Abhängigkeiten zu generieren (was im vorherigen Projekt nicht der Fall war). Für diese Generierung müssen die folgenden Zeilen 3–10 in der [pom.xml]-Datei vorhanden sein:
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
Diese definieren das Maven-Plugin, das das Projekt-Artefakt zusammen mit seinen Abhängigkeiten generieren kann. Fahren Sie anschließend wie folgt fort [1-6]:
![]() |
![]() |
- [4-6] stellen eine Maven-Build-Konfiguration dar;
- Geben Sie in [4] einen beliebigen Namen ein;
- Geben Sie in [5] den Projektordner an;
- Geben Sie in [6] die Maven-Ziele ein:
- [clean]: Der [target]-Ordner des Projekts wird gelöscht;
- [compile]: Das Projekt wird kompiliert. Die Kompilierungsergebnisse werden in einem neu erstellten [target]-Ordner abgelegt;
- [assembly:single]: Die Klassen des Projekts und seine Abhängigkeiten werden in einem einzigen JAR-Archiv im Ordner [target] abgelegt;
Nach der Ausführung erhält man das folgende Ergebnis:
![]() |
Ein JAR-Archiv ist eine komprimierte Datei, die mit einem Entpacker geöffnet werden kann. Nach dem Entpacken des obigen Archivs ergibt sich folgende Verzeichnisstruktur:
- in [8] die Klassen der Projektabhängigkeiten;
- in [9] die Klassen des Projekts selbst;
5.5. Beispiel-04
5.5.1. Ziel
Dieses Beispiel basiert auf einem Beispiel aus dem Dokument [Einführung in Spring IoC], das den Beitrag von Spring zur Konfiguration mehrschichtiger Architekturen veranschaulicht. Im Originaldokument verwendet das Beispiel eine in einer XML-Datei definierte Spring-Konfiguration. Hier implementieren wir das Beispiel unter Verwendung einer Konfiguration, die auf Java-Klassen und Annotationen basiert.
Hier möchten wir ein Spring-Projekt für die folgende Architektur konfigurieren:
![]() |
Jede Schicht verfügt über eine Schnittstelle, die von zwei Klassen implementiert wird. Wir möchten zeigen, dass wir dank Spring die Implementierung einer Schicht ändern können, ohne dass dies Auswirkungen auf den Code der anderen Schichten hat.
5.5.2. Das Eclipse-Projekt
5.5.2.1. Erstellung
Wir erstellen einen neuen Projekttyp:
![]() |
![]() |
- Geben Sie unter [4] den Namen des Eclipse-Projekts ein;
- Wählen Sie unter [5] ein Maven-Projekt aus;
- Wählen Sie in [6] eine Java-Version >=1.7 aus;
- Wählen Sie in [7] die vorgeschlagene Spring Boot-Version aus;
- Die Angaben in [8–11] sind Maven-Informationen;
- In [12] können Sie eine oder mehrere der vorgeschlagenen Abhängigkeiten auswählen. Dadurch werden eine Reihe von Abhängigkeiten zur Maven-Datei [pom.xml] hinzugefügt;
![]() |
- Wählen Sie in [13] einen vorhandenen, leeren Ordner als Speicherort für das Projekt aus;
- in [14] das generierte Projekt. Wir werden dessen Komponenten aufschlüsseln;
Das Projekt ist ein Maven-Projekt, das durch die folgende [pom.xml]-Datei konfiguriert wird:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-core-04</name>
<description>Programmation par interfaces</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.SpringCore04Application</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- Zeilen 6–12: enthalten die im Projekt-Erstellungsassistenten eingegebenen Informationen;
- Zeilen 14–19: das übergeordnete Maven-Projekt, das eine Reihe von Bibliotheken samt deren Versionen definiert. Sind einige davon Projektabhängigkeiten, werden sie in der [pom.xml]-Datei ohne ihre Versionen aufgeführt;
- Zeile 23: Diese Zeile wird nur verwendet, wenn Sie beabsichtigen, ein ausführbares Archiv des Projekts zu erstellen. Andernfalls bleibt sie ungenutzt;
- Zeilen 28–31: die minimalen Abhängigkeiten für ein Spring-Boot-Projekt. Beachten Sie, dass wir keine Abhängigkeiten aus der Checkbox-Liste ausgewählt haben;
- Zeilen 33–37: Die Abhängigkeit, die zur Verwaltung von JUnit-Unit-Tests [http://junit.org/] in Verbindung mit Spring erforderlich ist. Zeile 36 gibt an, dass die Abhängigkeit nur für Testzwecke benötigt wird. Folglich wird sie nicht in das Projektarchiv aufgenommen;
- Zeilen 42–45: das Plugin, das das Maven-Artefakt des Projekts generiert;
Die Liste der von dieser Datei bereitgestellten Abhängigkeiten lautet wie folgt [1]:
![]() |
Wir werden sehen, dass diese für das, was wir hier tun wollen, ausreichen.
5.5.2.2. Die ausführbare Klasse
![]() |
Die ausführbare Klasse [SpringCore04Application] [[2] sieht wie folgt aus:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringCore04Application {
public static void main(String[] args) {
SpringApplication.run(SpringCore04Application.class, args);
}
}
- Zeile 6: Die Annotation [@SpringBootApplication] ist eine Kurzform für die drei Annotationen [@Configuration, @EnableAutoConfiguration, @ComponentScan], was bedeutet:
- dass die Klasse [SpringCore04Application] eine Spring-Konfigurationsklasse ist;
- dass Spring Boot angewiesen wird, die Konfiguration auf der Grundlage der Klassen durchzuführen, die es im Klassenpfad des Projekts findet, in diesem Fall also die Maven-Abhängigkeiten;
- das aktuelle Verzeichnis (das der Klasse [SpringCore04Application]) zu durchsuchen, um weitere Spring-Komponenten zu finden;
- Zeile 10: Die statische Methode [SpringApplication.run] wird ausgeführt. Ihr erster Parameter ist eine Spring-Konfigurationsklasse, hier die Klasse [SpringCore04Application]. Ihr zweiter Parameter ist die Liste der Argumente, die an die Methode [main] übergeben werden (Zeile 9). Die statische Methode [SpringApplication.run] ist für die Erstellung des Spring-Kontexts zuständig, d. h. für die Erstellung der verschiedenen Beans, die entweder in den Konfigurationsklassen oder in den durch die Annotation [@ComponentScan] gescannten Verzeichnissen gefunden werden. Die [main]-Methode führt hier keine weiteren Aufgaben aus. Um ihr etwas mehr Substanz zu verleihen, werden wir sie wie folgt ändern:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringCore04Application {
public static void main(String[] args) {
// spring context instantiation
ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
// context display
System.out.println("---------------- Liste des beans Spring");
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName);
}
// closing context
context.close();
}
}
- Zeile 12: Die statische Methode [SpringApplication.run] gibt den von ihr erstellten Spring-Kontext zurück;
- Zeilen 15–17: Die Namen aller Beans in diesem Kontext werden angezeigt;
Die Anwendung kann wie folgt ausgeführt werden [1–3]. Die Standardmethode [Run As Java Application] ist ebenfalls gültig.
![]() |
Es ergibt sich folgendes Ergebnis:
- Zeilen 14–28: Beans aus dem Spring-Kontext. Ihre Rolle ist uns nicht bekannt. In Zeile 18 finden wir die Bean [springCore04Application], die aufgrund ihrer Annotation [@SpringBootApplication] automatisch zu einer Spring-Bean wird;
- die anderen Zeilen sind Spring-Protokolle auf der Ebene [INFO]. Wie wir bereits gesehen haben, können diese Protokolle über die Datei [logback.xml] gesteuert werden, die sich im Klassenpfad des Projekts befindet:
![]() |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- log level control -->
<root level="warn"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Wenn wir in Zeile 12 oben die Stufe auf [warn] statt auf [info] setzen, erhalten wir folgendes Ergebnis:
Die Protokolle sind verschwunden. Es werden nur Meldungen der Stufe [warn] angezeigt, und hier gab es keine.
5.5.3. Implementierung der verschiedenen Schichten der Architektur
![]() |
Wir werden nun die drei Schichten der oben genannten Architektur implementieren:
![]() |
Die [DAO]-Schicht wird durch das Paket [spring.core.dao] implementiert. Es stellt die folgende [IDao]-Schnittstelle bereit:
package spring.core.dao;
public interface IDao {
public int doSomethingInDaoLayer(int a, int b);
}
Diese Schnittstelle hat zwei Implementierungen: [Dao1] und [Dao2]:
package spring.core.dao;
public class Dao1 implements IDao {
public int doSomethingInDaoLayer(int a, int b) {
return a+b;
}
}
package spring.core.dao;
public class Dao2 implements IDao {
public int doSomethingInDaoLayer(int a, int b) {
return a-b;
}
}
Die [Business]-Schicht wird durch das Paket [spring.core.business] implementiert. Es stellt die folgende [IMetier]-Schnittstelle bereit:
package spring.core.metier;
public interface IMetier {
public int doSomethingInMetierLayer(int a, int b);
}
Diese Schnittstelle hat zwei Implementierungen: [Business1] und [Business2]:
package spring.core.metier;
import spring.core.dao.IDao;
public class Metier1 implements IMetier {
private IDao dao;
public int doSomethingInMetierLayer(int a, int b) {
a++;
b++;
return dao.doSomethingInDaoLayer(a, b);
}
public void setDao(IDao dao) {
this.dao = dao;
}
}
package spring.core.metier;
import spring.core.dao.IDao;
public class Metier2 implements IMetier {
private IDao dao;
public int doSomethingInMetierLayer(int a, int b) {
a--;
b++;
return dao.doSomethingInDaoLayer(a, b);
}
public void setDao(IDao dao) {
this.dao = dao;
}
}
Die [UI]-Schicht wird durch das Paket [spring.core.ui] implementiert. Es stellt die folgende [IUi]-Schnittstelle bereit:
package spring.core.ui;
public interface IUi {
public int doSomethingInUiLayer(int a, int b);
}
Diese Schnittstelle hat zwei Implementierungen: [Ui1] und [Ui2]:
package spring.core.ui;
import spring.core.metier.IMetier;
public class Ui1 implements IUi {
private IMetier metier;
public int doSomethingInUiLayer(int a, int b) {
a++;
b++;
return metier.doSomethingInMetierLayer(a, b);
}
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
package spring.core.ui;
import spring.core.metier.IMetier;
public class Ui2 implements IUi {
private IMetier metier;
public int doSomethingInUiLayer(int a, int b) {
a--;
b++;
return metier.doSomethingInMetierLayer(a, b);
}
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
5.5.4. Spring-Projektkonfiguration
![]() |
Die Konfigurationsklasse [Config] sieht wie folgt aus:
package spring.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;
@Configuration
public class Config {
// -------------- implementation [Ui1, Metier1, Dao1]
@Bean
public IDao dao1() {
return new Dao1();
}
@Bean
public IMetier metier1(IDao dao1) {
Metier1 metier = new Metier1();
metier.setDao(dao1);
return metier;
}
@Bean
public IUi ui1(IMetier metier1) {
Ui1 ui = new Ui1();
ui.setMetier(metier1);
return ui;
}
// -------------- implementation [Ui2, Metier2, Dao2]
@Bean
public IDao dao2() {
return new Dao2();
}
@Bean
public IMetier metier2(IDao dao2) {
Metier2 metier = new Metier2();
metier.setDao(dao2);
return metier;
}
@Bean
public IUi ui2(IMetier metier2) {
Ui2 ui = new Ui2();
ui.setMetier(metier2);
return ui;
}
}
- Zeilen 20–23: Die Bean mit dem Namen [dao1] (Methodenname) ist eine Instanz der Klasse [Dao1] (Zeile 22), die als Implementierung der Schnittstelle [IDao] (Zeile 21) betrachtet wird. Die Bean [dao1] wird daher als Instanz einer Schnittstelle betrachtet (die Terminologie ist zwar nicht korrekt, aber verständlich) und nicht als Instanz einer Klasse. Dies ist ein wichtiger Punkt, den es zu verstehen gilt. Alle anderen Beans sind ebenfalls Instanzen von Schnittstellen;
- Zeilen 25–30: eine Instanz der Schnittstelle [IMetier], die von der Klasse [Metier1] implementiert wird;
- Zeilen 32–37: eine Instanz der Schnittstelle [IUi], die von der Klasse [Ui1] implementiert wird;
- Zeilen 20–37: Implementierung der Schichten [UI, Business, DAO] mit den Instanzen [Ui1, Metier1, Dao1];
- Zeilen 40–57: Implementieren der Schichten [UI, Business, DAO] mit den Instanzen [Ui2, Business2, Dao2];
5.5.5. Unit-Test [JUnitTest]
![]() |
Die Klasse [JUnitTest] befindet sich im Verzeichnis [src/test/java] des Maven-Projekts. Dateien in diesem Verzeichnis werden nicht in das endgültige Projektarchiv aufgenommen. Der Code lautet wie folgt:
package spring.core.tests;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;
@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
- Zeile 16: Die Annotation [@SpringApplicationConfiguration] ist Teil des Spring Boot Test-Projekts (Zeile 8). Sie wird durch die folgende Abhängigkeit in der Datei [pom.xml] eingeführt:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
Diese Annotation nimmt als Parameter die Liste der Konfigurationsklassen entgegen, die zum Erstellen des für den Test erforderlichen Spring-Kontexts verwendet werden sollen. Hier verwenden wir die bereits vorgestellte Konfigurationsklasse [Config];
- Zeile 17: Die Annotation [@RunWith] ist eine JUnit-Annotation (Zeile 5). Ihr Parameter ist die Klasse, die für die Ausführung der Tests anstelle der Standard-JUnit-Framework-Klasse zuständig ist. Diese Klasse ist eine Spring-Klasse (Zeile 9). Sie verwendet die in der Testklasse vorhandenen Spring-Annotationen;
Die vollständige Klasse lautet wie folgt
...
@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
// layer [UI]
@Autowired
@Qualifier("ui1")
private IUi ui1;
@Autowired
@Qualifier("ui2")
private IUi ui2;
// business] layer
@Autowired
@Qualifier("metier1")
private IMetier metier1;
@Autowired
@Qualifier("metier2")
private IMetier metier2;
// layer [dao]
@Autowired
@Qualifier("dao1")
private IDao dao1;
@Autowired
@Qualifier("dao2")
private IDao dao2;
@Test
public void testDao() {
Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
}
@Test
public void testMetier() {
Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
}
@Test
public void testUI() {
Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
}
}
- Zeilen 8–10: Wir injizieren (Zeile 8) die Bean mit dem Namen (Zeile 9) [ui1]. Beachten Sie, dass wir in Zeile 10 eine Schnittstelleninstanz anstelle einer Klasseninstanz injizieren;
- Zeilen 21–32: Die anderen in der Klasse [Config] definierten Beans werden auf die gleiche Weise injiziert;
- Zeile 34: Die Annotation [@Test] kennzeichnet eine Methode, die während des Testens ausgeführt werden soll. Weitere mögliche Annotationen sind:
- [@BeforeClass]: Methode, die vor dem Start der Tests ausgeführt werden soll;
- [@AfterClass]: Methode, die ausgeführt werden soll, sobald alle Tests abgeschlossen sind;
- [@Before]: Methode, die vor jedem Test ausgeführt werden soll;
- [@After]: Methode, die nach jedem Test ausgeführt werden soll;
- Zeile 36: Wir überprüfen, ob der Aufruf [dao1.doSomethingInDaoLayer(10, 20)] den Wert 30 zurückgibt. Gemäß Konvention ist der erste Parameter der erwartete Wert und der zweite der tatsächliche Wert;
- Zeile 36: testet die Instanz [dao1] der Schnittstelle [IDao];
- Zeile 37: testet die Instanz [dao2] der Schnittstelle [IDao];
- Zeile 42: testet die Instanz [metier1] der Schnittstelle [IMetier];
- Zeile 43: testet die Instanz [metier2] der Schnittstelle [IMetier];
- Zeile 48: testet die Instanz [ui1] der Schnittstelle [IUi];
- Zeile 36: testet die Instanz [ui2] der Schnittstelle [IUi];
Die folgenden Assertions können in einer Testmethode verwendet werden:
- assertEquals(Ausdruck1, Ausdruck2): prüft, ob die Werte der beiden Ausdrücke gleich sind. Viele Ausdruckstypen werden akzeptiert (int, String, float, double, boolean, char, short). Sind die beiden Ausdrücke nicht gleich, wird eine Ausnahme vom Typ [AssertionFailedError] ausgelöst,
- assertEquals(real1, real2, delta): Prüft, ob zwei reelle Zahlen bis auf delta gleich sind, d. h. abs(real1-real2) <= delta. Man könnte beispielsweise assertEquals(real1, real2, 1E-6) schreiben, um zu überprüfen, ob zwei Werte bis auf 10⁻⁶ gleich sind,
- assertEquals(message, expression1, expression2) und assertEquals(message, real1, real2, delta) sind Varianten, mit denen Sie die Fehlermeldung angeben können, die der [AssertionFailedError]-Ausnahme zugeordnet werden soll, die ausgelöst wird, wenn die [assertEquals]-Methode fehlschlägt,
- assertNotNull(Object) und assertNotNull(message, Object): Prüft, ob die Object-Referenz nicht null ist,
- assertNull(Object) und assertNull(message, Object): prüft, ob die Object-Referenz null ist,
- assertSame(Object1, Object2) und assertSame(message, Object1, Object2): prüft, ob die Referenzen Object1 und Object2 auf dasselbe Objekt verweisen,
- assertNotSame(Object1, Object2) und assertNotSame(message, Object1, Object2): prüft, ob die Referenzen Object1 und Object2 nicht auf dasselbe Objekt verweisen;
Um den Test auszuführen, gehen Sie wie folgt vor:
![]() |
Es ergibt sich folgendes Ergebnis:
![]() |
Hier wurden alle Tests bestanden. Was zeigt dieses Beispiel? Es zeigt die Flexibilität, die das Spring-Framework bei der Konfiguration einer mehrschichtigen Architektur bietet. Sie können sich für die Implementierung [Ui1, Metier1, Dao1] oder [Ui2, Metier2, Dao2] entscheiden, indem Sie diese einfach konfigurieren. Wenn Sie also im vorherigen JUnit-Test nur die Injektion der [ui1, metier1, dao1]-Beans beibehalten, arbeiten Sie mit der ersten Architektur. Um die Architektur zu ändern, ändern Sie einfach die injizierten Beans. Dies geschieht, ohne den Code der Schichten zu ändern, die die Schnittstellen implementieren. Diese Art der Programmierung wird als schnittstellenbasierte Programmierung bezeichnet, da wir nicht die Instanzen der Klassen verwenden, die die Schichten implementieren, sondern die Instanzen ihrer Schnittstellen.
5.6. Fazit
- Spring verwaltet Objekte, die Singletons (eine einzige Instanz) sind. Spring verwaltet auch Objekte, die jedes Mal instanziiert werden, wenn eine Instanz von Spring angefordert wird. Dieser Fall wird ebenfalls in diesem Dokument vorgestellt;
- diese Objekte können auf verschiedene Arten deklariert werden, die kombiniert werden können:
- in einer XML-Datei,
- in einer Java-Klasse, die mit [@Configuration] annotiert ist,
- mit jeder Java-Klasse, die mit [@Component, @Service, ...] annotiert ist;
- ein Spring-Objekt kann mithilfe der Annotation [@Autowired] in ein anderes Spring-Objekt injiziert werden. Dies wird als Dependency Injection (DI) bezeichnet;
- Spring ist sehr nützlich für die Konfiguration von mehrschichtigen Architekturen, wenn es in Verbindung mit dem interface-basierten Programmierparadigma verwendet wird;














































