9. [TD]: Implementierung der Schicht [ui] mit einem Konsolenprogramm
Stichworte: mehrschichtige Architektur, Spring, Abhängigkeitsinjektion.
![]() |
9.1. Support
![]() |
In [1] enthält der Ordner [support / chap-09] das Eclipse-Projekt der Schicht [UI] der Konsolenanwendung.
9.2. Maven-Konfiguration
Das Eclipse-Projekt [elections-ui-metier-dao-jdbc] wird durch die folgende Maven-Datei [pom.xml] konfiguriert:
<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.elections</groupId>
<artifactId>elections-ui-metier-dao-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-ui-metier-dao-jdbc</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Geschäftsprozess -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot-Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Plugins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- zur Installation des Projekt-Artefakts im lokalen Maven-Repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- Zeilen 22–26: Das Archiv der Schicht [métier] und in der Folge das der Schicht [DAO] werden importiert;
9.3. Spring-Konfiguration
![]() |
Die Klasse [UiConfig] konfiguriert die Spring-Anwendung:
package elections.ui.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import elections.metier.config.MetierConfig;
@Import(MetierConfig.class)
@ComponentScan(basePackages = { "elections.ui.service" })
public class UiConfig {
}
- Zeile 8: Hier werden die in der Schicht [métier] definierten Beans importiert. Diese importierte bereits die in der Schicht [DAO] definierten Beans. Somit hat man hier Zugriff auf die Beans aller drei Schichten;
- Zeile 9: Das Paket [elections.ui.service] enthält weitere Beans;
9.4. Die Schnittstelle der Schicht [UI]
![]() |
Um zu verstehen, wie die Java-Schnittstelle der Schicht [UI] aussehen könnte, müssen wir wissen, wer diese Schnittstelle nutzen wird. Es handelt sich dabei nicht um den Nutzer des obigen Schemas, sondern um das Programm, das die gesamte Anwendung starten wird.
![]() |
Die Schnittstelle [IElectionsUI] wird dem Hauptprogramm [main] zur Verfügung gestellt, das die Anwendung startet. Was kann [main] von der Schicht [ui] anfordern? Es kann diese auffordern, die Interaktion mit dem Benutzer zu starten, der die fehlenden Daten zur Wahl eingeben wird. Wir verwenden die folgende minimale Schnittstelle:
package istia.st.elections.ui;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
- Zeile 7: Die Schnittstelle verfügt nur über eine einzige Methode: run. Durch den Aufruf dieser Methode wird die Schicht [ui] aufgefordert, die Interaktion mit dem Benutzer zu beginnen.
9.5. Die Startklasse der Anwendung
![]() |
Die Startklasse der Anwendung ist eine Java-Klasse mit einer statischen Methode [main]. Diese Methode muss Instanzen der Schichten [ui, metier, demo] erstellen und die Schicht [ui] anweisen, den Dialog mit dem Benutzer zu beginnen. Diese Klasse könnte die folgende Klasse [AbstractBootElections] sein:
package elections.ui.boot;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import elections.dao.entities.ElectionsException;
import elections.ui.config.UiConfig;
import elections.ui.service.IElectionsUI;
public abstract class AbstractBootElections {
// Abruf des Spring-Kontexts
protected AnnotationConfigApplicationContext ctx;
public void run() {
// Instanziierung der Schicht [ui]
IElectionsUI electionsUI = null;
try {
// Abruf des Spring-Kontexts
ctx = new AnnotationConfigApplicationContext(UiConfig.class);
// Abruf der Schicht [ui]
electionsUI = getUI();
} catch (RuntimeException ex) {
// Der Fehler wird gemeldet
afficheExceptions("Les erreurs suivantes se sont produites :", ex);
// Die Anwendung wird beendet
System.exit(1);
}
// Ausführung der Schicht [ui]
try {
electionsUI.run();
} catch (ElectionsException ex2) {
// Fehler wird gemeldet
afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
// Die Anwendung wird beendet
System.exit(3);
} catch (RuntimeException ex1) {
// Fehler wird gemeldet
afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
// Die Anwendung wird beendet
System.exit(2);
}
}
protected abstract IElectionsUI getUI();
private void afficheExceptions(String message, ElectionsException ex) {
// die Meldung wird angezeigt
System.out.println(String.format("%s -------------", message));
System.out.println(String.format("Code erreur : %d", ex.getCode()));
// Die Fehler werden angezeigt
for (String erreur : ex.getErreurs()) {
System.out.println(String.format("-- %s", erreur));
}
}
public void afficheExceptions(String message, Exception ex) {
// Die Meldung wird angezeigt
System.out.println(String.format("%s -------------", message));
// Der Ausnahmestapel wird angezeigt
Throwable cause = ex;
while (cause != null) {
System.out.println(String.format("-- %s", cause.getMessage()));
cause = cause.getCause();
}
}
}
- Zeile 19: Der Spring-Kontext wird instanziiert: Alle in den verschiedenen Konfigurationsdateien definierten Beans werden erstellt;
- Zeile 21: Es wird eine Referenz auf das Bean abgerufen, das die Schnittstelle [IElectionsUI] implementiert. Wir werden die Schnittstelle [IElectionsUI] durch zwei Beans implementieren:
- [ElectionsConsole] für eine Konsolenanwendung;
- [ElectionsSwing] für eine Swing-Anwendung;
Die Methode [getUI] ist abstrakt (Zeile 44). Tatsächlich wird die Klasse [AbstractBootElections] die übergeordnete Klasse zweier Klassen sein:
- [BootElectionsConsole], die der übergeordneten Klasse die Bean [ElectionsConsole] bereitstellt;
- [BootElectionsSwing], die der übergeordneten Klasse die Bean [ElectionsSwing] bereitstellt;
- Zeile 30: Die Methode [run] der Schnittstelle wird ausgeführt;
Ausnahmen werden an verschiedenen Stellen behandelt:
- Zeilen 22–27: Bei der Instanziierung des Spring-Kontexts kann eine Ausnahme auftreten;
- Zeilen 31–41: Bei der Ausführung der Schicht [UI] kann eine Ausnahme auftreten. Es werden zwei Arten von Ausnahmen unterschieden:
- die Klasse [ElectionsException], die von der Schicht [DAO] ausgelöst wird;
- die Klasse [RuntimeException] für sonstige Ausnahmen, die während der Ausführung auftreten könnten;
Die Klasse [BootElectionsConsole] ist die Klasse, die die Konsolenimplementierung startet. Ihr Code lautet wie folgt:
package elections.ui.boot;
import elections.ui.service.IElectionsUI;
public class BootElectionsConsole extends AbstractBootElections{
public static void main(String[] arguments) {
new BootElectionsConsole().run();
}
@Override
protected IElectionsUI getUI() {
return ctx.getBean("electionsConsole",IElectionsUI.class);
}
}
- Zeile 5: Die Klasse [BootElectionsConsole] erweitert die Klasse [AbstractBootElections]. Sie muss daher die Methode [getUI] implementieren, die ihre übergeordnete Klasse als abstrakt deklariert hatte;
- Zeilen 10–13: Implementierung der Methode [getUI];
- Zeile 12: Die Bean mit dem Namen [electionsConsole], die die Schnittstelle [IElectionsUI] implementiert, wird zurückgegeben. [ctx] ist der Spring-Kontext, der in der übergeordneten Klasse durch die folgende Deklaration definiert wurde:
// Abruf des Spring-Kontexts
protected AnnotationConfigApplicationContext ctx;
Da das Feld das Attribut [protected] besitzt, ist es in den untergeordneten Klassen sichtbar.
Die Bean in Zeile 12 wird wie folgt deklariert:
@Component
public class ElectionsConsole implements IElectionsUI {
Der Standardname dieses Beans ist der Name der Klasse mit einem kleinen Anfangsbuchstaben. Man kann diesen Standardnamen umgehen, indem man explizit Folgendes schreibt:
Die Klasse [BootElectionsSwing], die die Swing-Implementierung starten würde, könnte wie folgt aussehen:
package elections.ui.boot;
import elections.ui.service.IElectionsUI;
public class BootElectionsSwing extends AbstractBootElections {
public static void main(String[] arguments) {
new BootElectionsSwing().run();
}
@Override
protected IElectionsUI getUI() {
return ctx.getBean("electionsSwing", IElectionsUI.class);
}
}
Dieses Entwurfsmuster, bei dem das allen Klassen gemeinsame Verhalten in einer übergeordneten Klasse zusammengefasst wird und die untergeordneten Klassen die für sie spezifischen Details implementieren, wird als Strategy-Entwurfsmuster bezeichnet. Dieses Entwurfsmuster schreibt allen untergeordneten Klassen ein gemeinsames Verhalten vor.
9.6. Die Implementierungsklasse [ElectionsConsole]
![]() |
Unsere erste Implementierungsklasse der Schicht [ui] wird eine Klasse sein, die die Konsole zur Kommunikation mit dem Benutzer nutzt. Hier ist ein Beispiel für einen Dialog, der in der Eclipse-Konsole angezeigt wird:
Il y a 7 listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :
Nombre de voix de la liste [A] : 2500
Nombre de voix de la liste [B] : 4500
Nombre de voix de la liste [C] : x
Nombre de voix incorrect. Veuillez recommencer
Nombre de voix de la liste [C] : 8000
Nombre de voix de la liste [D] : 12000
Nombre de voix de la liste [E] : 16000
Nombre de voix de la liste [F] : 25000
Nombre de voix de la liste [G] : 32000
Résultats de l'élection
[G,32000,2,false]
[F,25000,2,false]
[E,16000,1,false]
[D,12000,1,false]
[C,8000,0,false]
[B,4500,0,true]
[A,2500,0,true]
Die Klasse [ElectionsConsole] könnte folgendes Grundgerüst haben:
package elections.ui.service;
import java.util.Comparator;
import java.util.Scanner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import elections.dao.entities.ListeElectorale;
import elections.metier.service.IElectionsMetier;
@Component
public class ElectionsConsole implements IElectionsUI {
@Autowired
private IElectionsMetier electionsMetier;
@Override
public void run() {
// Dateneingabe
try (Scanner clavier = new Scanner(System.in)) {
// Anforderung der Wettbewerbslisten an die Schicht [metier]
// Erfassung der Stimmen
}
// Berechnung der Sitze
// Die Ergebnisse werden gespeichert
// Sortierung der Listen in absteigender Reihenfolge der Stimmen
// Anzeige der Listen
}
// Vergleichsklasse für Wahllisten
class CompareListesElectorales implements Comparator<ListeElectorale> {
// Vergleich zweier Kandidatenlisten nach der Anzahl der Stimmen
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// Die Stimmen dieser beiden Listen werden verglichen
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
- Zeile 12: Die Klasse [ElectionsConsole] ist eine Spring-Komponente;
- Zeile 13: Die Klasse implementiert die Schnittstelle [IElectionsUI]
- Zeilen 15–16: Spring-Injektion einer Referenz auf die Schicht [metier];
- Zeilen 18–34: Die Methode [run] der Schnittstelle [IelectionsUI];
Das try in den Zeilen 21–28 heißt try-with-resources und hat folgende Syntax:
Die Ressource in Zeile 1 muss die Schnittstelle [java.lang.AutoCloseable] implementieren. Die Ressource wird in Zeile 1 geöffnet und nach Zeile 3 automatisch geschlossen, unabhängig davon, ob im zwischen diesen beiden Zeilen ausgeführten Code eine Ausnahme auftritt oder nicht. Diese Syntax stellt sicher, dass eine geöffnete Ressource in jedem Fall geschlossen wird, egal was passiert.
Aufgabe: Schreiben Sie den Code für die Methode [run]. Orientieren Sie sich dabei an den Kommentaren.






