Skip to content

3. Artikel 2 – Beispiele für dreischichtige Webarchitekturen

Ziele des Artikels:

  • 3-Schichten-Architekturen
  • Einfache Webarchitektur MVC
  • Struts-Architektur MVC
  • Spring-Architektur MVC

Verwendete Tools:

  • Spring: http://www.springframework.org/
  • iBatis SqlMap: http://www.ibatis.com/
  • JUnit: http://www.junit.org/index.htm
  • Eclipse: http://www.eclipse.org/
  • Struts: http://struts.apache.org/
  • Firebird: http://firebird.sourceforge.net/: SGBD, Treiber JDBC. Eigentlich ist jede Quelle mit der Nummer JDBC geeignet.
  • IBExpert, Personal Edition: http://www.hksoftware.net/download/ibep_2005.2.14.1_full.exe (März 2005). Mit IBExpert lässt sich der SGBD Firebird grafisch verwalten.
  • Tomcat: http://jakarta.apache.org/tomcat/
  • Tomcat-Plugin für Eclipse: http://www.sysdeo.com/eclipse/tomcatPlugin.html. Siehe auch das Dokument https://tahe.developpez.com/java/eclipse/

Zum Verständnis dieses Dokuments sind verschiedene Vorkenntnisse erforderlich. Einige davon können in Dokumenten erworben werden, die ich verfasst habe. In diesem Fall verweise ich darauf. Es versteht sich von selbst, dass dies nur ein Vorschlag ist und der Leser seine bevorzugten Dokumente verwenden kann.

  • Java-Sprache: [https://tahe.developpez.com/java/cours]
  • Webprogrammierung mit Java: [https://tahe.developpez.com/java/web/]
  • Webprogrammierung mit Java, Eclipse, Tomcat: [https://tahe.developpez.com/java/eclipse/]
  • Webprogrammierung mit Struts: [https://tahe.developpez.com/java/struts/]
  • Verwendung des Spring-Aspekts IoC: [https://tahe.developpez.com/java/springioc]
  • Tag-Bibliothek: JSTL (teilweise)
  • Ibatis-Dokumentation SqlMap: [https://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
  • Firebird: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf] (März 2005).

Die Ideen in diesem Dokument stammen aus einem Buch, das ich im Sommer 2004 gelesen habe, einem großartigen Werk von Rod Johnson: J2EE „Development without EJB“, erschienen im Wrox-Verlag.


3.1. Die Webanwendung „webarticles“

Wir möchten hier einige Elemente einer E-Commerce-Webanwendung vorstellen. Diese ermöglicht es Webkunden

  • , eine Liste von Artikeln aus einer Datenbank einzusehen
  • bestimmte Artikel in einen elektronischen Warenkorb zu legen
  • und diesen zu bestätigen. Diese Bestätigung hat lediglich zur Folge, dass die Bestände der gekauften Artikel in der Datenbank aktualisiert werden.

Dem Benutzer werden folgende Ansichten angezeigt:

  • die Ansicht [LISTE], die eine Liste der zum Verkauf stehenden Artikel anzeigt

Image

  • die Ansicht [INFOS], die zusätzliche Informationen zu einem Produkt liefert:

Image

  1. die Ansicht [PANIER], die den Inhalt des Warenkorbs des Kunden anzeigt

Image

  1. die Ansicht [PANIERVIDE] für den Fall, dass der Warenkorb des Kunden leer ist

Image

  1. die Ansicht [ERREURS], die alle Anwendungsfehler meldet

Image

3.2. Allgemeine Architektur der Anwendung

Wir möchten eine Anwendung mit der folgenden dreischichtigen Struktur erstellen:

  • Die drei Schichten werden durch die Verwendung von Java-Schnittstellen voneinander unabhängig gemacht
  • Die Integration der verschiedenen Schichten erfolgt über Spring
  • Jede Schicht ist in separaten Paketen untergebracht: „web“ (Benutzeroberfläche), „domain“ (Geschäftslogik) und „dao“ (Datenzugriffsschicht).

Wir gehen hier davon aus, dass die Schichten [domain] und [dao] bereits vorhanden sind. Wir konzentrieren uns ausschließlich auf die Schicht [web], die wir auf verschiedene Arten entwickeln möchten:

  • mithilfe einer klassischen Servlet-Controller-Technologie – Seiten JSP
  • mithilfe der Struts-Technologie MVC
  • mithilfe der Spring-Technologie MVC

In allen Fällen wird die Anwendung einer MVC-Architektur (Model-View-Controller) folgen. Wenn wir das obige Schichtmodell heranziehen, fügt sich die MVC-Architektur wie folgt ein:

Die Bearbeitung einer Anfrage eines Kunden erfolgt in folgenden Schritten:

  1. Der Kunde sendet eine Anfrage an den Controller. Dieser Controller ist ein Servlet, über das alle Kundenanfragen laufen. Er ist das Eingangstor der Anwendung. Er entspricht dem C in MVC.
  2. Der Controller verarbeitet diese Anfrage. Dazu benötigt er möglicherweise Unterstützung durch die Geschäftslogik-Schicht, die in der Struktur MVC als Modell M bezeichnet wird.
  3. Der Controller erhält eine Antwort von der Geschäftslogik. Die Anfrage des Kunden wurde bearbeitet. Dies kann zu mehreren möglichen Antworten führen. Ein klassisches Beispiel ist
    • eine Fehlerseite, falls die Anfrage nicht korrekt verarbeitet werden konnte
    • ansonsten eine Bestätigungsseite
  4. Der Controller wählt die Antwort (= Ansicht) aus, die an den Client gesendet werden soll. Dabei handelt es sich meist um eine Seite mit dynamischen Elementen. Der Controller stellt diese der Ansicht zur Verfügung.
  5. Die Ansicht wird an den Client gesendet. Dies ist das „V“ in MVC.

3.3. Das Modell

Wir befassen uns hier mit dem M von MVC. Das Modell besteht hier aus folgenden Elementen:

  1. die Geschäftsklassen
  2. die Datenzugriffsklassen
  3. die Datenbank

3.3.1. Die Datenbank

Die Datenbank enthält nur eine Tabelle namens ARTICLES. Diese wurde mit den folgenden Befehlen SQL generiert:

CREATE TABLE ARTICLES (
    ID            INTEGER NOT NULL,
    NOM           VARCHAR(30) NOT NULL,
    PRIX          NUMERIC(15,2) NOT NULL,
    STOCKACTUEL   INTEGER NOT NULL,
    STOCKMINIMUM  INTEGER NOT NULL
);


/* Einschränkungen */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');

/* Primärschlüssel */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
Primärschlüssel, der einen Artikel eindeutig identifiziert
nom
Name des Artikels
prix
sein Preis
stockactuel
Aktueller Lagerbestand
stockminimum
der Lagerbestand, bei dessen Unterschreitung eine Nachbestellung erfolgen muss

In den folgenden Tests wurde eine Datenbank mit dem Namen [Firebird] verwendet. [Firebird] ist eine „Open-Source“-Version von SGBD. Der Treiber JDBC [firebirdsql-full.jar] befindet sich im Ordner [WEB-INF/lib] der Webanwendung.

3.3.2. Die Pakete der Vorlage

Das Modell M wird hier in Form von drei Archiven bereitgestellt:

  • istia.st.articles.dao: enthält die Klassen für den Datenzugriff auf die Ebene [dao]
  • istia.st.articles.exception: enthält eine Ausnahmeklasse für diese Artikelverwaltung
  • istia.st.articles.domain: enthält die Fachklassen der Schicht [domain]
archive
contenu
rôle
istia.st.articles.dao
- enthält das Paket [istia.st.articles.dao], das wiederum folgende Elemente enthält:
- [IArticlesDao]: die Schnittstelle für den Zugriff auf die Dao-Schicht. Dies ist die einzige Schnittstelle, die die Schicht [domain] sieht. Sie sieht keine andere.
- [Article]: Klasse zur Definition eines Artikels
- [ArticlesDaoSqlMap]: Implementierungsklasse der Schnittstelle [IArticlesDao] mit dem Tool SqlMap
Datenzugriffsebene – befindet sich vollständig in der Schicht [dao] der 3-Tier-Architektur der Webanwendung
istia.st.articles.domain
– enthält das Paket [istia.st.articles.domain], das wiederum folgende Elemente enthält:
- [IArticlesDomain]: die Schnittstelle für den Zugriff auf die Schicht [domain]. Dies ist die einzige Schnittstelle, die die Webschicht sieht. Sie sieht keine andere.
- [AchatsArticles]: eine Klasse, die [IArticlesDomain] implementiert
- [Achat]: Klasse, die den Einkauf eines Kunden repräsentiert
- [Panier]: Klasse, die die Gesamtheit der Einkäufe eines Kunden darstellt
stellt das Modell der Online-Einkäufe dar – befindet sich vollständig in der Schicht [domain] der 3-Tier-Architektur der Webanwendung
istia.st.articles.exception
- enthält das Paket [istia.st.articles.exception], das wiederum folgende Elemente enthält:
- [UncheckedAccessArticlesException]: Klasse, die eine Ausnahme vom Typ [RuntimeException] definiert. Dieser Ausnahmetyp wird von der Schicht [dao] ausgelöst, sobald ein Problem beim Datenzugriff auftritt.
 

3.3.3. Das Paket [istia.st.articles.dao]

Die Klasse, die einen Artikel definiert, lautet wie folgt:

package istia.st.articles.dao;
import istia.st.articles.exception.UncheckedAccessArticlesException;

/**
 * @author ST - ISTIA
 *  
 */
public class Article {
  private int id;
  private String nom;
  private double prix;
  private int stockActuel;
  private int stockMinimum;

  /**
   * constructeur par défaut
   */
  public Article() {
  }

  public Article(int id, String nom, double prix, int stockActuel,
      int stockMinimum) {
     // Instanzattribute initialisieren
    setId(id);
    setNom(nom);
    setPrix(prix);
    setStockActuel(stockActuel);
    setStockMinimum(stockMinimum);
  }

     // Getter – Setter
  public int getId() {
    return id;
  }

  public void setId(int id) {
     // Gültige ID?
    if (id < 0)
      throw new UncheckedAccessArticlesException("id[" + id + "] invalide");
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
     // Gültiger Name?
    if(nom==null || nom.trim().equals("")){
      throw new UncheckedAccessArticlesException("Le nom est [null] ou vide");
    }
    this.nom = nom;
  }

  public double getPrix() {
    return prix;
  }

  public void setPrix(double prix) {
     // Gültiger Preis?
    if(prix<0) throw new UncheckedAccessArticlesException("Prix["+prix+"]invalide");
    this.prix = prix;
  }

  public int getStockActuel() {
    return stockActuel;
  }

  public void setStockActuel(int stockActuel) {
     // Gültiger Lagerbestand?
    if (stockActuel < 0)
      throw new UncheckedAccessArticlesException("stockActuel[" + stockActuel + "] invalide");
    this.stockActuel = stockActuel;
  }

  public int getStockMinimum() {
    return stockMinimum;
  }

  public void setStockMinimum(int stockMinimum) {
     // Gültiger Lagerbestand?
    if (stockMinimum < 0)
      throw new UncheckedAccessArticlesException("stockMinimum[" + stockMinimum + "] invalide");
    this.stockMinimum = stockMinimum;
  }

  public String toString() {
    return "[" + id + "," + nom + "," + prix + "," + stockActuel + ","
        + stockMinimum + "]";
  }
}

Diese Klasse bietet:

  1. einen Konstruktor, mit dem die 5 Informationen eines Artikels festgelegt werden können
  2. Zugriffsmethoden, die oft als Getter/Setter bezeichnet werden und zum Lesen und Schreiben der 5 Informationen dienen. Die Namen dieser Methoden entsprechen dem Standard JavaBean. Die Verwendung von Objekten der Klasse JavaBean in der Schicht DAO zur Anbindung an die Daten der Klasse SGBD ist gängige Praxis.
  3. eine Überprüfung der in den Artikel eingegebenen Daten. Bei fehlerhaften Daten wird eine Ausnahme ausgelöst.
  4. Eine Methode toString, mit der der Wert eines Artikels als Zeichenkette abgerufen werden kann. Dies ist häufig für die Fehlersuche in einer Anwendung nützlich.

Die Schnittstelle [IArticlesDao] ist wie folgt definiert:

package istia.st.articles.dao;

import istia.st.articles.domain.Article;
import java.util.List;

/**
 * @author ST-ISTIA
 *
 */
public interface IArticlesDao {

  /**
   * @return : liste de tous les articles
   */
  public List getAllArticles();

  /**
   * @param unArticle :
   *          l'article à ajouter
   */
  public int ajouteArticle(Article unArticle);

  /**
   * @param idArticle :
   *          id de l'article à supprimer
   */
  public int supprimeArticle(int idArticle);

  /**
   * @param unArticle :
   *          l'article à modifier
   */
  public int modifieArticle(Article unArticle);

  /**
   * @param idArticle :
   *          id de l'article cherché
   * @return : l'article trouvé ou null
   */
  public Article getArticleById(int idArticle);

  /**
   * vide la table des articles
   */
  public void clearAllArticles();

  /**
   *
   * @param idArticle id de l'article dont on change le stock
   * @param mouvement valeur à ajouter au stock (valeur signée)
   */
  public int changerStockArticle(int idArticle, int mouvement);
}

Die verschiedenen Methoden der Schnittstelle haben folgende Funktionen:

getAllArticles
gibt alle Artikel aus der Tabelle ARTICLES in einer Liste von Objekten [Article] zurück
clearAllArticles
leert die Tabelle ARTICLES
getArticleById
gibt das Objekt [Article] zurück, das durch seinen Primärschlüssel identifiziert wird
ajouteArticle
ermöglicht das Hinzufügen eines Artikels zur Tabelle ARTICLES
modifieArticle
ermöglicht das Ändern eines Artikels in der Tabelle [ARTICLES]
supprimerArticle
ermöglicht das Löschen eines Artikels aus der Tabelle [ARTICLES]
changerStockArticle
ermöglicht die Änderung des Bestands eines Artikels in der Tabelle [ARTICLES]

Die Schnittstelle stellt Client-Programmen eine Reihe von Methoden zur Verfügung, die ausschließlich durch ihre Signaturen definiert sind. Sie kümmert sich nicht darum, wie diese Methoden tatsächlich implementiert werden. Dies sorgt für Flexibilität in einer Anwendung. Das Client-Programm ruft die Schnittstelle auf und nicht eine bestimmte Implementierung davon.

Die Auswahl einer bestimmten Implementierung erfolgt über eine Spring-Konfigurationsdatei. Wir schlagen vor, hier die Schnittstelle IArticlesDao mithilfe eines Open-Source-Produkts namens SqlMap zu implementieren. Damit können wir alle Anweisungen SQL aus dem Java-Code entfernen.

Die Implementierungsklasse [ArticlesDaoSqlMap] ist wie folgt definiert:

package istia.st.articles.dao;

// Importe
import com.ibatis.sqlmap.client.SqlMapClient;
import istia.st.articles.domain.Article;
import java.util.List;

public class ArticlesDaoSqlMap implements IArticlesDao {

   // Felder
  private SqlMapClient sqlMap;

   // Konstruktoren
  public ArticlesDaoSqlMap(String sqlMapConfigFileName) { }

   // Methoden
  public SqlMapClient getSqlMap() {}
  public void setSqlMap(SqlMapClient sqlMap) { }
  public synchronized List getAllArticles() {}
  public synchronized int ajouteArticle(Article unArticle) {}
  public synchronized int supprimeArticle(int idArticle) {}
  public synchronized int modifieArticle(Article unArticle) {}
  public synchronized Article getArticleById(int idArticle) {}
  public synchronized void clearAllArticles() { }
  public synchronized int changerStockArticle(int idArticle, int mouvement) {}
}

Alle Methoden für den Datenzugriff wurden synchronisiert, um Probleme durch gleichzeitigen Zugriff auf die Datenquelle zu vermeiden. Zu einem bestimmten Zeitpunkt hat jeweils nur ein Thread Zugriff auf eine bestimmte Methode.

Die Klasse [ArticlesDaoSqlMap] nutzt das Tool [Ibatis SqlMap]. Der Vorteil dieses Tools besteht darin, dass es ermöglicht, den Datenzugriffscode SQL aus dem Java-Code auszulagern. Dieser wird dann in eine Konfigurationsdatei geschrieben. Wir werden später noch darauf zurückkommen. Zum Erstellen benötigt die Klasse [ArticlesDaoSqlMap] eine Konfigurationsdatei, deren Name als Parameter an den Konstruktor der Klasse übergeben wird. Diese Konfigurationsdatei definiert die erforderlichen Informationen, um:

  • den Zugriff auf die Datenbank SGBD, in der sich die Artikel befinden
  • die Verwaltung eines Verbindungspools
  • die Verwaltung von Transaktionen

In unserem Beispiel heißt sie [sqlmap-config-firebird.xml] und definiert den Zugriff auf eine Firebird-Datenbank:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
    PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
  <transactionManager type="JDBC">
      <dataSource type="SIMPLE">
            <property name="JDBC.Driver" value="org.firebirdsql.jdbc.FBDriver"/>
                <property name="JDBC.ConnectionURL"
                    value="jdbc:firebirdsql:localhost/3050:D:/data/Databases/firebird/dbarticles.gdb"/>
                <property name="JDBC.Username" value="sysdba"/>
                <property name="JDBC.Password" value="masterkey"/>
                <property name="JDBC.DefaultAutoCommit" value="true"/>
        </dataSource>
  </transactionManager>
  <sqlMap resource="articles.xml"/>
</sqlMapConfig>

Die oben genannte Konfigurationsdatei [articles.xml] ermöglicht es, festzulegen, wie eine Instanz der Klasse [istia.st.articles.dao.Article] aus einer Zeile der Tabelle [ARTICLES] des SGBD erstellt wird. Außerdem definiert sie die Abfragen SQL, mit denen die Schicht [dao] die Daten aus der Firebird-Datenquelle abrufen kann.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
    PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="Articles">

     <!-- ein Alias für die Klasse istia.st.articles.dao.Article -->
  <typeAlias alias="article" type="istia.st.articles.dao.Article"/>

     <!-- das Mapping OR M: Tabellenzeile ARTICLES – Instanz der Klasse „Artikel“ -->
  <resultMap id="article" class="article">
    <result property="id" column="ID"/>
    <result property="nom" column="NOM"/>
    <result property="prix" column="PRIX"/>
    <result property="stockActuel" column="STOCKACTUEL"/>
    <result property="stockMinimum" column="STOCKMINIMUM"/>
  </resultMap>

     <!-- die Abfrage SQL zum Abrufen aller Artikel -->
  <statement id="getAllArticles" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum from ARTICLES
</statement>

     <!-- die Abfrage SQL zum Löschen aller Artikel -->
  <statement id="clearAllArticles">delete from ARTICLES</statement>

     <!-- die Abfrage SQL zum Einfügen eines Artikels -->
  <statement id="insertArticle">
    insert into ARTICLES (id, nom, prix,
    stockactuel, stockminimum) values
    (#(id#,#name#,#preis#,#aktuellerBestand#,#Mindestbestand#)
</statement>

     <!-- die Abfrage SQL zum Löschen eines bestimmten Artikels -->
  <statement id="deleteArticle">delete FROM ARTICLES where id=#id#</statement>

     <!-- die Abfrage SQL zum Ändern eines bestimmten Artikels -->
  <statement id="modifyArticle">
    update ARTICLES set nom=#Name#,
    prix=#Preis#,aktuellerBestand=#aktuellerBestand#,Mindestbestand=#Mindestbestand# where
    id=#id#
</statement>

     <!-- die Abfrage SQL zum Abrufen eines bestimmten Artikels -->
  <statement id="getArticleById" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum FROM ARTICLES where id=#id#
</statement>

     <!-- die Abfrage SQL zum Ändern des Bestands eines bestimmten Artikels -->
  <statement id="changerStockArticle">
    update ARTICLES set
    stockActuel=stockActuel+#Bewegung#
    where id=#id# und stockActuel+#Bewegung#&gt;=0
</statement>
</sqlMap>

Der Code des Pakets [dao] ist im Anhang zu finden.

3.3.4. Das Paket [istia.st.articles.domain]

Die Schnittstelle [IArticlesDomain] entkoppelt die Schicht [métier] von der Schicht [web]. Letztere greift über diese Schnittstelle auf die Schicht [métier/domain] zu, ohne sich um die Klasse zu kümmern, die sie tatsächlich implementiert. Die Schnittstelle definiert die folgenden Aktionen für den Zugriff auf die Geschäftsschicht:

 package istia.st.articles.domain;

// Eingänge
import java.util.ArrayList;
import java.util.List;

public abstract interface IArticlesDomain {

   // Methoden
  void acheter(Panier panier);
  List getAllArticles();
  Article getArticleById(int idArticle);
  ArrayList getErreurs();
}
List getAllArticles()
gibt die Objektliste [Article] zurück, die dem Kunden angezeigt werden soll
Article getArticleById(int idArticle)
gibt das durch [idArticle] identifizierte Objekt [Article] zurück
void acheter(Panier panier)
validiert den Warenkorb des Kunden, indem die Bestände der gekauften Artikel um die gekaufte Menge verringert werden – kann fehlschlagen, wenn der Bestand nicht ausreicht
ArrayList getErreurs()
gibt die Liste der aufgetretenen Fehler zurück – leer, wenn keine Fehler vorliegen

Hier wird die Schnittstelle [IArticlesDomain] durch die folgende Klasse [AchatsArticles] implementiert:

package istia.st.articles.domain;

// Importe
import istia.st.articles.dao.IArticlesDao;
import istia.st.articles.exception.UncheckedAccessArticlesException;
import java.util.ArrayList;
import java.util.List;

public class AchatsArticles implements IArticlesDomain {

   // Felder
  private IArticlesDao articlesDao;
  private ArrayList erreurs;

   // Konstruktoren
  public AchatsArticles(IArticlesDao articlesDao) { }

   // Methoden
  public ArrayList getErreurs() {}
  public List getAllArticles() {}
  public Article getArticleById(int id) {}
  public void acheter(Panier panier) { }
}

Diese Klasse implementiert die vier Methoden der Schnittstelle [IArticlesDomain]. Sie verfügt über zwei private Felder:

IArticlesDao articlesDao
das von der Datenzugriffsebene bereitgestellte Datenzugriffsobjekt
ArrayList erreurs
die Liste eventueller Fehler

Um eine Instanz der Klasse zu erstellen, muss das Objekt bereitgestellt werden, das den Zugriff auf die Daten von SGBD ermöglicht:

public AchatsArticles(IArticlesDao articlesDao)
Konstruktor

Die Klasse [Achat] repräsentiert einen Kauf des Kunden:

package istia.st.articles.domain;

public class Achat {

   // Felder
  private Article article;
  private int qte;

  // Konstruktoren
  public Achat(Article article, int qte) { }

  // Methoden
  public double getTotal() {}
  public Article getArticle() {}
  public void setArticle(Article article) { }
  public int getQte() {}
  public void setQte() { }
  public String toString() {}
}

Die Klasse [Achat] ist eine JavaBean mit den folgenden Feldern und Methoden:

article
der gekaufte Artikel
qte
die gekaufte Menge
double getTotal()
gibt den Kaufbetrag an
String toString()
Identitätszeichenfolge des Objekts

Die Klasse [Panier] repräsentiert alle Einkäufe des Kunden:

package istia.st.articles.domain;

// Importe
import java.util.ArrayList;

public class Panier {

  // Felder
  private ArrayList achats;

   // Konstruktoren
  public Panier() { }

   // Methoden
  public ArrayList getAchats() {}
  public void ajouter(Achat unAchat) { }
  public void enlever(int idAchat) { }
  public double getTotal() {}
  public String toString() { }
}

Die Klasse [Panier] ist eine JavaBean mit den folgenden Feldern und Methoden:

achats
die Liste der Einkäufe des Kunden – Liste von Objekten vom Typ [Achat]
void ajouter(Achat unAchat)
Fügt einen Einkauf zur Einkaufsliste hinzu
void enlever(int idArticle)
Entfernt den Kauf des Artikels idArticle
double getTotal()
berechnet den Gesamtbetrag der Einkäufe
String toString()
gibt die Identitätszeichenfolge des Warenkorbs zurück
ArrayList getAchats()
gibt die Liste der Einkäufe zurück

Der Code des Pakets [domain] ist im Anhang zu finden.

3.3.5. Das Paket [istia.st.articles.exception]

Dieses Paket enthält die Klasse, die die Ausnahme definiert, die von der Schicht [dao] ausgelöst wird, wenn ein Problem beim Zugriff auf die Datenquelle auftritt:

package istia.st.articles.exception;

public class UncheckedAccessArticlesException
    extends RuntimeException {

  public UncheckedAccessArticlesException() {
    super();
  }

  public UncheckedAccessArticlesException(String mesg) {
    super(mesg);
  }

  public UncheckedAccessArticlesException(String mesg, Throwable th) {
    super(mesg, th);
  }
}

3.3.6. Tests des Modells

Das Modell M wurde unter Eclipse mit folgender Konfiguration getestet:

Image

Anmerkungen:

  • In [WEB-INF/lib] befinden sich:
    • die für das Tool [ibatis SqlMap] erforderlichen Archive, das für den Zugriff auf die Firebird-Datenbank SGBD zuständig ist: ibatis-*.jar
    • die für das Tool [spring] erforderliche: spring.jar
    • den Treiber JDBC für SGBD und [Firebird]: firebirdsql-full.jar
    • die für die Protokolle erforderlichen Archive: log4-*.jar, commons-logging.jar
    • die drei Archive des getesteten Modells: istia.st.articles.*.jar
    • das für das Testtool erforderliche Archiv: [junit]
  • In [WEB-INF/src] befinden sich die Konfigurationsdateien, die von Eclipse automatisch in [WEB-INF/classes] kopiert werden:
    • die Konfigurationsdateien des Tools [sqlmap]: sqlmap-config-firebird.xml, articles.xml
    • die des Tools [spring]: spring-config-test-dao.xml, spring-config-test-domain.xml
    • die Konfigurationsdatei des Tools [log4j]: log4j.properties
  • Im Paket [istia.st.articles.tests] befinden sich die Testklassen des Modells

3.3.6.1. Tests der Schicht [dao]

Die Testklasse JUnit der Schicht [dao] sieht wie folgt aus. Anhand ihres Inhalts lässt sich nachvollziehen, wie die Methoden der Schnittstelle [IArticlesDao] verwendet werden:

package istia.st.articles.tests.dao;

import java.util.List;
import junit.framework.TestCase;
import istia.st.articles.dao.IArticlesDao;
import istia.st.articles.dao.Article;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

// Test der Klasse ArticlesDaoSqlMap
public class JunitModeleDaoArticles extends TestCase {

     // eine Instanz der getesteten Klasse
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // Ruft eine Instanz für den Datenzugriff ab
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-dao.xml"))).getBean("articlesDao");
    }

    public void testGetAllArticles() {
         // zeigt die Artikel an
        listArticles();
    }

    public void testClearAllArticles() {
         // leert die Artikeltabelle
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
    }

    public void testAjouteArticle() {
         // Inhalt von ARTICLES löschen
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // Einfügen
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // liest die Tabelle ARTICLES
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         //zeigt sie an
        listArticles();
    }

    public void testSupprimeArticle() {
         // Löschung des Inhalts von ARTICLES
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // Einfügen
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // liest die Tabelle ARTICLES
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         // Löschen
        articlesDao.supprimeArticle(4);
         // liest die Tabelle ARTICLES
        articles = articlesDao.getAllArticles();
        assertEquals(1, articles.size());
         // zeigt die Tabelle an
        listArticles();
    }

    public void testModifieArticle() {
         // Löschung des Inhalts von ARTICLES
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // Einfügen
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // liest die Tabelle ARTICLES
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         // getById
        Article unArticle = articlesDao.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getNom(), "article4");
         // Änderung
        articlesDao.modifieArticle(new Article(4, "article4", 44, 44, 44));
         // getById
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getPrix(), 44, 1e-6);
         // zeigt die Tabelle an
        listArticles();
    }

    public void testGetArticleById() {
         // Löschung des Inhalts von ARTICLES
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // Einfügen
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // liest die Tabelle ARTICLES
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         // getById
        Article unArticle = articlesDao.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getNom(), "article4");
    }

    private void listArticles() {
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
         // Die gelesenen Artikel werden angezeigt
        for (int i = 0; i < articles.size(); i++) {
            System.out.println(((Article) articles.get(i)).toString());
        }
    }

    public void testChangerStockArticle() throws InterruptedException {
         // Löschung des Inhalts von ARTICLES
        articlesDao.clearAllArticles();
         // Einfügen
        int nbArticles = articlesDao.ajouteArticle(new Article(3, "article3",
                30, 101, 3));
        assertEquals(nbArticles, 1);
        nbArticles = articlesDao.ajouteArticle(new Article(4, "article4", 40,
                40, 4));
        assertEquals(nbArticles, 1);
         // Erstellung von 100 Threads zur Aktualisierung des Lagerbestands von Artikel 3
        Thread[] taches = new Thread[100];
        for (int i = 0; i < taches.length; i++) {
            taches[i] = new ThreadMajStock("thread-" + i, articlesDao);
            taches[i].start();
        }
         // Warten auf das Ende der Threads
        for (int i = 0; i < taches.length; i++) {
            taches[i].join();
        }
         // Artikel 3 abrufen und dessen Lagerbestand prüfen
        Article unArticle = articlesDao.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        assertEquals(1, unArticle.getStockActuel());
         // Bestand von Artikel 4 ändern
        boolean erreur = false;
        int nbLignes = articlesDao.changerStockArticle(4, -100);
        assertEquals(0, nbLignes);
         // Zeigt die Tabelle an
        listArticles();
    }
}

Anmerkungen:

  • Die Testklasse speichert mithilfe ihrer Methode setUp eine Instanz der zu testenden Klasse:
1
2
3
4
5
6
7
8
     // eine Instanz der getesteten Klasse
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // Ruft eine Instanz für den Datenzugriff ab
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-dao.xml"))).getBean("articlesDao");
    }
  • Das zu testende Objekt wird von [Spring] bereitgestellt. Oben wird die Spring-Bean mit dem Namen [articlesDao] angefordert. Diese Bean ist in der Spring-Konfigurationsdatei [spring-config-test-dao.xml] definiert:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- die Datenzugriffsklasse -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
</beans>

Wie oben zu sehen ist, handelt es sich bei der Bean „[articlesDao]“ um eine Instanz der Klasse „[istia.st.articles.dao.ArticlesDaoSqlMap]“. Diese Klasse verfügt über einen Konstruktor, der als Parameter den Namen der Konfigurationsdatei des Tools „[SqlMap]“ erwartet. Dieser Name wird hier angegeben. Es handelt sich um [sqlmap-config-firebird.xml]. Letzteres wurde bereits beschrieben. Es liefert alle notwendigen Informationen für den Zugriff auf die Daten von SGBD.

Die Methode [testChangerStockArticle] erstellt 100 Threads, die den Lagerbestand eines bestimmten Artikels verringern sollen. Hier geht es darum, den gleichzeitigen Zugriff auf SGBD zu testen. Da die Methode [changerStockArticle] der Klasse [istia.st.articles.dao.ArticlesDaoSqlMap] synchronisiert wurde, besteht dieser Test. Entfernt man die Synchronisation, besteht er nicht mehr. Die Klasse, die für die Aktualisierung des Bestands zuständig ist, lautet wie folgt:

package istia.st.articles.tests;

import istia.st.articles.dao.IArticlesDao;

public class ThreadMajStock extends Thread {

    /**
     * nom du thread
     */
    private String name;

    /**
     * objet d'accès aux données
     */
    private IArticlesDao articlesDao;

    /**
     * 
     * @param name
     *            le nom du thread afin de l'identifier
     * @param articlesDao
     *            l'objet d'accès aux données du sgbd
     */
    public ThreadMajStock(String name, IArticlesDao articlesDao) {
        this.name = name;
        this.articlesDao = articlesDao;
    }

    /**
     * décrémente le stock de l'article 3 d'une unité fait un suivi écran des
     * opérations
     */
    public void run() {
         // Weiterverfolgung
        System.out.println(name + " lancé");
         // Bestandsänderung Artikel 3
        articlesDao.changerStockArticle(3, -1);
         // Nachverfolgung
        System.out.println(name + " terminé");
    }
}
  • Die oben genannte Klasse verringert den Bestand des Artikels Nr. 3 um 1

3.3.6.2. Tests der Ebene [domain]

Die Testklasse JUnit der Schicht [domain] lautet wie folgt:

package istia.st.articles.tests.domain;

import java.util.List;
import junit.framework.TestCase;
import istia.st.articles.dao.Article;
import istia.st.articles.dao.IArticlesDao;
import istia.st.articles.domain.Achat;
import istia.st.articles.domain.IArticlesDomain;
import istia.st.articles.domain.Panier;

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

// Test der Klasse ArticlesDaoSqlMap
public class JunitModeleDomainArticles extends TestCase {

     // eine Instanz der Domänenzugriffsklasse
    private IArticlesDomain articlesDomain;

     // eine Instanz der Datenzugriffsklasse
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // ruft eine Instanz des Domänenzugriffs ab
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");
         // ruft eine Instanz des Datenzugriffs ab
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");
    }

     // Abruf eines bestimmten Artikels
    public void testGetArticleById() {
         // Löschung des Inhalts von ARTICLES
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // Einfügen
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // liest die Tabelle ARTICLES
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         // getById
        Article unArticle = articlesDomain.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getNom(), "article4");
    }

     // Bildschirmanzeige
    private void listArticles() {
         // liest die Tabelle ARTICLES
        List articles = articlesDomain.getAllArticles();
         // die gelesenen Artikel werden angezeigt
        for (int i = 0; i < articles.size(); i++) {
            System.out.println(((Article) articles.get(i)).toString());
        }
    }

     // Artikel kaufen
    public void testAchatPanier(){
         // Löschen des Inhalts von ARTICLES
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // Einfügen
        Article article3=new Article(3, "article3", 30, 30, 3);
        articlesDao.ajouteArticle(article3);
        Article article4=new Article(4, "article4", 40, 40, 4);
        articlesDao.ajouteArticle(article4);
         // liest die Tabelle ARTICLES
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         // Erstellung eines Warenkorbs mit zwei Artikeln
        Panier panier=new Panier();
        panier.ajouter(new Achat(article3,10));
        panier.ajouter(new Achat(article4,10));
         // Prüfungen
        assertEquals(700.0,panier.getTotal(),1e-6);
        assertEquals(2,panier.getAchats().size());
         // Warenkorb bestätigen
        articlesDomain.acheter(panier);
         // Prüfungen
        assertEquals(0,articlesDomain.getErreurs().size());
        assertEquals(0,panier.getAchats().size());
         // Artikel Nr. 3 suchen
        article3=articlesDomain.getArticleById(3);
        assertEquals(20,article3.getStockActuel());
         // Artikel Nr. 4 suchen
        article4=articlesDomain.getArticleById(4);
        assertEquals(30,article4.getStockActuel());
         // Neuer Warenkorb
        panier.ajouter(new Achat(article3,100));
         // Warenkorb bestätigen
        articlesDomain.acheter(panier);
         // Überprüfung – zu viele Artikel gekauft
         // Es muss ein Fehler vorliegen
        assertEquals(1,articlesDomain.getErreurs().size());
         // Artikel Nr. 3 suchen
        article3=articlesDomain.getArticleById(3);
         // Der Lagerbestand hat sich wohl nicht geändert
        assertEquals(20,article3.getStockActuel());    
    }

     // Einkäufe stornieren
    public void testRetirerAchats(){
         // Inhalt von ARTICLES löschen
        articlesDao.clearAllArticles();
         // liest die Tabelle ARTICLES
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // Einfügen
        Article article3=new Article(3, "article3", 30, 30, 3);
        articlesDao.ajouteArticle(article3);
        Article article4=new Article(4, "article4", 40, 40, 4);
        articlesDao.ajouteArticle(article4);
         // liest die Tabelle ARTICLES
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         // Erstellung eines Warenkorbs mit zwei Artikeln
        Panier panier=new Panier();
        panier.ajouter(new Achat(article3,10));
        panier.ajouter(new Achat(article4,10));
         // Prüfungen
        assertEquals(700.0,panier.getTotal(),1e-6);
        assertEquals(2,panier.getAchats().size());
         // einen bereits gekauften Artikel hinzufügen
        panier.ajouter(new Achat(article3,10));
         // Überprüfungen
         // Der Gesamtbetrag muss auf 1000 geändert werden
        assertEquals(1000.0,panier.getTotal(),1e-6);
         // Immer noch 2 Artikel im Warenkorb
        assertEquals(2,panier.getAchats().size());
         // Die Stückzahl von Artikel 3 muss auf 20 geändert werden
        Achat achat=(Achat)panier.getAchats().get(0);
        assertEquals(20,achat.getQte());
         // Artikel 3 wird aus dem Warenkorb entfernt
        panier.enlever(3);
         // Überprüfungen
         // Der Gesamtbetrag muss auf 400 geändert werden
        assertEquals(400.0,panier.getTotal(),1e-6);
         // Nur 1 Artikel im Warenkorb
        assertEquals(1,panier.getAchats().size());
         // Es muss sich um Artikel Nr. 4 handeln
        assertEquals(4,((Achat)panier.getAchats().get(0)).getArticle().getId());
    }
}

Anmerkungen:

  • Die Testklasse speichert mithilfe ihrer Methode setUp eine Instanz der zu testenden Klasse sowie eine Instanz der Datenzugriffsklasse. Dieser letzte Punkt ist umstritten. Theoretisch sollte die Testklasse keinen Zugriff auf die Schicht [dao] benötigen, die sie gar nicht kennen dürfte. Hier haben wir diese „Ethik“ außer Acht gelassen, deren Einhaltung uns dazu gezwungen hätte, neue Methoden in unserer Schnittstelle [IArticlesDomain] zu erstellen.
     // eine Instanz der Domänenzugriffsklasse
    private IArticlesDomain articlesDomain;

     // eine Instanz der Klasse für den Datenzugriff
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // ruft eine Instanz für den Domänenzugriff ab
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");
         // ruft eine Instanz der Datenzugriffsklasse ab
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");
    }
  • Das zu testende Objekt wird von [Spring] bereitgestellt. Oben wird die Spring-Bean mit dem Namen [articlesDomain] angefordert. Diese Bean ist in der Spring-Konfigurationsdatei [spring-config-test-domain.xml] definiert:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- die Datenzugriffsklasse -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- die Fachklasse -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
</beans>

Wie oben zu sehen ist, handelt es sich bei der Bean [articlesDomain] um eine Instanz der Klasse [istia.st.articles.domain.AchatsArticles]. Diese Klasse verfügt über einen Konstruktor, der als Parameter ein Zugriffsobjekt auf die Schicht [dao] vom Typ [IArticlesDao] erwartet. Hier gibt die Konfigurationsdatei an, dass es sich bei diesem Objekt um die Bean mit dem Namen [articlesDao] handelt. Dies veranlasst Spring, diese Bean zu instanziieren. Die Instanziierung der Bean [articlesDao] wurde bereits zuvor erläutert. Letztendlich wurden also zwei Beans instanziiert:

  • [articlesDao] vom Typ [istia.st.articles.dao.ArticlesDaoSqlMap]
  • [articlesDomain] vom Typ [ istia.st.articles.domain.AchatsArticles]

Diese beiden Instanziierungen werden durch den ersten Aufruf von Spring ausgelöst:

1
2
3
4
         // ruft eine Instanz für den Domänenzugriff ab
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");

Dabei wird die Bean [articlesDomain] abgerufen. Beim zweiten Aufruf von Spring:

1
2
3
         // ruft eine Instanz des Datenzugriffs ab
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");

gibt [Spring] lediglich eine Referenz auf die Bean [articlesDao] zurück, die bereits beim vorherigen Aufruf erstellt wurde. Das ist das Prinzip des Singletons. Wenn man eine Bean von Spring anfordert, instanziiert Spring sie, falls sie noch nicht existiert; andernfalls gibt Spring eine Referenz auf die bereits vorhandene Bean zurück.

3.4. Dreischichtige Webanwendung MVC

Im Folgenden wollen wir die folgende dreischichtige Webanwendung erstellen:

Die Anwendung wird eine Architektur MVC aufweisen. Das Modell M wurde bereits geschrieben und getestet. Es handelt sich um das zuvor beschriebene Modell. Es wird uns in drei Archiven [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception] zur Verfügung gestellt. Wir müssen nun den Controller C und die Views V schreiben.

Betrachten wir zunächst einen klassischen Ansatz, bei dem:

  • der Controller C durch ein einziges Servlet bereitgestellt wird
  • die Ansichten V durch Seiten JSP bereitgestellt werden

3.5. MVC-Architektur auf Basis eines Controller-Servlets und von JSP-Seiten

Die Architektur MVC der Anwendung sieht wie folgt aus:

M=modèle
die Fachklassen, die Datenzugriffsklassen und die Datenbank
V=vues
die Seiten JSP
C=contrôleur
das Servlet zur Verarbeitung von Client-Anfragen

3.5.1. Das Modell

Es wurde bereits zuvor vorgestellt. Es besteht aus den Java-Archiven [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception].

3.5.2. Die Ansichten

Die Ansichten entsprechen denen, die zu Beginn des Dokuments vorgestellt wurden:

LISTE
liste.jsp
Die Ansichten sind im Ordner [vues] der Anwendung zusammengefasst
INFOS
infos.jsp
PANIER
panier.jsp
PANIERVIDE
paniervide.jsp
ERREURS
erreurs.jsp

3.5.3. Der Controller

Der Controller besteht aus einem einzigen Servlet namens [WebArticles]. Er verarbeitet die verschiedenen Anfragen der Clients. Diese werden durch das Vorhandensein eines Parameters [action] in der Anfrage HTTP des Clients formalisiert:

requête
Bedeutung
Aktion des Controllers
Mögliche Antworten
action=liste
Der Kunde möchte die Liste der
Artikel
– fordert die Liste der Artikel bei der
an
– [LISTE]
- [ERREURS]
action=infos
Der Kunde fragt
Informationen zu einem der in der Ansicht angezeigten Artikel
[LISTE]
- fordert den Artikel bei der Geschäftsebene an
- [INFOS]
- [ERREURS]
action=achat
Der Kunde kauft einen Artikel
- fordert den Artikel bei der Geschäftsebene an und
legt ihn in den Warenkorb des Kunden
- [INFOS] bei Mengenfehler
- [LISTE], wenn kein Fehler vorliegt
action=retirerachat
Der Kunde möchte einen
Artikel aus seinem Warenkorb entfernen
- ruft den Warenkorb aus der Sitzung ab und ändert ihn
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
Der Kunde möchte seinen
Warenkorb
- ruft den Warenkorb aus der Sitzung ab
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
Der Kunde hat seinen Einkauf abgeschlossen
und wechselt zur Zahlungsphase
- aktualisiert in der Datenbank die Bestände der
gekauften Artikel
- entfernt aus dem Warenkorb des Kunden die Artikel, deren
deren Kauf bestätigt wurde
- [LISTE]
- [ERREURS]

3.5.4. Konfiguration der Anwendung

Wir werden versuchen, die Anwendung so zu konfigurieren, dass sie gegenüber Änderungen wie den folgenden so flexibel wie möglich ist:

  1. Änderung der URLs der verschiedenen Ansichten
  2. Änderung der Klassen, die die Schnittstellen [IArticlesDao] und [IArticlesDomain] implementieren
  3. Änderung von SGBD, der Datenbank und der Artikeltabelle

3.5.5. Änderungen der URLs

Die Namen der URLs der Ansichten werden zusammen mit einigen weiteren Parametern in der Anwendungskonfigurationsdatei [web.xml] gespeichert:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
        <servlet-name>webarticles</servlet-name>
        <servlet-class>istia.st.articles.web.WebArticles</servlet-class>
        <init-param>
            <param-name>springConfigFileName</param-name>
            <param-value>spring-config-sqlmap-firebird.xml</param-value>
        </init-param>
        <init-param>
            <param-name>urlMain</param-name>
            <param-value>/main</param-value>
        </init-param>
        <init-param>
            <param-name>urlErreurs</param-name>
            <param-value>/vues/erreurs.jsp</param-value>
        </init-param>
        <init-param>
            <param-name>urlListe</param-name>
            <param-value>/vues/liste.jsp</param-value>
        </init-param>
        <init-param>
            <param-name>urlInfos</param-name>
            <param-value>/vues/infos.jsp</param-value>
        </init-param>
        <init-param>
            <param-name>urlPanier</param-name>
            <param-value>/vues/panier.jsp</param-value>
        </init-param>
        <init-param>
            <param-name>urlPanierVide</param-name>
            <param-value>/vues/paniervide.jsp</param-value>
        </init-param>
        <init-param>
            <param-name>urlDebug</param-name>
            <param-value>/vues/debug.jsp</param-value>
        </init-param>
    </servlet>
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
    <servlet-mapping>
        <servlet-name>webarticles</servlet-name>
        <url-pattern>/main</url-pattern>
    </servlet-mapping>
</web-app>

In der Datei [web.xml]

  • die URLs der verschiedenen Ansichten der Anwendung
  • der Name [springConfigFileName] der Spring-Konfigurationsdatei, die die Erstellung von Singleton-Objekten für den Zugriff auf die Geschäftslogik- und DAO-Schichten ermöglicht
  • die Ansicht [/vues/index.jsp], die angezeigt wird, wenn die vom Client angeforderte URL /<context> lautet, wobei <context> der Kontext der Anwendung ist

3.5.6. Die Änderung der Klassen zur Implementierung der Schnittstellen

Im Sinne der dreischichtigen Architektur müssen die Schichten voneinander isoliert sein. Diese Isolation wird wie folgt erreicht:

  • Die Schichten kommunizieren untereinander über Schnittstellen und nicht über konkrete Klassen
  • Der Code einer Schicht instanziiert niemals selbst die Klasse einer anderen Schicht, um sie zu verwenden. Er fordert lediglich von einem externen Tool – in diesem Fall [Spring] – eine Instanz der Implementierung der Schnittstelle der Schicht an, die er verwenden möchte. Dazu muss sie, wie wir wissen, nicht den Namen der Implementierungsklasse kennen, sondern lediglich den Namen des Spring-Beans, auf das sie verweisen möchte.

In unserer Anwendung könnte die Spring-Konfigurationsdatei wie folgt aussehen:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- die Datenzugriffsklasse -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- die Geschäftsklasse -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
</beans>

Um Zugriff auf die Schicht [métier] zu erhalten, kann eine Klasse der Schicht [Interface utilisateur, UI] den Bean [articlesDomain] anfordern. Spring instanziiert dann ein Objekt vom Typ [istia.st.articles.domain.AchatsArticles]. Für diese Instanziierung benötigt es ein Bean vom Typ [articlesDao], d. h. ein Objekt vom Typ [istia.st.articles.dao.ArticlesDaoSqlMap]. Spring instanziiert dann ein solches Objekt. Diese Instanziierung erfolgt anhand der Informationen in der Datei [sqlmap-config-firebird.xml], einer Konfigurationsdatei für den Datenzugriff durch SqlMap. Am Ende des Vorgangs verfügt die Klasse [UI], die den Bean [articlesDomain] angefordert hat, über die gesamte Kette, die sie mit den Daten von SGBD verbindet:

3.5.7. Änderungen im Zusammenhang mit SGBD oder der Datenbank

Die Unabhängigkeit der Webanwendung von Änderungen im Zusammenhang mit SGBD oder der Datenbank wird hier durch die Konfigurationsdateien von SqlMap gewährleistet. Es gibt zwei davon:

  1. die Datei [sql-map-config-firebird.xml]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
    PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
    <transactionManager type="JDBC">
            <dataSource type="SIMPLE">
            <property name="JDBC.Driver" value="org.firebirdsql.jdbc.FBDriver"/>
            <property name="JDBC.ConnectionURL"
                value="jdbc:firebirdsql:localhost/3050:d:/data/databases/firebird/dbarticles.gdb"/>
            <property name="JDBC.Username" value="sysdba"/>
            <property name="JDBC.Password" value="masterkey"/>
            <property name="JDBC.DefaultAutoCommit" value="true"/>
        </dataSource>
    </transactionManager>
    <sqlMap resource="articles.xml"/>
</sqlMapConfig>

Diese Datei verweist hier auf eine Firebird-Datenbank. Es reicht aus, den Namen des Treibers JDBC zu ändern, um mit einem anderen Treiber, z. B. SGBD, zu arbeiten.

  1. die Datei [articles.xml], die die verschiedenen für die Anwendung erforderlichen Anweisungen SQL enthält:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
    PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="Articles">

     <!-- ein Alias für die Klasse istia.st.articles.dao.Article -->
  <typeAlias alias="article" type="istia.st.articles.dao.Article"/>

     <!-- das Mapping OR M: Tabellenzeile ARTICLES – Instanz der Klasse „Artikel“ -->
  <resultMap id="article" class="article">
    <result property="id" column="ID"/>
    <result property="nom" column="NOM"/>
    <result property="prix" column="PRIX"/>
    <result property="stockActuel" column="STOCKACTUEL"/>
    <result property="stockMinimum" column="STOCKMINIMUM"/>
  </resultMap>

     <!-- die Abfrage SQL zum Abrufen aller Artikel -->
  <statement id="getAllArticles" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum from ARTICLES
</statement>

     <!-- die Abfrage SQL zum Löschen aller Artikel -->
  <statement id="clearAllArticles">delete from ARTICLES</statement>

     <!-- die Abfrage SQL zum Einfügen eines Artikels -->
  <statement id="insertArticle">
    insert into ARTICLES (id, nom, prix,
    stockactuel, stockminimum) values
    (#(id#,#name#,#preis#,#aktuellerBestand#,#Mindestbestand#)
</statement>

     <!-- die Abfrage SQL zum Löschen eines bestimmten Artikels -->
  <statement id="deleteArticle">delete FROM ARTICLES where id=#id#</statement>

     <!-- die Abfrage SQL zum Ändern eines bestimmten Artikels -->
  <statement id="modifyArticle">
    update ARTICLES set nom=#Name#,
    prix=#Preis#,aktuellerBestand=#aktuellerBestand#,Mindestbestand=#Mindestbestand# where
    id=#id#
</statement>

     <!-- die Abfrage SQL zum Abrufen eines bestimmten Artikels -->
  <statement id="getArticleById" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum FROM ARTICLES where id=#id#
</statement>

     <!-- die Abfrage SQL zum Ändern des Bestands eines bestimmten Artikels -->
  <statement id="changerStockArticle">
    update ARTICLES set
    stockActuel=stockActuel+#Bewegung#
    where id=#id# und stockActuel+#Bewegung#&gt;=0
</statement>
</sqlMap>

Sollten sich die Namen der Artikeltabelle oder der Spalten ändern, müssten die Abfragen in dieser Konfigurationsdatei neu geschrieben werden, ohne dass der Java-Code geändert werden müsste. Dies wäre auch der Fall, wenn eine Abfrage aus Leistungsgründen durch eine gespeicherte Prozedur ersetzt würde.

3.5.8. Die Gesamtarchitektur der Anwendung [webarticles]

Eine Java-Webanwendung ist wie ein Puzzle mit vielen Teilen. Durch die Einführung einer MVC-Architektur erhöht sich in der Regel die Anzahl dieser Teile. Die Struktur der Anwendung [webarticles] unter [eclipse] sieht wie folgt aus:

structure générale - on voit ci-dessous les
 archives Java utilisées par le projet
 Eclipse.
spring : pour Spring
ibatis : pour SqlMap
log4j, commons-logging : pour les logs
 de Spring et Sqlmap
firebird : pour le SGBD firebird
mysql : pour le SGBD MySQL
jstl, standard : pour la bibliothèque
 de balises JSTL
le dossier des sources java : contient le code Java
 ainsi que les fichiers de configuration
 spring et sqlmap. Eclipse recopie
 automatiquement ces fichiers 
dans [WEB-INF/classes]. 
C'est là que l'application les trouvera.
le dossier [WEB-INF] de l'application : contient le
 descripteur [web.xml] de l'application ainsi que le
 fichiers de définition de la bibliothèques JSTL
les vues

3.5.9. Die Ansichten JSP

Die Ansichten JSP verwenden die Tag-Bibliothek JSTL.

3.5.9.1. entete.jsp

Um den verschiedenen Ansichten eine gewisse Einheitlichkeit zu verleihen, werden diese denselben Kopfbereich verwenden, in dem der Name der Anwendung zusammen mit dem Menü angezeigt wird:

Das Menü ist dynamisch und wird vom Controller festgelegt. Dieser fügt in die an die Seite JSP übermittelte Anfrage ein Schlüsselattribut „actions“ ein, dessen zugehöriger Wert ein Array vom Typ Hastable[] ist. Jedes Element dieses Arrays ist ein Dictionary, das dazu dient, eine Option des Kopfmenüs zu generieren. Jedes Dictionary hat zwei Schlüssel:

  • href: die mit der Menüoption verknüpfte URL
  • lien: der Text der Menüoption

Die anderen Ansichten der Anwendung verwenden die durch [entete.jsp] definierte Kopfzeile mithilfe des folgenden Tags JSP:

<jsp:include page="entete.jsp"/>

Bei der Ausführung bewirkt dieses Tag, dass der Code der Seite JSP, die es enthält, den Code der Seite [entete.jsp] einbindet. Da es sich bei der URL der Seite um eine relative URL handelt (ohne „/“), wird die Seite „[entete.jsp]“ im selben Ordner gesucht wie die Seite, die das Tag <jsp:include> enthält.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<html>
    <head>
        <title>webarticles</title>
    </head>
    <body>
        <table>
            <tr>
                <td><h2>Magasin virtuel</h2></td>
                <c:forEach items="${actions}" var="action">
                    <td>|</td>
                    <td><a href="<c:out value="${action.href}"/>"><c:out value="${action.lien}"/></a></td>
                </c:forEach>
            </tr>
        </table>
        <hr>

3.5.9.2. liste.jsp

Diese Ansicht zeigt die Liste der zum Verkauf stehenden Artikel an:

Sie wird nach einer Anfrage an /main?action=liste oder /main?action=validationpanier angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
listarticles
ArrayList Objekte vom Typ [Article]
message
String-Objekt – am Seitenende anzuzeigende Meldung

Jeder Link [Infos] im Array HTML der Artikel hat eine URL der Form [?action=infos&id=ID], wobei ID das ID-Feld des angezeigten Artikels ist.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Liste des articles</h2>
        <table border="1">
            <tr>
                <th>NOM</th><th>PRIX</th>
            </tr>
            <c:forEach var="article" items="${listarticles}">
                <tr>
                    <td><c:out value="${article.nom}"/></td>
                    <td><c:out value="${article.prix}"/></td>
                    <td><a href="<c:out value="?action=infos&id=${article.id}"/>">Infos</a></td>
                </tr>
            </c:forEach>
        </table>
        <p>
        <c:out value="${message}"/>
    </body>
</html>

3.5.9.3. infos.jsp

Diese Ansicht zeigt Informationen zu einem Artikel an und ermöglicht zudem dessen Kauf:

Image

Sie wird nach einer Anfrage an /main?action=infos&id=ID oder einer Anfrage an /main?action=achat&id=ID angezeigt, wenn die gekaufte Menge falsch ist. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
article
Objekt vom Typ [Article] – anzuzeigender Artikel
msg
String-Objekt – Meldung, die bei einem Fehler bezüglich der Menge angezeigt werden soll
qte
String-Objekt – im Eingabefeld anzuzeigender Wert [Qte]

Die Felder [msg] und [qte] werden bei einem Eingabefehler bei der Menge verwendet:

Image

Diese Seite enthält ein Formular, das über die Schaltfläche [Acheter] übermittelt wird. Die Ziel-URL von POST lautet [?action=achat&id=ID], wobei ID die ID des gekauften Artikels ist.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Article d'id [<c:out value="${article.id}"/>]</h2>
        <table border="1">
            <tr>
                <th>NOM</th><th>PRIX</th><th>STOCK ACTUEL</th><th>STOCK MINIMUM</th>
            </tr>
                <tr>
                    <td><c:out value="${article.nom}"/></td>
                    <td><c:out value="${article.prix}"/></td>
                    <td><c:out value="${article.stockActuel}"/></td>
                    <td><c:out value="${article.stockMinimum}"/></td>
                </tr>
        </table>
        <p>
        <form method="post" action="?action=achat&id=<c:out value="${article.id}"/>"/>
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qte <input type="text" name="qte" size="3" value="<c:out value="${qte}"/>"></td>
                    <td><c:out value="${msg}"/></td>
                </tr>
            </table>
        </form>
    </body>
</html>

3.5.9.4. panier.jsp

Diese Ansicht zeigt den Inhalt des Warenkorbs an:

Image

Sie wird nach einer Anfrage an /main?action=warenkorb oder /main?action=retirerachat&id=ID angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
panier
Objekt vom Typ [Panier] – der anzuzeigende Warenkorb

Jeder Link [Retirer] im Array HTML der Warenkorb-Artikel hat eine URL der Form [?action=retirerachat&id=ID], wobei ID das Feld [id] desArtikels, den man aus dem Warenkorb entfernen möchte.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Contenu de votre panier</h2>
        <table border="1">
            <tr>
                <td>Article</td><td>Qte</td><td>Pu</td><td>Total</td>
            </tr>
            <c:forEach var="achat" items="${panier.achats}">
                <tr>
                    <td><c:out value="${achat.article.nom}"/></td>
                    <td><c:out value="${achat.qte}"/></td>
                    <td><c:out value="${achat.article.prix}"/></td>
                    <td><c:out value="${achat.total}"/></td>
                    <td><a href="<c:out value="?action=retirerachat&id=${achat.article.id}"/>">Retirer</a></td>
                </tr>
            </c:forEach>
        </table>
        <p>
        Total de la commande : <c:out value="${panier.total}"/> euros
    </body>
</html>

3.5.9.5. paniervide.jsp

Diese Ansicht zeigt die Information an, dass der Warenkorb leer ist:

Image

Sie wird nach einer Anfrage an /main?action=warenkorb oder /main?action=retirerachat&id=ID angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen

Code:

1
2
3
4
5
6
7
8
9
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Contenu de votre panier</h2>
        <p>
        Votre panier est vide.
    </body>
</html>

3.5.9.6. erreurs.jsp

Diese Ansicht wird bei Fehlern angezeigt:

Image

Sie wird nach jeder Anfrage angezeigt, die zu einem Fehler führt, mit Ausnahme der Kaufaktion mit einer falschen Menge, die von der Ansicht [INFOS] verarbeitet wird. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
erreurs
ArrayList – eine Reihe von String-Objekten, die die anzuzeigenden Fehlermeldungen darstellen

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Les erreurs suivantes se sont produites</h2>
        <ul>
            <c:forEach var="erreur" items="${erreurs}">
                <li><c:out value="${erreur}"/></li>
            </c:forEach>
        </ul>
    </body>
</html>

3.5.9.7. index.jsp

Diese Seite ist in der Anwendungsdatei [web.xml] als Startseite der Anwendung definiert:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
....
    </servlet>
    <servlet-mapping>
....
    </servlet-mapping>
        <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>    
</web-app>

Die Ansicht [index.jsp] leitet den Client lediglich zum Einstiegspunkt der Anwendung weiter:

1
2
3
4
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/main?action=liste"/>

3.5.10. Der Controller

Nun muss noch das Herzstück unserer Webanwendung geschrieben werden: der Controller. Seine Aufgabe besteht darin:

  • die Anfrage des Clients entgegenzunehmen,
  • die von ihm angeforderte Aktion mithilfe der Fachklassen bearbeiten,
  • die entsprechende Ansicht als Antwort zurückgeben.

3.5.10.1. Initialisierung des Controllers

Wenn die Controller-Klasse vom Servlet-Server geladen wird, wird ihre Methode [init] ausgeführt. Dies geschieht nur einmal. Sobald der Controller in den Arbeitsspeicher geladen ist, verbleibt er dort und verarbeitet die Anfragen der verschiedenen Clients. Für jeden Client wird ein eigener Ausführungs-Thread erstellt, sodass die Methoden des Controllers gleichzeitig von verschiedenen Threads ausgeführt werden. Es sei daran erinnert, dass der Controller aus diesem Grund keine Felder enthalten darf, die von seinen Methoden geändert werden könnten. Seine Felder müssen schreibgeschützt sein. Sie werden von der Methode [init] initialisiert, was deren Hauptaufgabe ist. Diese Methode hat nämlich die Besonderheit, dass sie nur einmal von einem einzigen Thread ausgeführt wird. Es gibt daher in dieser Methode keine Probleme mit konkurrierendem Zugriff auf die Felder des Controllers. Die Methode [init] dient dazu, die für die Webanwendung erforderlichen Objekte zu initialisieren, die von allen Client-Threads im schreibgeschützten Modus gemeinsam genutzt werden. Diese gemeinsam genutzten Objekte können an zwei Stellen platziert werden:

  • in den privaten Feldern des Controllers
  • im Ausführungskontext der Anwendung (ServletContext)

Die Methode [init] der Anwendung [webarticles] führt folgende Aktionen durch:

  • Sie überprüft, ob in der Datei [web.xml] die für den ordnungsgemäßen Betrieb der Anwendung erforderlichen Parameter vorhanden sind. Diese wurden in Abschnitt 3.5.5 beschrieben.
  • ein privates Feld [ArrayList erreurs] mit der Liste eventueller Fehler setzt. Diese Liste ist leer, wenn keine Fehler vorliegen, existiert aber dennoch.
  • Wenn Fehler aufgetreten sind, bricht die Methode [init] an dieser Stelle ab. Andernfalls erstellt sie ein Objekt vom Typ [IArticlesDomain], das als Geschäftsobjekt dient, das der Controller für seine Zwecke verwendet. Wie in 3.5.6 erläutert, fordert der Controller das benötigte Bean vom Spring-Framework an. Bei dieser Instanziierung können verschiedene Fehler auftreten. Ist dies der Fall, werden diese wiederum im Feld [erreurs] des Controllers gespeichert.

3.5.10.2. Methoden doGet, doPost

Diese beiden Methoden verarbeiten die Client-Anfragen HTTP, GET und POST. Diese werden unterschiedslos verarbeitet. Die Methode [doPost] kann somit an die Methode [doGet] weiterleiten oder umgekehrt. Die Kundenanfrage wird wie folgt verarbeitet:

  • Das Feld [erreurs] wird überprüft. Ist es nicht leer, bedeutet dies, dass bei der Initialisierung der Anwendung Fehler aufgetreten sind und diese nicht funktionieren kann. Als Antwort wird dann die Ansicht [ERREURS] gesendet.
  • Der Parameter [action] der Anfrage wird abgerufen und überprüft. Entspricht er keiner bekannten Aktion, wird die Ansicht [ERREURS] mit einer entsprechenden Fehlermeldung gesendet.
  • Ist der Parameter [action] gültig, wird die Anfrage des Clients zur Bearbeitung an eine aktionsspezifische Prozedur weitergeleitet. Die Prozedur zur Bearbeitung der Aktion [uneAction] hat folgende Signatur:
1
2
3
4
5
6
7
8
/**
   * @param request la requête du client
   * @param response la réponse au client
   * @throws IOException
   * @throws ServletException
   */
  private void doUneAction(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException; 

3.5.10.3. Verarbeitung der verschiedenen Aktionen

Die Methoden zur Verarbeitung der verschiedenen möglichen Aktionen der Anwendung lauten wie folgt:

Methode
Anfrage
Verarbeitung
Mögliche Antworten
doListe
GET /main?action=liste
- Liste der Artikel
bei der Fachklasse
- anzeigen
[LISTE] oder [ERREURS]
doInfos
GET /main?action=infos&id=ID
- den Artikel mit der ID „ID“ bei
der Fachklasse
- anzeigen
[INFOS] oder [ERREURS]
doAchat
POST /main?action=achat&id=ID
- Die gekaufte Menge ist Teil der übermittelten Parameter
- den Artikel mit der ID=ID bei
der Fachklasse abfragen
- in den Warenkorb aufnehmen in
der Kundensitzung
[LISTE] oder [INFOS]
oder [ERREURS]
doRetirerAchat
GET /main?action=retirerachat&id=ID
- Artikel mit der ID=ID aus der
Einkaufsliste des Warenkorbs
der Kundensitzung
[PANIER]
doPanier
GET /main?action=warenkorb
- Warenkorb der
Kundensitzung
[PANIER] oder [PANIERVIDE]
doValidationPanier
GET /main?action=warenkorbbestätigung
- die Lagerbestände aller Artikel in der Datenbank verringern
Bestände aller Artikel
, die sich im Warenkorb der
Kunden-Sitzung
[LISTE] oder [ERREURS]

3.5.10.4. Der Code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
package istia.st.articles.web;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import istia.st.articles.domain.Achat;
import istia.st.articles.dao.Article;
import istia.st.articles.domain.IArticlesDomain;
import istia.st.articles.domain.Panier;
import istia.st.articles.exception.UncheckedAccessArticlesException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

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

/**
 * @author ST
 *  
 */
public class WebArticles extends HttpServlet {

     // private Felder
    private ArrayList erreurs = new ArrayList();
    private IArticlesDomain articlesDomain = null;
    private final String URL_MAIN = "urlMain";
    private final String URL_ERREURS = "urlErreurs";
    private final String URL_LISTE = "urlListe";
    private final String URL_INFOS = "urlInfos";
    private final String URL_PANIER = "urlPanier";
    private final String URL_PANIER_VIDE = "urlPanierVide";
    private final String URL_DEBUG = "urlDebug";
    private final String SPRING_CONFIG_FILENAME = "springConfigFileName";
    private final String[] parameters =
        {
            URL_MAIN,
            URL_ERREURS,
            URL_LISTE,
            URL_INFOS,
            URL_PANIER,
            URL_PANIER_VIDE,
            URL_DEBUG,
            SPRING_CONFIG_FILENAME };
    private ServletConfig config;
    private final String ACTION_LISTE = "liste";
    private final String ACTION_PANIER = "panier";
    private final String ACTION_ACHAT = "achat";
    private final String ACTION_INFOS = "infos";
    private final String ACTION_RETIRER_ACHAT = "retirerachat";
    private final String ACTION_VALIDATION_PANIER = "validationpanier";
    private String urlActionListe;
    private final String lienActionListe = "Liste des articles";
    private String urlActionPanier;
    private final String lienActionPanier = "Voir le panier";
    private String urlActionValidationPanier;
    private final String lienActionValidationPanier = "Valider le panier";
    private Hashtable hActionListe = new Hashtable(2);
    private Hashtable hActionPanier = new Hashtable(2);
    private Hashtable hActionValidationPanier = new Hashtable(2);

    public void init() {
         // Die Initialisierungsparameter des Servlets werden abgerufen
        config = getServletConfig();
        String param = null;
        for (int i = 0; i < parameters.length; i++) {
            param = config.getInitParameter(parameters[i]);
            if (param == null) {
                 // Der Fehler wird gespeichert
                erreurs.add(
                    "Paramètre ["
                        + parameters[i]
                        + "] absent dans le fichier [web.xml]");
            }
        }
         // Gibt es Fehler?
        if (erreurs.size() != 0) {
            return;
        }
         // Es wird ein Objekt IArticlesDomain für den Zugriff auf die Geschäftsebene angelegt
        try {
            articlesDomain =
                (IArticlesDomain)
                    (
                        new XmlBeanFactory(
                            new ClassPathResource(
                                (String) config.getInitParameter(
                                    SPRING_CONFIG_FILENAME)))).getBean(
                    "articlesDomain");
        } catch (Exception ex) {
             // Der Fehler wird protokolliert
            erreurs.add(
                "Erreur de configuration de l'accès aux données : "
                    + ex.toString());
            return;
        }
         // Es werden bestimmte URLs der Anwendung gespeichert
        hActionListe.put("href", "?action=" + ACTION_LISTE);
        hActionListe.put("lien", lienActionListe);
        hActionPanier.put("href", "?action=" + ACTION_PANIER);
        hActionPanier.put("lien", lienActionPanier);
        hActionValidationPanier.put(
            "href",
            "?action=" + ACTION_VALIDATION_PANIER);
        hActionValidationPanier.put("lien", lienActionValidationPanier);

         // Fertig
        return;
    }


    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {

         // Es wird überprüft, wie die Initialisierung des Servlets verlaufen ist
        if (erreurs.size() != 0) {
             // Liegt die URL der Fehlerseite vor?
            if (config.getInitParameter(URL_ERREURS) == null) {
                throw new ServletException(erreurs.toString());
            }
             // Die Fehlerseite wird angezeigt
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] {
            });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // Ende
            return;
        }
         // Die Aktion wird verarbeitet
        String action = request.getParameter("action");
        if (action == null) {
             // Liste der Artikel
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_LISTE)) {
             // Liste der Artikel
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_INFOS)) {
             // Informationen zu einem Artikel
            doInfos(request, response);
            return;
        }
        if (action.equals(ACTION_ACHAT)) {
             // Artikel kaufen
            doAchat(request, response);
            return;
        }
        if (action.equals(ACTION_PANIER)) {
             // Warenkorb anzeigen
            doPanier(request, response);
            return;
        }
        if (action.equals(ACTION_RETIRER_ACHAT)) {
             // Artikel aus dem Warenkorb entfernen
            doRetirerAchat(request, response);
            return;
        }
        if (action.equals(ACTION_VALIDATION_PANIER)) {
             // Warenkorb bestätigen
            doValidationPanier(request, response);
            return;
        }
         // unbekannte Aktion
        ArrayList erreurs = new ArrayList();
        erreurs.add("action [" + action + "] inconnue");
         // Die Fehlerseite wird angezeigt
        request.setAttribute("actions", new Hashtable[] { hActionListe });
        afficheErreurs(request, response, erreurs);
         // Ende
        return;
    }


    private void doValidationPanier(
        HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {

         // Der Käufer hat seinen Warenkorb bestätigt
        Panier panier = (Panier) request.getSession().getAttribute("panier");
         // Dieser Warenkorb wird bestätigt
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // nicht normal
            erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // Fehler werden abgerufen
        ArrayList erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
            request.setAttribute(
                "actions",
                new Hashtable[] { hActionListe, hActionPanier });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // die Artikelliste wird angezeigt
        request.setAttribute("message", "Votre panier a été validé");
        doListe(request, response);
         // Ende
        return;
    }


    private void doRetirerAchat(
        HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {

         // einen Artikel aus dem Warenkorb entfernen
        try {
            Panier panier =
                (Panier) request.getSession().getAttribute("panier");
            String strIdAchat = request.getParameter("id");
            panier.enlever(Integer.parseInt(strIdAchat));
        } catch (NumberFormatException ignored) {
        } catch (NullPointerException ignored) {
        }
         // Warenkorb anzeigen
        doPanier(request, response);
    }


    private void doPanier(
        HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {

         // Warenkorb anzeigen
        Panier panier = (Panier) request.getSession().getAttribute("panier");
         // Warenkorb leer?
        if (panier == null || panier.getAchats().size() == 0) {
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_PANIER_VIDE))
                .forward(request, response);
             // Ende
            return;
        }
         // Es befindet sich etwas im Warenkorb
        request.setAttribute("panier", panier);
        request.setAttribute(
            "actions",
            new Hashtable[] { hActionListe, hActionValidationPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_PANIER))
            .forward(request, response);
         // Ende
        return;
    }


    private void doAchat(
        HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {

         // Kauf eines Artikels
         // Menge abrufen
        int qté = 0;
        try {
            qté = Integer.parseInt(request.getParameter("qte"));
            if (qté <= 0)
                throw new NumberFormatException();
        } catch (NumberFormatException ex) {
             // falsche Menge
            request.setAttribute("msg", "Quantité incorrecte");
            request.setAttribute("qte", request.getParameter("qte"));
            String url =
                config.getInitParameter(URL_MAIN)
                    + "?action=infos&id="
                    + request.getParameter("id");
            getServletContext().getRequestDispatcher(url).forward(
                request,
                response);
             // Ende
            return;
        }
         // Kundensitzung abrufen
        HttpSession session = request.getSession();
         // Kauf wird angelegt
        Article article = (Article) session.getAttribute("article");
        Achat achat = new Achat(article, qté);
         // Der Kauf wird in den Warenkorb des Kunden gelegt
        Panier panier = (Panier) session.getAttribute("panier");
        if (panier == null) {
            panier = new Panier();
            session.setAttribute("panier", panier);
        }
        panier.ajouter(achat);
         // Rückkehr zur Artikelliste
        String url = config.getInitParameter(URL_MAIN) + "?action=liste";
        getServletContext().getRequestDispatcher(url).forward(
            request,
            response);
         // Ende
        return;
    }


    private void afficheDebugInfos(
        HttpServletRequest request,
        HttpServletResponse response,
        ArrayList infos)
        throws ServletException, IOException {

         // Die Artikelliste wird angezeigt
        request.setAttribute("infos", infos);
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_DEBUG))
            .forward(request, response);
         // Ende
        return;
    }


    public void doPost(
        HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException {

         // wie get
        doGet(request, response);
    }


    private void doInfos(
        HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException {

         // die Fehlerliste
        ArrayList erreurs = new ArrayList();
         // die angeforderte ID wird abgerufen
        String strId = request.getParameter("id");
         // Irgendetwas?
        if (strId == null) {
             // nicht normal
            erreurs.add("action incorrecte([infos,id=null]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // strId wird vollständig umgewandelt
        int id = 0;
        try {
            id = Integer.parseInt(strId);
        } catch (Exception ex) {
             // nicht normal
            erreurs.add("action incorrecte([infos,id=" + strId + "]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // Der Schlüssel-ID-Eintrag wird angefordert
        Article article = null;
        try {
            article=articlesDomain.getArticleById(id);
        } catch (UncheckedAccessArticlesException ex) {
             // nicht normal
            erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
        if (article == null) {
             // nicht normal
            erreurs.add("Article de clé [" + id + "] inexistant");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // Der Artikel wird in die Sitzung aufgenommen
        request.getSession().setAttribute("article", article);
         // Die Informationsseite wird angezeigt
        request.setAttribute("actions", new Hashtable[] { hActionListe });
         /   / request.setAttribute("urlMain",config.getInitParameter(URL_MAIN));
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_INFOS))
            .forward(request, response);
         // Ende
        return;
    }

    private void afficheErreurs(
        HttpServletRequest request,
        HttpServletResponse response,
        ArrayList erreurs)
        throws ServletException, IOException {

         // die Fehlerseite wird angezeigt
        request.setAttribute("erreurs", erreurs);
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
            .forward(request, response);
         // Ende
        return;
    }


    private void doListe(
        HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException {

         // die Fehlerliste
        ArrayList erreurs = new ArrayList();
         // die Artikelliste wird angefordert
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // Der Fehler wird gespeichert
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
         // Gibt es Fehler?
        if (erreurs.size() != 0) {
             // Die Fehlerseite wird angezeigt
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // Ende
            return;
        }
         // die Artikelliste wird angezeigt
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);
         // Ende
        return;
    }

    /**
     * suivi console pour débogage
     * @param message : le message à afficher
     */
    private void affiche(String message) {
        System.out.println(message);
    }
}

Wir lassen dem Leser Zeit, diesen Code in Ruhe zu lesen und zu verstehen. Wir hoffen, dass die Kommentare dabei helfen.

3.5.10.5. Tests der Anwendung

Sehen wir uns einige Screenshots aus den Tests an. Zunächst die Startseite der Anwendung:

Image

Die angeforderte URL lautete tatsächlich [http://localhost:8080/webarticles]. Der Leser wird feststellen, dass wir in der Datei [web.xml] eine Startseite für die Anwendung definieren:

    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>    

Die Ansicht [index.jsp] ist wie folgt definiert:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/main?action=liste"/>

Es erfolgte also eine Weiterleitung zur URL [http://localhost:8080/webarticles/main?action=liste], wie die Browser-URL auf dem Screenshot zeigt. Die URL [/main?action=liste] wurde also aufgerufen. Ebenfalls unter [web.xml] ist die URL /main dem Servlet [webarticles] zugeordnet:

    <servlet-mapping>
        <servlet-name>webarticles</servlet-name>
        <url-pattern>/main</url-pattern>
    </servlet-mapping>

Ebenfalls in [web.xml] ist das Servlet [webarticles] mit dem Servlet [ istia.st.articles.web.WebArticles] verknüpft:

        <servlet-name>webarticles</servlet-name>
        <servlet-class>istia.st.articles.web.WebArticles</servlet-class>

Das Servlet [ istia.st.articles.web.WebArticles] wird daher vom Tomcat-Servlet-Container geladen, falls dies noch nicht geschehen ist, und seine Methode [init] wird ausgeführt:

    public void init() {
         // die Initialisierungsparameter des Servlets werden abgerufen
        config = getServletConfig();
        String param = null;
        for (int i = 0; i < parameters.length; i++) {
            param = config.getInitParameter(parameters[i]);
            if (param == null) {
                 // Der Fehler wird gespeichert
                erreurs.add(
                    "Paramètre ["
                        + parameters[i]
                        + "] absent dans le fichier [web.xml]");
            }
        }
         // Gibt es Fehler?
        if (erreurs.size() != 0) {
            return;
        }
         // Ein Objekt IArticlesDomain für den Zugriff auf die Geschäftslogik wird erstellt
        try {
            articlesDomain =
                (IArticlesDomain)
                    (
                        new XmlBeanFactory(
                            new ClassPathResource(
                                (String) config.getInitParameter(
                                    SPRING_CONFIG_FILENAME)))).getBean(
                    "articlesDomain");
        } catch (Exception ex) {
             // Der Fehler wird gespeichert
            erreurs.add(
                "Erreur de configuration de l'accès aux données : "
                    + ex.toString());
            return;
        }
         // bestimmte URLs der Anwendung werden gespeichert
        hActionListe.put("href", "?action=" + ACTION_LISTE);
        hActionListe.put("lien", lienActionListe);
        hActionPanier.put("href", "?action=" + ACTION_PANIER);
        hActionPanier.put("lien", lienActionPanier);
        hActionValidationPanier.put(
            "href",
            "?action=" + ACTION_VALIDATION_PANIER);
        hActionValidationPanier.put("lien", lienActionValidationPanier);

         // Fertig
        return;
    }

Anmerkungen: Die Methode [init]

  • überprüft das Vorhandensein bestimmter Konfigurationsparameter
  • instanziiert mithilfe von Spring einen Dienst für den Zugriff auf die Anwendungsdomäne
  • legt eine Fehlerliste an, um eventuelle Initialisierungsfehler anzuzeigen
  • eine Reihe privater Felder:
    • Fehler: die Liste der von [init] erkannten Fehler
    • [hActionListe, hActionPanier, hActionValidationPanier]: Wörterbücher. Jedes davon enthält die erforderlichen Informationen zur Anzeige einer Option des Hauptmenüs, das von der Ansicht [entete.jsp] angezeigt wird
    • acticlesDomain: Der Dienst für den Zugriff auf das Anwendungsmodell

Die Methode [init] wird nur einmal ausgeführt, und zwar beim ersten Laden des Servlets. Anschließend wird je nach Typ [GET, POST] der Client-Anfrage eine der Methoden [doGet, doPost] ausgeführt. Hier führen beide Methoden dasselbe aus, und der Code wurde in [doGet] platziert:

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {
         // Es wird überprüft, wie die Initialisierung des Servlets verlaufen ist
        if (erreurs.size() != 0) {
             // Liegt die URL der Fehlerseite vor?
            if (config.getInitParameter(URL_ERREURS) == null) {
                throw new ServletException(erreurs.toString());
            }
             // Die Fehlerseite wird angezeigt
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] {
            });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // Ende
            return;
        }
         // Die Aktion wird verarbeitet
        String action = request.getParameter("action");
        if (action == null) {
             // Liste der Artikel
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_LISTE)) {
             // Liste der Artikel
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_INFOS)) {
             // Informationen zu einem Artikel
            doInfos(request, response);
            return;
        }
        if (action.equals(ACTION_ACHAT)) {
             // Artikel kaufen
            doAchat(request, response);
            return;
        }
        if (action.equals(ACTION_PANIER)) {
             // Warenkorb anzeigen
            doPanier(request, response);
            return;
        }
        if (action.equals(ACTION_RETIRER_ACHAT)) {
             // Artikel aus dem Warenkorb entfernen
            doRetirerAchat(request, response);
            return;
        }
        if (action.equals(ACTION_VALIDATION_PANIER)) {
             // Warenkorb bestätigen
            doValidationPanier(request, response);
            return;
        }
         // unbekannte Aktion
        ArrayList erreurs = new ArrayList();
        erreurs.add("action [" + action + "] inconnue");
         // Die Fehlerseite wird angezeigt
        request.setAttribute("actions", new Hashtable[] { hActionListe });
        afficheErreurs(request, response, erreurs);
         // Ende
        return;
    }
  • Die Methode [doGet] prüft zunächst, ob nach Ausführung der Methode [init] Initialisierungsfehler aufgetreten sind. Wenn ja, lässt sie die Ansicht [ERREURS] anzeigen, und damit ist der Vorgang beendet.
  • Andernfalls ruft sie den Parameter [action] aus der Client-Anfrage ab. Zur Erinnerung: Die Anwendung wurde so konzipiert, dass sie nur Anfragen bearbeitet, die zwingend einen Parameter [action] enthalten.
  • Sie lässt die mit der Aktion verknüpfte Methode ausführen. In diesem Fall ist das die Methode [doListe].

Die Methode [doListe] lautet wie folgt:

    private void doListe(
        HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException {

         // die Fehlerliste
        ArrayList erreurs = new ArrayList();
         // die Artikelliste wird angefordert
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // Der Fehler wird gespeichert
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
         // Gibt es Fehler?
        if (erreurs.size() != 0) {
             // Die Fehlerseite wird angezeigt
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // Ende
            return;
        }
         // Artikelübersicht anzeigen
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);
         // Ende
        return;
    }
  • Zur Erinnerung: Die Methode [init] hat den Dienst für den Zugriff auf das Anwendungsmodell (Domain-Schicht) in einem privaten Feld des Servlets gespeichert:
     // private Felder
    private IArticlesDomain articlesDomain = null;
  • Über diesen Zugriffsdienst kann die Liste der Artikel abgefragt werden:
         // die Fehlerliste
        ArrayList erreurs = new ArrayList();
         // Artikelübersicht anfordern
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // Der Fehler wird gespeichert
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
  • Sollten Fehler auftreten, wird die Ansicht [ERREURS] gesendet:
         // Gibt es Fehler?
        if (erreurs.size() != 0) {
             // Die Fehlerseite wird angezeigt
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // Ende
            return;
        }
  • Andernfalls wird die Ansicht [LISTE] gesendet:
1
2
3
4
5
6
7
         // Artikelübersicht anzeigen
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);

In unserem Beispiel ist alles gut gelaufen und wir haben die Ansicht [LISTE] erhalten. Der Leser sei gebeten, den Code der Ansicht [LISTE] noch einmal durchzulesen, um festzustellen, dass die von dieser Ansicht erwarteten dynamischen Parameter oben vom Controller tatsächlich bereitgestellt werden. Die gleiche Überprüfung ist für jede Ansicht durchzuführen:

  • die dynamischen Parameter der Ansicht ermitteln
  • sicherstellen, dass der Controller diese tatsächlich in die Attribute der an die Ansicht übermittelten Anfrage einfügt

Wir geben nun einfach den Ablauf der Bildschirme wieder, die ein Benutzer der Anwendung durchläuft. Der Leser ist aufgefordert, jedes Mal eine ähnliche Überlegung wie zuvor anzustellen:

Ausgehend von der Artikelliste kann der Benutzer einen Artikel auswählen:

Der Käufer kann hier den Artikel Nr. 3 kaufen. Machen wir einen Eingabefehler bei der Menge:

Der Fehler wurde gemeldet. Kaufen wir nun einige Artikel:

Der Kauf wurde registriert und die Artikelliste erneut angezeigt. Schauen wir uns den Warenkorb an:

Der Artikel befindet sich im Warenkorb. Entfernen wir ihn:

Der Artikel wurde aus dem Warenkorb entfernt und dieser erneut angezeigt. Hier ist er leer.

Kaufen wir 100 Artikel Nr. 3 und 2 Artikel Nr. 4:

Der Kauf des Artikels Nr. 3 war nicht möglich, da 100 Stück gekauft werden sollten, aber nur 30 auf Lager waren. Dieser Artikel blieb im Warenkorb:

Artikel Nr. 4 wurde hingegen gekauft, wie sein neuer Lagerbestand von 39 (40-1) zeigt:

3.6. Architektur MVC mit Struts

3.6.1. Allgemeine Architektur der Anwendung

Kommen wir noch einmal auf die Architektur MVC der Anwendung zurück:

In der vorherigen Version:

  • wurde die Steuerung durch ein Servlet übernommen
  • wurden die Ansichten durch JSP-Seiten bereitgestellt
  • Das Modell wurde durch drei .jar-Dateien bereitgestellt

In der Struts-Version:

  • wird der Controller durch ein Servlet bereitgestellt, das vom generischen Struts-Servlet [ActionServlet] abgeleitet ist
  • Die Ansichten werden – bis auf wenige Details – von denselben JSP-Seiten wie zuvor bereitgestellt
  • Das Modell wird durch dieselben drei Archive bereitgestellt

Wir werden sehen, dass die Umstellung der vorherigen Anwendung auf Struts aus den folgenden Aufgaben besteht:

  • Aktionen, die zuvor in bestimmten Methoden des Servlets/Controllers verarbeitet wurden, werden nun von Instanzen von Klassen verarbeitet, die von der Struts-Klasse „[Action]“ abgeleitet sind
  • Erstellen Sie die Konfigurationsdateien [web.xml] und [struts-config.xml]
  • Nehmen Sie einige Änderungen an den Seiten JSP vor

Zur Erinnerung: Die von STRUTS verwendete generische Architektur MVC:

M=modèle
die Fachklassen, die Datenzugriffsklassen und die Datenbank
V=vues
die Seiten JSP
C=contrôleur
das Servlet zur Verarbeitung von Client-Anfragen, die Objekte [Action] und die mit den Formularen verknüpften Beans [ActionForm].
  • Der Controller ist das Herzstück der Anwendung. Alle Client-Anfragen laufen über ihn. Es handelt sich um ein generisches Servlet, das von STRUTS bereitgestellt wird. In bestimmten Fällen kann es erforderlich sein, davon eine Unterklasse abzuleiten. In einfachen Fällen ist dies jedoch nicht notwendig. Dieses generische Servlet bezieht die benötigten Informationen aus einer Datei, die meist den Namen struts-config.xml trägt.
  • Enthält die Anfrage des Kunden Formularparameter, werden diese vom Controller in ein Bean-Objekt übertragen. Die im Laufe der Zeit so erstellten Bean-Objekte werden in der Sitzung oder in der Anfrage des Kunden gespeichert. Dieser Punkt ist konfigurierbar. Sie müssen nicht neu erstellt werden, wenn sie bereits vorhanden sind.
  • In der Konfigurationsdatei struts-config.xml werden jedem URL, das programmgesteuert verarbeitet werden soll (und somit keiner Ansicht JSP entspricht, die direkt angefordert werden könnte), bestimmte Informationen zugeordnet:
    • den Namen der Action-Klasse, die für die Bearbeitung der Anfrage zuständig ist. Auch hier kann das instanziierte Action-Objekt in der Sitzung oder der Anfrage gespeichert werden.
    • Wenn die angeforderte URL parametrisiert ist (z. B. beim Absenden eines Formulars an den Controller), wird der Name des Beans angegeben, das für die Speicherung der Formularinformationen zuständig ist.
  • Ausgestattet mit diesen Informationen aus seiner Konfigurationsdatei kann der Controller beim Empfang einer Anfrage nach URL durch einen Client feststellen, ob und welche Bean erstellt werden muss. Nach der Instanziierung kann die Bean überprüfen, ob die von ihr gespeicherten Daten, die aus dem Formular stammen, gültig sind oder nicht. Eine Methode der Bean namens „validate“ wird automatisch vom Controller aufgerufen. Die Bean wird vom Entwickler erstellt. Dieser fügt daher in die Methode „validate“ den Code ein, der die Gültigkeit der Formulardaten überprüft. Erweisen sich die Daten als ungültig, fährt der Controller nicht fort. Er übergibt die Kontrolle an eine Ansicht, deren Namen er in seiner Konfigurationsdatei findet. Der Austausch ist damit beendet. Es ist zu beachten, dass der Entwickler festlegen kann, dass die Gültigkeit des Formulars nicht überprüft wird. Dies erfolgt ebenfalls in der Datei „struts-config.xml“. In diesem Fall ruft der Controller die Methode „validate“ der Bean nicht auf.
  • Sind die Daten der Bean korrekt, findet keine Überprüfung statt oder ist keine Bean vorhanden, übergibt der Controller die Kontrolle an das Action-Objekt, das mit der Datei URL verknüpft ist. Dies geschieht, indem er die Ausführung der Methode `execute` dieses Objekts anfordert und dabei die Referenz der Bean übergibt, die er gegebenenfalls erstellt hat. An dieser Stelle führt der Entwickler die erforderlichen Schritte aus: Er muss gegebenenfalls auf Geschäftsklassen oder Datenzugriffsklassen zurückgreifen. Am Ende der Verarbeitung übergibt das Action-Objekt dem Controller den Namen der Ansicht, die er als Antwort an den Client senden soll.
  • In seiner Konfigurationsdatei findet der Controller das Objekt URL, das dem Namen der Ansicht zugeordnet ist, die er anzeigen soll. Er sendet diese dann. Der Austausch mit dem Client ist beendet.

In unserer Anwendung werden wir keine Objekte vom Typ [Bean] als Pufferobjekte zwischen dem Client und den Klassen vom Typ [Action] verwenden. Das Objekt [Action] wird die Parameter der Client-Anfrage direkt aus dem Objekt [HttpServletRequest] abrufen, das es erhält. Dies erleichtert die Portierung unserer ursprünglichen Anwendung. Die endgültige Architektur unserer Anwendung sieht daher wie folgt aus:

M=modèle
die Fachklassen, die Datenzugriffsklassen und die Datenbank
V=vues
die Seiten JSP
C=contrôleur
das Servlet zur Verarbeitung von Client-Anfragen, die Objekte [Action]

3.6.2. Das Modell

Es wurde bereits zuvor vorgestellt. Es besteht aus den Java-Archiven [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception].

3.6.3. Konfiguration der Anwendung

3.6.3.1. Allgemeine Architektur

Die allgemeine Architektur des Eclipse-Projekts sieht wie folgt aus:

Image

3.6.3.2. Konfiguration des Datenzugriffs

Da sich die Datenzugriffsschnittstelle nicht ändert, sind die zugehörigen Konfigurationsdateien dieselben wie in der vorherigen Version. Sie sind in [WEB-INF/src] definiert:

Image

Auf dem obigen Screenshot stammen die Dateien [articles.xml, spring-config-sqlmap-firebird.xml, sqlmap-config-firebird.xml, log4j.properties] aus der vorherigen Version.

3.6.3.3. Das Archivverzeichnis

In [WEB-INF/lib] befinden sich dieselben Archive wie in der vorherigen Version sowie das für Struts erforderliche Archiv:

Image

3.6.3.4. Konfiguration der Anwendung

Die Anwendung wird mithilfe von zwei Dateien konfiguriert: [web.xml, struts-config.xml] im Ordner [WEB-INF]:

Image

Die Datei [web.xml] sieht wie folgt aus:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
        <servlet-name>strutswebarticles</servlet-name>
        <servlet-class>istia.st.articles.web.struts.MainServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <init-param>
            <param-name>springConfigFileName</param-name>
            <param-value>spring-config-sqlmap-firebird.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>strutswebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
        <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>        
</web-app>

Was steht in dieser Datei?

  • Die Startseite der Anwendung ist [vues/index.jsp] (welcome-file)
  • URL-Anfragen der Form *.do werden an das Servlet [strutswebarticles] weitergeleitet (Servlet-Mapping)
  • Das Servlet [strutswebarticles] ist eine Instanz der Klasse [ istia.st.articles.web.struts.MainServlet] (Servlet-Name, Servlet-Klasse)
  • Dieses Servlet akzeptiert zwei Initialisierungsparameter
    • den Namen der Struts-Konfigurationsdatei (config)
    • den Namen der Spring-Konfigurationsdatei (springConfigFileName)

Die Datei [struts-config.xml] sieht wie folgt aus:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
    <action-mappings>
        <action path="/main" type="istia.st.articles.web.struts.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/liste.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
        <action path="/liste" type="istia.st.articles.web.struts.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/liste.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
        <action path="/infos" type="istia.st.articles.web.struts.InfosArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
        <action 
            path="/achat" type="istia.st.articles.web.struts.AchatArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherListeArticles" path="/main.do"/>
        </action>
        <action 
            path="/panier" type="istia.st.articles.web.struts.VoirPanierAction">
            <forward name="afficherPanier" path="/vues/panier.jsp"/>
            <forward name="afficherPanierVide" path="/vues/paniervide.jsp"/>
        </action>
        <action 
            path="/retirerachat" type="istia.st.articles.web.struts.RetirerAchatAction">
            <forward name="afficherPanier" path="/panier.do"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
        <action 
            path="/validerpanier" type="istia.st.articles.web.struts.ValiderPanierAction">
            <forward name="afficherListeArticles" path="/main.do"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
    </action-mappings>
    <message-resources parameter="ApplicationResources" null="false" />
</struts-config>

Was besagt diese Konfigurationsdatei?

  • Dass unser Controller die folgenden URLs verarbeitet:
main.do
um die Liste der Artikel anzuzeigen
liste.do
um die Artikelliste anzuzeigen
infos.do
um Informationen zu einem bestimmten Artikel anzuzeigen
achat.do
um einen bestimmten Artikel zu kaufen
panier.do
um den Warenkorb anzuzeigen
retirerachat.do
um einen Artikel aus dem Warenkorb zu entfernen
validerpanier.do
zum Bestätigen eines Warenkorbs
  • Die oben genannten Aktionen entsprechen eins zu eins den Aktionen, die vom Servlet in der vorherigen Version verarbeitet wurden. Für jede davon werden folgende Informationen angegeben:
  • den Namen der Klasse, die für die Verarbeitung dieser Aktion zuständig ist
  • die möglichen Antworten (= Ansichten) nach der Verarbeitung der Aktion. Der Controller wählt jeweils nur eine davon aus.
  • den Namen einer Meldungsdatei für die Anwendung (message-resources). Hier existiert die Datei zwar, ist aber leer. Sie wird nicht verwendet. Sie muss im Verzeichnis [ClassPath] der Anwendung abgelegt werden. Hier wird sie im Verzeichnis [WEB-INF/classes] abgelegt. Unter Eclipse erhält man dieses Ergebnis, indem man sie im Verzeichnis [WEB-INF/src] ablegt:

Image

3.6.4. Die Ansichten JSP

Die verwendeten Ansichten JSP stammen ebenfalls aus der vorherigen Version. Es wurden nur sehr wenige Änderungen vorgenommen: Es handelt sich um die URLs der Form [?action=XX?id=YY& ...], die nun zu [/XX.do?id=YY&....] werden. Wir greifen hier bereits gegebene Erläuterungen wieder auf, um dem Benutzer das Zurückblättern zu ersparen. Es ist wichtig zu verstehen, dass die vom Controller an die Ansicht übermittelten Informationen in beiden Versionen exakt dieselben sind. In diesem Punkt wurde nichts geändert.

3.6.4.1. entete.jsp

Um den verschiedenen Ansichten eine gewisse Einheitlichkeit zu verleihen, werden diese denselben Kopfbereich verwenden, in dem der Name der Anwendung zusammen mit dem Menü angezeigt wird:

Das Menü ist dynamisch und wird vom Controller festgelegt. Dieser fügt in die an die Seite JSP übermittelte Anfrage ein Schlüsselattribut „actions“ ein, dessen zugehöriger Wert ein Array vom Typ Hastable[] ist. Jedes Element dieses Arrays ist ein Dictionary, das dazu dient, eine Option des Kopfmenüs zu generieren. Jedes Dictionary hat zwei Schlüssel:

  • href: die mit der Menüoption verknüpfte URL
  • lien: der Text der Menüoption

Die anderen Ansichten der Anwendung verwenden die durch [entete.jsp] definierte Kopfzeile mithilfe des folgenden Tags JSP:

<jsp:include page="entete.jsp"/>

Bei der Ausführung bewirkt dieses Tag, dass der Code der Seite JSP, die es enthält, den Code der Seite [entete.jsp] einbindet. Da es sich bei der URL der Seite um eine relative URL handelt (ohne „/“), wird die Seite [entete.jsp] im selben Ordner gesucht wie die Seite, die das Tag <jsp:include> enthält.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<html>
    <head>
        <title>webarticles</title>
    </head>
    <body>
        <table>
            <tr>
                <td><h2>Magasin virtuel</h2></td>
                <c:forEach items="${actions}" var="action">
                    <td>|</td>
                    <td><a href="<c:out value="${action.href}"/>"><c:out value="${action.lien}"/></a></td>
                </c:forEach>
            </tr>
        </table>
        <hr>

Anmerkungen: Keine Änderungen gegenüber der vorherigen Version

3.6.4.2. liste.jsp

Diese Ansicht zeigt die Liste der zum Verkauf stehenden Artikel an:

Sie wird nach einer Abfrage mit /main.do oder /validerpanier.do angezeigt. Die Elemente der Controller-Abfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
listarticles
ArrayList Objekte vom Typ [Article]
message
String-Objekt – am Seitenende anzuzeigende Meldung

Jeder Link [Infos] im Array HTML der Artikel hat eine URL der Form [/infos.do?id=ID], wobei ID das ID-Feld des angezeigten Artikels ist.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Liste des articles</h2>
        <table border="1">
            <tr>
                <th>NOM</th><th>PRIX</th>
            </tr>
            <c:forEach var="article" items="${listarticles}">
                <tr>
                    <td><c:out value="${article.nom}"/></td>
                    <td><c:out value="${article.prix}"/></td>
                    <td><a href="<c:out value="infos.do?id=${article.id}"/>">Infos</a></td>
                </tr>
            </c:forEach>
        </table>
        <p>
        <c:out value="${message}"/>
    </body>
</html>

Anmerkungen: eine Änderung (oben hervorgehoben)

3.6.4.3. infos.jsp

Diese Ansicht zeigt Informationen zu einem Artikel an und ermöglicht auch dessen Kauf:

Image

Sie wird nach einer Anfrage /infos.do?id=ID oder einer Anfrage /achat.do?id=ID angezeigt, wenn die gekaufte Menge falsch ist. Die Elemente der Anfrage des Controllers lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
article
Objekt vom Typ [Article] – anzuzeigender Artikel
msg
String-Objekt – Meldung, die bei einem Fehler bezüglich der Menge angezeigt werden soll
qte
Objekt String – Wert, der im Eingabefeld [Qte] angezeigt werden soll

Die Felder [msg] und [qte] werden bei einem Eingabefehler bei der Menge verwendet:

Image

Diese Seite enthält ein Formular, das über die Schaltfläche [Acheter] übermittelt wird. Die Ziel-URL von POST lautet [/achat.do?id=ID], wobei ID die ID des gekauften Artikels ist.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Article d'id [<c:out value="${article.id}"/>]</h2>
        <table border="1">
            <tr>
                <th>NOM</th><th>PRIX</th><th>STOCK ACTUEL</th><th>STOCK MINIMUM</th>
            </tr>
                <tr>
                    <td><c:out value="${article.nom}"/></td>
                    <td><c:out value="${article.prix}"/></td>
                    <td><c:out value="${article.stockActuel}"/></td>
                    <td><c:out value="${article.stockMinimum}"/></td>
                </tr>
        </table>
        <p>
        <form method="post" action="achat.do?id=<c:out value="${article.id}"/>"/>
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qte <input type="text" name="qte" size="3" value="<c:out value="${qte}"/>"></td>
                    <td><c:out value="${msg}"/></td>
                </tr>
            </table>
        </form>
    </body>
</html>

Anmerkungen: eine Änderung (oben hervorgehoben)

3.6.4.4. panier.jsp

Diese Ansicht zeigt den Inhalt des Warenkorbs an:

Image

Sie wird nach einer Anfrage an /panier.do oder /retirerachat.do?id=ID angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
panier
Objekt vom Typ [Panier] – der anzuzeigende Warenkorb

Jeder Link [Retirer] im Array HTML der Warenkorb-Artikel hat eine URL der Form [retirerachat.do?id=ID], wobei ID das Feld [id] desArtikels, den man aus dem Warenkorb entfernen möchte.

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Contenu de votre panier</h2>
        <table border="1">
            <tr>
                <td>Article</td><td>Qte</td><td>Pu</td><td>Total</td>
            </tr>
            <c:forEach var="achat" items="${panier.achats}">
                <tr>
                    <td><c:out value="${achat.article.nom}"/></td>
                    <td><c:out value="${achat.qte}"/></td>
                    <td><c:out value="${achat.article.prix}"/></td>
                    <td><c:out value="${achat.total}"/></td>
                    <td><a href="<c:out value="retirerachat.do?id=${achat.article.id}"/>">Retirer</a></td>
                </tr>
            </c:forEach>
        </table>
        <p>
        Total de la commande : <c:out value="${panier.total}"/> euros
    </body>
</html>

Anmerkungen: Eine Änderung (oben hervorgehoben)

3.6.4.5. paniervide.jsp

Diese Ansicht zeigt die Information an, dass der Warenkorb leer ist:

Image

Sie wird nach einer Anfrage /panier.do oder /retirerachat.do?id=ID angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen

Code:

1
2
3
4
5
6
7
8
9
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Contenu de votre panier</h2>
        <p>
        Votre panier est vide.
    </body>
</html>

Kommentare: keine Änderungen.

3.6.4.6. erreurs.jsp

Diese Ansicht wird bei Fehlern angezeigt:

Image

Sie wird nach jeder Anfrage angezeigt, die zu einem Fehler führt, mit Ausnahme der Kaufaktion mit einer falschen Menge, die von der Ansicht [INFOS] verarbeitet wird. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable[]“ – das Array der Menüoptionen
erreurs
ArrayList – String-Objekte, die die anzuzeigenden Fehlermeldungen darstellen

Code:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<jsp:include page="entete.jsp"/>
        <h2>Les erreurs suivantes se sont produites</h2>
        <ul>
            <c:forEach var="erreur" items="${erreurs}">
                <li><c:out value="${erreur}"/></li>
            </c:forEach>
        </ul>
    </body>
</html>

Kommentare: keine Änderungen.

3.6.4.7. index.jsp

Diese Seite ist in der Anwendungsdatei [web.xml] als Startseite der Anwendung definiert:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
....
    </servlet>
    <servlet-mapping>
....
    </servlet-mapping>
        <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>    
</web-app>

Die Ansicht [index.jsp] leitet den Client lediglich zum Einstiegspunkt der Anwendung weiter:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/main.do"/>

Anmerkungen: eine Änderung (oben hervorgehoben)

3.6.5. Der Struts-Controller

Struts verfügt über einen generischen Controller namens [ActionServlet]. Es ist bekannt, dass ein Servlet über eine Methode namens [init] verfügt, mit der die Anwendung beim Start initialisiert werden kann. Wenn wir den generischen Struts-Controller [ActionServlet] verwenden, haben wir keinen Zugriff auf dessen Methode [init]. Hier müssen wir beim Start der Anwendung einige Aufgaben erledigen, im Wesentlichen ein Objekt für den Zugriff auf das Modell instanziieren. Daher benötigen wir eine Methode [init]. Wir leiten also die Klasse [Actionservlet] in der folgenden Klasse [MainServlet] ab:

package istia.st.articles.web.struts;

import istia.st.articles.domain.IArticlesDomain;

import java.util.ArrayList;
import java.util.Hashtable;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

import org.apache.struts.action.ActionServlet;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST - ISTIA
 *  
 */
public class MainServlet extends ActionServlet {
   // Private Felder
  private ArrayList erreurs = new ArrayList();
  private IArticlesDomain articlesDomain = null;
  private final String SPRING_CONFIG_FILENAME = "springConfigFileName";
  private final String[] parameters = { SPRING_CONFIG_FILENAME };
  private ServletConfig config;
  private final String ACTION_LISTE = "liste.do";
  private final String ACTION_PANIER = "panier.do";
  private final String ACTION_ACHAT = "achat.do";
  private final String ACTION_INFOS = "infos.do";
  private final String ACTION_RETIRER_ACHAT = "retirerachat.do";
  private final String ACTION_VALIDATION_PANIER = "validerpanier.do";
  private String urlActionListe;
  private final String lienActionListe = "Liste des articles";
  private String urlActionPanier;
  private final String lienActionPanier = "Voir le panier";
  private String urlActionValidationPanier;
  private final String lienActionValidationPanier = "Valider le panier";
  private Hashtable hActionListe = new Hashtable(2);
  private Hashtable hActionPanier = new Hashtable(2);
  private Hashtable hActionValidationPanier = new Hashtable(2);

     // Getter – Setter
  public IArticlesDomain getArticlesDomain() {
    return articlesDomain;
  }
  public void setArticlesDomain(IArticlesDomain articlesDomain) {
    this.articlesDomain = articlesDomain;
  }

  public ArrayList getErreurs() {
    return erreurs;
  }
  public void setErreurs(ArrayList erreurs) {
    this.erreurs = erreurs;
  }

  public Hashtable getHActionListe() {
    return hActionListe;
  }
  public void setHActionListe(Hashtable actionListe) {
    hActionListe = actionListe;
  }

  public Hashtable getHActionPanier() {
    return hActionPanier;
  }
  public void setHActionPanier(Hashtable actionPanier) {
    hActionPanier = actionPanier;
  }

  public Hashtable getHActionValidationPanier() {
    return hActionValidationPanier;
  }
  public void setHActionValidationPanier(Hashtable actionValidationPanier) {
    hActionValidationPanier = actionValidationPanier;
  }

  public void init() throws ServletException{

     // Initialisierung der übergeordneten Klasse
    super.init();
     // die Initialisierungsparameter des Servlets werden abgerufen
    config = getServletConfig();
    String param = null;
    for (int i = 0; i < parameters.length; i++) {
      param = config.getInitParameter(parameters[i]);
      if (param == null) {
         // Der Fehler wird gespeichert
        erreurs.add("Paramètre [" + parameters[i]
            + "] absent dans le fichier [web.xml]");
      }
    }
     // Gibt es Fehler?
    if (erreurs.size() != 0) {
      return;
    }
     // Ein Objekt IArticlesDomain für den Zugriff auf die Geschäftslogik wird erstellt
    try {
      articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
          new ClassPathResource((String) config
              .getInitParameter(SPRING_CONFIG_FILENAME))))
          .getBean("articlesDomain");
    } catch (Exception ex) {
       // Der Fehler wird gespeichert
      erreurs.add("Erreur de configuration de l'accès aux données : "
          + ex.toString());
      return;
    }
     // bestimmte URLs der Anwendung werden gespeichert
    hActionListe.put("href", ACTION_LISTE);
    hActionListe.put("lien", lienActionListe);
    hActionPanier.put("href", ACTION_PANIER);
    hActionPanier.put("lien", lienActionPanier);
    hActionValidationPanier.put("href", ACTION_VALIDATION_PANIER);
    hActionValidationPanier.put("lien", lienActionValidationPanier);

     // Fertig
    return;
  }
}

Anmerkungen:

  • Der Nutzen der Klasse liegt in ihrer Methode [init] und ihren privaten Feldern
  • Die Methode [init] führt dieselbe Aufgabe aus wie die Methode [init] des Controllers aus der vorherigen Version:
    • Sie überprüft das Vorhandensein bestimmter Konfigurationsparameter
    • sie instanziiert mithilfe von Spring einen Dienst für den Zugriff auf die Anwendungsdomäne
    • sie legt eine Fehlerliste an, um eventuelle Initialisierungsfehler anzuzeigen
  • Bevor die Arbeit beginnt, ruft die Methode [init] die Methode [init] der übergeordneten Klasse [ActionServlet] auf. Diese ist dafür zuständig, die Struts-Konfigurationsdatei [struts-config.xml] auszuwerten.
  • Es sind eine Reihe von privaten Feldern mit ihren Zugriffsmethoden definiert:
    • Fehler: die Liste der von [init] erkannten Fehler
    • [hActionListe, hActionPanier, hActionValidationPanier]: Wörterbücher. Jedes davon enthält die notwendigen Informationen zur Anzeige einer Option des Hauptmenüs, das von der Ansicht [entete.jsp] angezeigt wird
    • acticlesDomain: der Dienst für den Zugriff auf das Anwendungsmodell
  • Der Controller einer Struts-Anwendung ist für die Klassen [Action] zugänglich, die für die Verarbeitung der verschiedenen möglichen Aktionen zuständig sind. Diese Klassen haben Zugriff auf die zuvor genannten privaten Felder, da diese über öffentliche Accessoren verfügen.

Die Instanziierung dieses Controllers erfolgt über die Datei [web.xml]:

<web-app>
    <servlet>
        <servlet-name>strutswebarticles</servlet-name>
        <servlet-class>istia.st.articles.web.struts.MainServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <init-param>
            <param-name>springConfigFileName</param-name>
            <param-value>spring-config-sqlmap-firebird.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>strutswebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

Jede URL, die auf .do endet, wird von einer Instanz der Klasse [istia.st.articles.web.struts.MainServlet] verarbeitet

3.6.6. Die Aktionen der Struts-Anwendung

3.6.6.1. Einführung

Jede Struts-Aktion wird zum Gegenstand einer Klasse. In der vorherigen Version war jede Aktion Gegenstand einer Methode im Controller der Anwendung. Das Schreiben der Klasse [Action] besteht meist darin:

  • dem Kopieren und Einfügen der Methode, die in der vorherigen Version verwendet wurde
  • den Code an die Struts-Konventionen anzupassen

3.6.6.2. main.do, liste.do

Diese beiden Aktionen sind identisch und werden in [struts-config.xml] wie folgt definiert:

1
2
3
4
5
6
7
8
        <action path="/main" type="istia.st.articles.web.struts.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/liste.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
        <action path="/liste" type="istia.st.articles.web.struts.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/liste.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Wenn eine dieser Aktionen ([main.do, liste.do]) in einem Browser ausgeführt wird, erhält man folgendes Ergebnis:

Image

Der Code der Klasse [ListeArticlesAction] lautet wie folgt:

package istia.st.articles.web.struts;

import istia.st.articles.domain.IArticlesDomain;
import istia.st.articles.exception.UncheckedAccessArticlesException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
 * @author ST - ISTIA
 *  
 */
public class ListeArticlesAction extends Action {

  /**
   * affichage de la liste des articles - s'appuie sur la couche [domain]
   * 
   * @param mapping :
   *          configuration de l'action dans struts-config.xml
   * @param form :
   *          le formulaire passé à l'action - ici aucun
   * @param request :
   *          la requête HTTP du client
   * @param response :
   *          la réponse HTTP au client
   */
  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

     // das Kontroll-Servlet
    MainServlet mainServlet = (MainServlet) this.getServlet();

     // Initialisierungsfehler?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }

     // das Objekt für den Zugriff auf die Domäne
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();

     // die Fehlerliste
    ArrayList erreurs = new ArrayList();
     // Die Artikelliste wird angefordert
    List articles = null;
    try {
      articles = articlesDomain.getAllArticles();
    } catch (UncheckedAccessArticlesException ex) {
       // Der Fehler wird gespeichert
      erreurs.add("Erreur lors de l'obtention de tous les articles : "
          + ex.toString());
    }
     // Gibt es Fehler?
    if (erreurs.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // Die Artikelliste wird angezeigt
    request.setAttribute("listarticles", articles);
    request.setAttribute("message", "");
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionPanier() });
    return mapping.findForward("afficherListeArticles");
  }
}

Anmerkungen:

  • Das Schreiben des Codes für eine Klasse [Action] besteht im Wesentlichen darin, den Code ihrer Methode [execute] zu schreiben
  • In der Instanz des Controllers wurden bestimmte Informationen gespeichert. Eine Referenz darauf erhält man wie folgt:
     // das Kontroll-Servlet
    MainServlet mainServlet = (MainServlet) this.getServlet();
  • Man ruft die Liste der vom Controller gespeicherten Initialisierungsfehler ab. Ist diese Liste nicht leer, wird die Ansicht [ERREURS] an den Client gesendet:
1
2
3
4
5
6
7
8
     // Initialisierungsfehler?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }

Die Ansicht, die tatsächlich an den Client gesendet wird, wird uns von [struts-config.xml] bereitgestellt:

        <action path="/main" type="istia.st.articles.web.struts.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/liste.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Es handelt sich um die Ansicht [/vues/erreurs.jsp]. Der Leser wird gebeten, zu überprüfen, was von dieser Ansicht erwartet wird. Diese Informationen werden hier von der Aktion im Objekt [request] als Attribute bereitgestellt.

  • Ebenfalls mithilfe des Controllers kann die Aktion das Objekt für den Zugriff auf das Anwendungsmodell (Domain-Schicht) abrufen:
     // das Objekt für den Zugriff auf die Domäne
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
  • Anschließend kann die Liste der Artikel abgefragt werden:
     // die Fehlerliste
    ArrayList erreurs = new ArrayList();
     // Die Artikelliste wird angefordert
    List articles = null;
    try {
      articles = articlesDomain.getAllArticles();
    } catch (UncheckedAccessArticlesException ex) {
       // Der Fehler wird gespeichert
      erreurs.add("Erreur lors de l'obtention de tous les articles : "
          + ex.toString());
    }
  • Sollten Fehler auftreten, wird die Ansicht [ERREURS] gesendet:
1
2
3
4
5
6
7
8
     // Gibt es Fehler?
    if (erreurs.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
  • Andernfalls wird die Ansicht [LISTE] gesendet:
1
2
3
4
5
6
     // Die Artikelliste wird angezeigt
    request.setAttribute("listarticles", articles);
    request.setAttribute("message", "");
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionPanier() });
    return mapping.findForward("afficherListeArticles");

Die Ansicht, die tatsächlich an den Kunden gesendet wird, wird uns von [struts-config.xml] bereitgestellt:

        <action path="/main" type="istia.st.articles.web.struts.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/liste.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Es handelt sich um die Ansicht [/vues/liste.jsp]. Der Leser wird gebeten, zu überprüfen, was von dieser Ansicht erwartet wird. Diese Informationen werden hier von der Aktion im Objekt [request] als Attribute bereitgestellt.

3.6.6.3. infos.do

Diese Aktion dient dazu, Informationen zu einem der in der Ansicht [LISTE] angezeigten Artikel bereitzustellen:

Diese Aktion ist in [struts-config.xml] wie folgt konfiguriert:

        <action path="/infos" type="istia.st.articles.web.struts.InfosArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Der Code der Klasse [InfosArticleAction] lautet wie folgt:

package istia.st.articles.web.struts;

import istia.st.articles.dao.Article;
import istia.st.articles.domain.IArticlesDomain;
import istia.st.articles.exception.UncheckedAccessArticlesException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
 * @author ST-ISTIA
 *  
 */
public class InfosArticleAction extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

     // das Kontroll-Servlet
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // Initialisierungsfehler?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // das Objekt für den Zugriff auf die Domäne
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
     // die Fehlerliste
    ArrayList erreurs = new ArrayList();
     // Die angeforderte ID wird abgerufen
    String strId = request.getParameter("id");
     // irgendetwas?
    if (strId == null) {
       // nicht normal
      erreurs.add("action incorrecte([infos,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // strId wird in eine Ganzzahl umgewandelt
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // nicht normal
      erreurs.add("action incorrecte([infos,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // Der Schlüssel-ID-Eintrag wird angefordert
    Article article = null;
    try {
      article = articlesDomain.getArticleById(id);
    } catch (UncheckedAccessArticlesException ex) {
       // nicht normal
      erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
    if (article == null) {
       // nicht normal
      erreurs.add("Article de clé [" + id + "] inexistant");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // Der Artikel wird in die Sitzung aufgenommen
    request.getSession().setAttribute("article", article);
     // Die Informationsseite wird angezeigt
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionListe() });
    return mapping.findForward("afficherInfosArticle");
  }
}

Anmerkungen:

  • Der Anfang der Methode [execute] ist identisch mit dem zuvor untersuchten. Dies gilt auch für die anderen Aktionen.
  • Die Methode ruft den Parameter [id] ab, der sich normalerweise in der URL befinden sollte. Diese muss tatsächlich die Form [/infos.do?id=X] haben. Es werden verschiedene Prüfungen durchgeführt, um das Vorhandensein und die Gültigkeit des Parameters [id] zu überprüfen. Im Falle eines Problems wird die Ansicht [ERREURS] gesendet.
  • Ist [id] gültig, wird der entsprechende Artikel von der Ebene [domain] angefordert. Löst diese eine Ausnahme aus oder wird der Artikel nicht gefunden, wird wiederum die Ansicht [ERREURS] gesendet.
  • Wenn alles gut geht, wird der abgerufene Artikel in die Sitzung gespeichert. Dies ist ein strittiger Punkt. Hier geht man davon aus, dass der Kunde diesen Artikel möglicherweise kaufen wird. Tut er dies, wird der Artikel aus der Sitzung abgerufen, anstatt ihn erneut bei der Ebene [domain] anzufordern.
  • Schließlich wird die Ansicht [INFOS] angezeigt. Die Ansicht, die tatsächlich an den Kunden gesendet wird, wird uns von [struts-config.xml] bereitgestellt:
        <action path="/infos" type="istia.st.articles.web.struts.InfosArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Es handelt sich um die Ansicht [/vues/infos.jsp]. Der Leser wird gebeten, zu überprüfen, was von dieser Ansicht erwartet wird. Diese Informationen werden hier von der Aktion im Objekt [request] als Attribute bereitgestellt.

3.6.6.4. achat.do

Diese Aktion dient dazu, den Artikel zu kaufen, der in der vorherigen Ansicht [INFOS] angezeigt wird:

Sobald der Artikel gekauft wurde, wird die Ansicht [LISTE] erneut angezeigt (rechte Ansicht). Betrachtet man den Code HTML der obigen linken Ansicht, so stellt man fest, dass das Tag <form> wie folgt definiert ist:

1
2
3
4
5
6
7
8
9
        <form method="post" action="achat.do?id=3"/>
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qte <input type="text" name="qte" size="3" value=""></td>
                    <td></td>
                </tr>
            </table>
        </form>

Man sieht also, dass das Formular mit der Aktion [achat.do] an den Controller gesendet wird.

Diese Aktion ist in [struts-config.xml] wie folgt konfiguriert:

        <action 
            path="/achat" type="istia.st.articles.web.struts.AchatArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherListeArticles" path="/main.do"/>
        </action>

Der Code der Klasse [AchatArticleAction] lautet wie folgt:

package istia.st.articles.web.struts;

import istia.st.articles.dao.Article;
import istia.st.articles.domain.Achat;
import istia.st.articles.domain.Panier;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
 * @author ST
 *  
 */
public class AchatArticleAction extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

     // das Kontroll-Servlet
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // Initialisierungsfehler?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // die Liste der Fehler bei dieser Aktion
    ArrayList erreurs = new ArrayList();
     // die gekaufte Menge wird abgerufen
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // Falsche Menge
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherInfosArticle");
    }
     // Die Kundensitzung wird abgerufen
    HttpSession session = request.getSession();
     // Abruf des in der Sitzung gespeicherten Artikels
    Article article = (Article) session.getAttribute("article");
     // Ist die Sitzung abgelaufen?
    if(article==null){
       // Fehlerseite anzeigen
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
     // Neuer Kauf wird angelegt
    Achat achat = new Achat(article, qté);
     // Der Einkauf wird in den Warenkorb des Kunden gelegt
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
     // Zurück zur Artikelliste
    return mapping.findForward("afficherListeArticles");
  }
}

Anmerkungen:

  • Der Anfang der Methode [execute] ist identisch mit den zuvor untersuchten Methoden.
  • Zur Erinnerung: Das an den Controller gesendete Formular hat folgende Form:
1
2
3
4
5
6
7
8
9
        <form method="post" action="achat.do?id=3"/>
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qte <input type="text" name="qte" size="3" value=""></td>
                    <td></td>
                </tr>
            </table>
        </form>
  • Die Anfrage enthält zwei Parameter: [id]: Nummer des gekauften Artikels, [qte]: gekaufte Menge.
  • Das Vorhandensein und die Gültigkeit des Parameters [qte] werden geprüft. Wird dieser Parameter als fehlerhaft erkannt, wird die Ansicht [INFOS] zusammen mit einer Fehlermeldung an den Benutzer zurückgegeben:
     // die Fehlerliste zu dieser Aktion
    ArrayList erreurs = new ArrayList();
     // Die gekaufte Menge wird abgerufen
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // falsche Menge
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherInfosArticle");
    }
  • Der gekaufte Artikel wird aus der Sitzung abgerufen. Diese ist möglicherweise abgelaufen. In diesem Fall wird die Ansicht [ERREURS] gesendet:
     // Die Sitzung des Kunden wird abgerufen
    HttpSession session = request.getSession();
     // Abruf des in die Sitzung aufgenommenen Artikels
    Article article = (Article) session.getAttribute("article");
     // Ist die Sitzung abgelaufen?
    if(article==null){
       // Die Fehlerseite wird angezeigt
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
  • Wenn die Sitzung nicht abgelaufen ist, wird der Artikel in den Warenkorb gelegt, der ebenfalls aus der Sitzung abgerufen wird:
     // Die Sitzung des Kunden wird abgerufen
    HttpSession session = request.getSession();
     // Der in der Sitzung gespeicherte Artikel wird abgerufen
    Article article = (Article) session.getAttribute("article");
     // Sitzung abgelaufen?
    if(article==null){
       // Die Fehlerseite wird angezeigt
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
     // Der neue Kauf wird angelegt
    Achat achat = new Achat(article, qté);
     // Der Kauf wird in den Warenkorb des Kunden gelegt
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
  • Schließlich wird die Ansicht [LISTE] gesendet:
     // Rückkehr zur Artikelliste
    return mapping.findForward("afficherListeArticles");
  • Die Ansicht, die tatsächlich an den Kunden gesendet wird, wird uns von [struts-config.xml] bereitgestellt:
        <action 
            path="/achat" type="istia.st.articles.web.struts.AchatArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherListeArticles" path="/main.do"/>
        </action>

Es handelt sich um die Ansicht [/main.do]. Diese Ansicht ist keine Ansicht, sondern eine Aktion. Die zuvor beschriebene Aktion [/main.do] wird daher ausgeführt und zeigt die Liste der Artikel an.

3.6.6.5. panier.do

Diese Aktion dient dazu, alle Einkäufe des Kunden anzuzeigen. Sie ist über die Menüoption [Voir le panier] verfügbar:

Der Code HTML, der mit dem Link [Voir le panier] verknüpft ist, lautet wie folgt:

<a href="panier.do">Voir le panier</a>

Die Aktion [panier.do] ist in [struts-config.xml] wie folgt konfiguriert:

        <action 
            path="/panier" type="istia.st.articles.web.struts.VoirPanierAction">
            <forward name="afficherPanier" path="/vues/panier.jsp"/>
            <forward name="afficherPanierVide" path="/vues/paniervide.jsp"/>
        </action>

Der Code der Klasse [VoirPanierAction] lautet wie folgt:

package istia.st.articles.web.struts;

import istia.st.articles.domain.Panier;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
 * @author ST-ISTIA
 *  
 */
public class VoirPanierAction extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

     // das Kontroll-Servlet
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // Initialisierungsfehler?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // Der Warenkorb wird angezeigt
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null || panier.getAchats().size() == 0) {
       // Warenkorb leer
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherPanierVide");
    } else {
       // Der Warenkorb enthält etwas
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(), mainServlet.getHActionValidationPanier() });
      return mapping.findForward("afficherPanier");
    }
  }
}

Anmerkungen:

  • Der Anfang der Methode [execute] ist identisch mit den zuvor untersuchten Methoden.
  • Der Warenkorb wird aus der Sitzung abgerufen, in der er sich normalerweise befindet. Die Sitzung könnte abgelaufen sein, sodass kein Warenkorb vorhanden ist. Dieser Fall wird nicht als Fehler behandelt, sondern es wird einfach davon ausgegangen, dass der Warenkorb leer ist.
  • Ist der Warenkorb leer, wird die Ansicht [PANIERVIDE] angezeigt
  • Andernfalls wird die Ansicht [PANIER] angezeigt

Die tatsächlich an den Client gesendeten Ansichten werden durch die Aktion definiert:

        <action 
            path="/panier" type="istia.st.articles.web.struts.VoirPanierAction">
            <forward name="afficherPanier" path="/vues/panier.jsp"/>
            <forward name="afficherPanierVide" path="/vues/paniervide.jsp"/>
        </action>

3.6.6.6. retirerachat.do

Diese Aktion dient dazu, einen Artikel aus dem Warenkorb zu entfernen:

Nach der Aktion [retirerachat.do] wird der Warenkorb erneut angezeigt (Ansicht rechts oben). Betrachtet man den Code HTML des Links [Valider le panier] aus der Ansicht links oben, ergibt sich Folgendes:

<a href="retirerachat.do?id=3">Retirer</a>

Die Aktion [retirerachat.do] erhält also als Parameter die ID des Artikels, der aus dem Warenkorb entfernt werden soll. Diese Aktion ist in [struts-config.xml] wie folgt konfiguriert:

        <action 
            path="/retirerachat" type="istia.st.articles.web.struts.RetirerAchatAction">
            <forward name="afficherPanier" path="/panier.do"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Der Code der Klasse [RetirerAchatAction] lautet wie folgt:

package istia.st.articles.web.struts;

import istia.st.articles.domain.Panier;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
 * @author ST-ISTIA
 *  
 */
public class RetirerAchatAction extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

     // das Kontroll-Servlet
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // Initialisierungsfehler?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // die Liste der Fehler bei dieser Aktion
    ArrayList erreurs = new ArrayList();
     // Warenkorb abrufen
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // Sitzung abgelaufen
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherPanierVide");
    }
     // Die ID des zu entfernenden Artikels wird abgerufen
    String strId = request.getParameter("id");
     // Etwas?
    if (strId == null) {
       // nicht normal
      erreurs.add("action incorrecte([retirerachat,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // strId wird in eine Ganzzahl umgewandelt
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // nicht normal
      erreurs.add("action incorrecte([retirerachat,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // Den Kauf stornieren
    panier.enlever(id);
     // Der Warenkorb wird erneut angezeigt
    return mapping.findForward("afficherPanier");
  }
}

Anmerkungen:

  • Der Anfang der Methode [execute] ist identisch mit den zuvor untersuchten Methoden.
  • Der nachfolgende Code dient dazu, das Vorhandensein und die Gültigkeit des Parameters [id] zu überprüfen. Sollte dieser fehlerhaft sein, wird die Ansicht [ERREURS] gesendet.
  • Andernfalls wird der Artikel aus dem Warenkorb entfernt:
     // Kauf wird entfernt
    panier.enlever(id);
  • Anschließend wird der Warenkorb erneut angezeigt:
     // Warenkorb wird erneut angezeigt
    return mapping.findForward("afficherPanier");

Die Ansicht, die tatsächlich an den Client gesendet wird, wird durch die Aktion definiert:

        <action 
            path="/retirerachat" type="istia.st.articles.web.struts.RetirerAchatAction">
            <forward name="afficherPanier" path="/panier.do"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Man sieht, dass in Bezug auf die Ansicht die Aktion [/panier.do] ausgelöst wird. Diese wurde bereits beschrieben. Sie zeigt je nach Status des Warenkorbs die Ansicht [PANIER] oder [PANIERVIDE] an.

3.6.6.7. validerpanier.do

Diese Aktion dient dazu, die vom Kunden getätigten Einkäufe zu bestätigen. Konkret bedeutet dies eine einzige Aktion: Die Bestände der gekauften Artikel werden in der Datenbank um die gekauften Mengen verringert. Diese Aktion stammt aus dem folgenden Menü:

 

Der Code HTML des Links [Valider le panier] lautet wie folgt:

<a href="validerpanier.do">Valider le panier</a>

Wenn dieser Link aktiviert wird, werden die Bestände verringert und die Artikelliste erneut angezeigt.

Diese Aktion ist in [struts-config.xml] wie folgt konfiguriert:

        <action 
            path="/validerpanier" type="istia.st.articles.web.struts.ValiderPanierAction">
            <forward name="afficherListeArticles" path="/main.do"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Der Code der Klasse [ValiderPanierAction] lautet wie folgt:

package istia.st.articles.web.struts;

import istia.st.articles.domain.IArticlesDomain;
import istia.st.articles.domain.Panier;
import istia.st.articles.exception.UncheckedAccessArticlesException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
 * @author ST-ISTIA
 *  
 */
public class ValiderPanierAction extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

     // das Kontroll-Servlet
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // Initialisierungsfehler?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // Die Fehlerseite wird angezeigt
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // das Domänenzugriffsobjekt
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
     // die Liste der Fehler bei dieser Aktion
    ArrayList erreurs = new ArrayList();
         // Der Käufer hat seinen Warenkorb bestätigt
        Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // Sitzung abgelaufen
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
     request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
         // Der Warenkorb wird bestätigt
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // nicht normal
            erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
        }
         // Eventuelle Fehler werden abgerufen
         erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(),mainServlet.getHActionPanier() });
      return mapping.findForward("afficherErreurs");
        }
         // Alles scheint in Ordnung zu sein OK – Die Artikelliste wird angezeigt
    return mapping.findForward("afficherListeArticles");
  }
}

Anmerkungen:

  • Der Anfang der Methode [execute] ist identisch mit den zuvor untersuchten Methoden.
  • Der Warenkorb wird aus der Sitzung abgerufen. Ist diese abgelaufen, wird die Ansicht [ERREURS] gesendet:
     // Fehlerliste zu dieser Aktion
    ArrayList erreurs = new ArrayList();
         // Der Käufer hat seinen Warenkorb bestätigt
        Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // Sitzung abgelaufen
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
     request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
  • Die Artikel im Warenkorb werden bestätigt. Es können Fehler auftreten, wenn die Lagerbestände nicht ausreichen, um die Bestellungen zu erfüllen. In diesem Fall wird die Ansicht [ERREURS] gesendet:
         // Der Warenkorb wird bestätigt
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // nicht normal
            erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
        }
         // Eventuelle Fehler werden abgerufen
         erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(),mainServlet.getHActionPanier() });
      return mapping.findForward("afficherErreurs");
        }
  • Wenn alles erfolgreich verlaufen ist, wird die Artikelliste erneut angezeigt:
         // Alles scheint in Ordnung zu sein OK – Die Artikelliste wird angezeigt
    return mapping.findForward("afficherListeArticles");

Die tatsächlich an den Kunden gesendete Ansicht wird durch die Aktion definiert:

        <action 
            path="/validerpanier" type="istia.st.articles.web.struts.ValiderPanierAction">
            <forward name="afficherListeArticles" path="/main.do"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>

Man sieht, dass in Bezug auf die Ansicht die Aktion [/main.do] ausgelöst wird. Diese wurde bereits beschrieben. Sie zeigt die Ansicht [LISTE] an.

3.7. Architektur von MVC mit Spring

3.7.1. Allgemeine Architektur der Anwendung

Kommen wir noch einmal auf die Architektur MVC der Anwendung zurück:

In der ersten Version:

  • wurde die Steuerung durch ein Servlet übernommen
  • wurden die Ansichten durch JSP-Seiten bereitgestellt
  • Das Modell wurde durch eine Gruppe von drei .jar-Archiven bereitgestellt

In der Struts-Version:

  • wurde die Steuerung durch ein Servlet übernommen, das vom generischen Struts-Servlet [ActionServlet] abgeleitet war
  • Die Ansichten wurden durch dieselben Seiten JSP bereitgestellt wie in der Version [Struts]
  • Das Modell wurde durch dieselben drei Archive bereitgestellt

In der Spring-Version:

  • wird der Controller von einem von Spring bereitgestellten Servlet [DispatcherServlet] übernommen
  • Die Ansichten werden durch dieselben Seiten JSP bereitgestellt wie zuvor, bis auf einige Details
  • wird das Modell durch dieselben drei Archive bereitgestellt

Wir werden feststellen, dass die Umstellung unserer Anwendung von Struts auf Spring einfach ist, wenn man bereit ist, nicht alle Elemente zu verwenden, die für eine orthodoxe Spring-Architektur MVC empfohlen werden. Die wichtigsten Änderungen sind folgende:

  • Die Aktionen, die in Struts in speziellen Methoden des Servlets/Controllers oder durch Instanzen von Klassen behandelt wurden, die von der Klasse [Action] abgeleitet waren, werden nun durch Instanzen von Klassen behandelt, die die Spring-Schnittstelle [Controller] implementieren
  • Die erforderlichen Konfigurationsdateien lauten nun wie folgt:
    • [web.xml], da es sich um eine Webanwendung handelt. Diese Datei enthält einen Listener, der bei der Initialisierung der Anwendung die Datei [applicationContext.xml]
    • [applicationContext.xml] auswertet, wodurch die von der Anwendung benötigten Beans erstellt werden, insbesondere die des Modellzugriffsdienstes
  • Die Ansichten JSP werden mit denen von Struts identisch sein. Es wird erforderlich sein, eine neue zu erstellen.

Zur Erinnerung: Die in der vorherigen Version verwendete Architektur MVC STRUTS:

M=modèle
die Fachklassen, die Datenzugriffsklassen und die Datenbank
V=vues
die Seiten JSP
C=contrôleur
das Servlet zur Verarbeitung von Client-Anfragen, die Objekte [Action]

Mit Spring verwenden wir eine identische Architektur:

M=modèle
die Fachklassen, die Datenzugriffsklassen und die Datenbank
V=vues
die Seiten JSP
C=contrôleur
das Servlet zur Verarbeitung von Client-Anfragen, die Objekte, die die Schnittstelle implementieren [Controller]
  • Der Controller ist das Herzstück der Anwendung. Alle Client-Anfragen laufen über ihn. Es handelt sich um ein generisches Servlet, das von SPRING bereitgestellt wird. Es ist vom Typ [DispatcherServlet]. Wir werden diesen Controller fortan als Controller [Spring] bezeichnen.
  • Der Controller [Spring] leitet die Anfrage des Clients an eine der Instanzen vom Typ [Controller] weiter. Pro zu bearbeitender Aktion gibt es eine solche Instanz. Diese wird in der angeforderten URL wie bei Struts definiert. So weiß man, dass die angeforderte Aktion die Aktion [liste] ist, da die angeforderte URL [liste.do] lautet
  • Wenn C der Anwendungskontext ist, nutzt der Controller [Spring] eine Datei namens [C-servlet.xml], die die Rolle der Konfigurationsdatei struts-config.xml in der Struts-Version übernimmt. Jeder Aktion, die von der Anwendung verarbeitet werden soll, wird der Name der Controller-Klasse zugeordnet, die für die Bearbeitung der Anfrage zuständig ist.
  • Der Controller übergibt die Kontrolle an das der Aktion zugeordnete Controller-Objekt. Dazu fordert er die Ausführung der Methode handleRequest dieses Objekts an und übergibt dabei die Anfrage des Clients. An dieser Stelle führt der Entwickler die erforderlichen Schritte aus: Gegebenenfalls muss er auf Fachklassen oder Datenzugriffsklassen zurückgreifen. Am Ende der Verarbeitung übergibt das Controller-Objekt dem Controller den Namen der Ansicht, die er als Antwort an den Client senden soll.
  • In seiner Konfigurationsdatei findet der Controller die Methode URL, die dem Namen der Ansicht zugeordnet ist, deren Anzeige angefordert wurde. Er sendet diese dann. Der Austausch mit dem Client ist damit abgeschlossen.

3.7.2. Das Modell

Es ist dasselbe wie in den beiden vorherigen Versionen. Es besteht aus den Java-Archiven [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception].

3.7.3. Konfiguration der Anwendung

3.7.3.1. Allgemeine Architektur

Die allgemeine Architektur des Eclipse-Projekts sieht wie folgt aus:

Image

3.7.3.2. Konfiguration des Datenzugriffs

Da sich die Schnittstelle für den Datenzugriff nicht ändert, sind die zugehörigen Konfigurationsdateien dieselben wie in der vorherigen Version. Sie sind in [WEB-INF/src] definiert:

Image

Auf dem obigen Screenshot stammen die Dateien [articles.xml, spring-config-sqlmap-firebird.xml, sqlmap-config-firebird.xml, log4j.properties] aus früheren Versionen.

3.7.3.3. Das Archivverzeichnis

In [WEB-INF/lib] befinden sich dieselben Archive wie in der vorherigen Version, mit Ausnahme derjenigen von Struts, das nicht mehr benötigt wird:

Image

3.7.3.4. Konfiguration der Anwendung

Die Anwendung wird mithilfe von drei Dateien konfiguriert: [web.xml, applicationContext.xml, springwebarticles-servlet.xml] im Ordner [WEB-INF]:

Image

Die Datei [web.xml] lautet wie folgt:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
     <!-- Der Spring-Kontext-Lader der Anwendung -->
    <listener>
        <listener-class> 
            org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     <!-- das Servlet -->
    <servlet>
        <servlet-name>springwebarticles</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
     <!-- das URL-Mapping -->
    <servlet-mapping>
        <servlet-name>springwebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
     <!-- das Eingabedokument -->
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Was steht in dieser Datei?

  • Die Startseite der Anwendung ist [/vues/index.jsp] (welcome-file)
  • URL-Anfragen der Form *.do werden an das Servlet [springwebarticles] weitergeleitet (Servlet-Mapping)
  • Das Servlet [springwebarticles] ist eine Instanz der Klasse [org.springframework.web.servlet.DispatcherServlet] (servlet-name, servlet-class), die von Spring bereitgestellt wird.
  • Der Listener [org.springframework.web.context.ContextLoaderListener] wird beim Start der Anwendung gestartet. Seine Hauptaufgabe besteht darin, die in der Datei [applicationContext.xml] definierten Spring-Beans zu instanziieren

Die Datei [applicationContext.xml] lautet wie folgt:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- die Klasse für den Datenzugriff -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- die Geschäftsklasse -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!-- die Konfiguration der Webanwendung-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>

Hier finden sich sowohl bekannte als auch weniger bekannte Elemente. Bei der Initialisierung der Anwendung werden drei Beans instanziiert:

  • articlesDao: Dienst für den Zugriff auf die Schicht [dao]
  • articlesDomain: Dienst für den Zugriff auf das Modell
  • config: Ein Bean, in dem wir die Informationen zusammenfassen, die alle Clients gemeinsam nutzen müssen. Dieser Bean übernimmt die Rolle, die üblicherweise der Anwendungskontext spielt, jedoch mit typisierten statt untypisierten Informationen.

Die letzte Datei [springwebarticles.xml] definiert die von der Anwendung akzeptierten Aktionen auf eine Weise, die der in der Struts-Datei [struts-config.xml] verwendeten sehr ähnlich ist. Ihr Inhalt 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>
     <!-- die Aktionshandler = Controller -->
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="InfosController" 
        class="istia.st.articles.web.spring.InfosController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="AchatController" 
        class="istia.st.articles.web.spring.AchatController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="VoirPanierController" 
        class="istia.st.articles.web.spring.VoirPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="RetirerAchatController" 
        class="istia.st.articles.web.spring.RetirerAchatController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="ValiderPanierController" 
        class="istia.st.articles.web.spring.ValiderPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

     <!-- das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/liste.do">ListController</prop>
                <prop key="/main.do">ListController</prop>
                <prop key="/infos.do">InfosController</prop>
                <prop key="/achat.do">AchatController</prop>
                <prop key="/panier.do">VoirPanierController</prop>
                <prop key="/retirerachat.do">RetirerAchatController</prop>
                <prop key="/validerpanier.do">ValiderPanierController</prop>
            </props>
        </property>
    </bean>

     <!-- View-Handler -->
    <bean id="viewResolver" 
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass">
            <value>org.springframework.web.servlet.view.JstlView</value>
        </property>
        <property name="prefix">
            <value>/vues/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

     <!-- die Meldungsdatei -->
    <bean id="messageSource" 
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename">
            <value>messages</value>
        </property>
    </bean>
</beans>

Was besagt diese Konfigurationsdatei?

  • Dass unser Controller die folgenden URLs verarbeitet:
main.do
um die Liste der Artikel anzuzeigen
liste.do
um die Liste der Artikel anzuzeigen
infos.do
um Informationen zu einem bestimmten Artikel anzuzeigen
achat.do
um einen bestimmten Artikel zu kaufen
panier.do
um den Warenkorb anzuzeigen
retirerachat.do
um einen Artikel aus dem Warenkorb zu entfernen
validerpanier.do
zum Bestätigen eines Warenkorbs
  • Die oben genannten Aktionen entsprechen eins zu eins den Aktionen, die vom Controller in früheren Versionen verarbeitet wurden. Für jede davon ist der Name der Klasse angegeben, die für die Verarbeitung zuständig ist. Nehmen wir das Beispiel der Aktion [/panier.do]:
  • Sie muss von der Bean [VoirPanierController] verarbeitet werden. Dieser Name ist frei wählbar. Es handelt sich um einen einfachen Schlüssel.
                <prop key="/panier.do">VoirPanierController</prop>
  • Der Schlüssel [VoirPanierController] ist der Name einer Bean, die in derselben Konfigurationsdatei definiert ist:
    <bean id="VoirPanierController" 
        class="istia.st.articles.web.spring.VoirPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
  • Die Bean [VoirPanierController] definiert:
  • die zu instanziierende Klasse [ istia.st.articles.web.spring.VoirPanierController] zur Verarbeitung der Aktion
  • wie sie instanziiert werden soll. Hier wird die Bean [config], die durch [applicationContext.xml] definiert und beim Start der Anwendung instanziiert wird, als Parameter übergeben. Dies geschieht für alle Aktionen [Controller]. Somit verfügt jede dieser Aktionen in einem privaten Feld über das Objekt [config], in dem sie alle Informationen findet, die von allen Clients gemeinsam genutzt werden.
  • So müssen die Namen der Ansichten aufgelöst werden:
     <!-- View-Manager -->
    <bean id="viewResolver" 
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass">
            <value>org.springframework.web.servlet.view.JstlView</value>
        </property>
        <property name="prefix">
            <value>/vues/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

Wie bei Struts gibt die Instanz [Controller], die eine Aktion verarbeitet, nach der Verarbeitung einen Schlüssel an den Spring-Controller zurück, um ihm mitzuteilen, welche Ansicht er anzeigen soll. Anhand dieses Schlüssels gibt es verschiedene Strategien, um die dem Schlüssel zugeordnete Ansicht zu generieren. Die gewählte Strategie ist diejenige, die durch den Bean [viewResolver] definiert wird. Hier ist dieser Bean der Klasse [org.springframework.web.servlet.view.InternalResourceViewResolver] mit verschiedenen Initialisierungsparametern zugeordnet. Ohne ins Detail zu gehen, legt die Bean [viewResolver] hier fest, dass, wenn der Schlüssel der Ansicht „XX“ lautet, die generierte Ansicht [/vues/XX.jsp] sein wird. Die Art der an den Client gesendeten Ansichten kann auf verschiedene Weise geändert werden:

  • durch Ändern der Implementierungsklasse für die Bean [viewResolver]
  • durch Ändern der Initialisierungsparameter der Implementierungsklasse

So kann man von einer Ansicht „HTML“ zu einer Ansicht „XML“ wechseln, indem man einfach den Wert der Bean „[viewResolver]“ ändert

  • dem Namen einer Meldungsdatei für die Anwendung (messageSource). In diesem Fall existiert die Datei zwar, ist aber leer. Sie wird nicht verwendet. Sie muss im Verzeichnis [ClassPath] der Anwendung abgelegt werden. Hier wird sie im Verzeichnis [WEB-INF/classes] abgelegt. Unter Eclipse erhält man dieses Ergebnis, indem man sie im Verzeichnis [WEB-INF/src] ablegt:

Image

3.7.4. Die Ansichten JSP

Die verwendeten Ansichten JSP sind diejenigen, die von Struts verwendet werden. Keine davon wird geändert:

Image

Es wird eine einzige neue Ansicht erstellt: redirpanier.jsp. Sie dient ausschließlich dazu, den Client zur Aktion [/panier.do] weiterzuleiten. Ihr Code lautet wie folgt:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/panier.do"/>

Der Leser wird gebeten, die Definition der verschiedenen Ansichten in der Struts-Version noch einmal durchzulesen.

3.7.5. Die Verarbeitung der Aktionen

Die für die Verarbeitung der verschiedenen Aktionen erforderlichen Klassen wurden im Paket [istia.st.articles.web.spring] zusammengefasst:

Image

Lassen Sie uns die Funktionsweise der Spring-Anwendung anhand eines Beispiels veranschaulichen:

  • Der Benutzer ruft die URL [http://localhost:8080/springwebarticles/main.do] auf

Image

Was ist passiert?

  • Die Datei [web.xml] der Anwendung [springwebarticles] wurde aufgerufen:
<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
     <!-- Lader für den Spring-Kontext der Anwendung -->
    <listener>
        <listener-class> 
            org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     <!-- das Servlet -->
    <servlet>
        <servlet-name>springwebarticles</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
     <!-- das URL-Mapping -->
    <servlet-mapping>
        <servlet-name>springwebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
     <!-- das Eingabedokument -->
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
  • Wenn dies die erste Anfrage an die Anwendung war, wurden verschiedene Vorgänge ausgelöst:
    • Der Listener [ org.springframework.web.context.ContextLoaderListener] wurde geladen
    • er hat die Konfigurationsdatei [applicationContext.xml] ausgewertet:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- die Klasse für den Datenzugriff -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- die Geschäftsklasse -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!-- die Konfiguration der Webanwendung-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>
  • Die oben genannten Beans wurden im Anwendungskontext erstellt
  • anschließend wurde die Datei [springwebarticles-servlet.xml] verarbeitet:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- die Aktionshandler = Controller -->
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="InfosController" 
        class="istia.st.articles.web.spring.InfosController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="AchatController" 
        class="istia.st.articles.web.spring.AchatController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="VoirPanierController" 
        class="istia.st.articles.web.spring.VoirPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="RetirerAchatController" 
        class="istia.st.articles.web.spring.RetirerAchatController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
    <bean id="ValiderPanierController" 
        class="istia.st.articles.web.spring.ValiderPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

     <!-- das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/liste.do">ListController</prop>
                <prop key="/main.do">ListController</prop>
                <prop key="/infos.do">InfosController</prop>
                <prop key="/achat.do">AchatController</prop>
                <prop key="/panier.do">VoirPanierController</prop>
                <prop key="/retirerachat.do">RetirerAchatController</prop>
                <prop key="/validerpanier.do">ValiderPanierController</prop>
            </props>
        </property>
    </bean>

     <!-- View-Handler -->
    <bean id="viewResolver" 
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass">
            <value>org.springframework.web.servlet.view.JstlView</value>
        </property>
        <property name="prefix">
            <value>/vues/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

     <!-- die Meldungsdatei -->
    <bean id="messageSource" 
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename">
            <value>messages</value>
        </property>
    </bean>
</beans>
  • Die durch diese Datei definierten Beans [Controller] wurden ebenfalls erstellt
  • Nun ist alles bereit, um die Anfrage des Clients zu bearbeiten. Diese lautete: [http://localhost:8080/springwebarticles]. Hier wird nicht ein URL aus dem Kontext angefordert, sondern der Kontext selbst. Daher wird der Abschnitt [welcome-file] der Datei [web.xml] verwendet.
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
  • Die Ansicht [index.jsp] sieht wie folgt aus:
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/main.do"/>
  • Der Client wird daher aufgefordert, zur URL [http://localhost:8080/springwebarticles/main.do] weiterzuleiten. Er tut dies.
  • Der Spring-Controller erhält somit eine neue Anfrage. Er verarbeitet die Datei [springwebarticles-servlet.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- Aktionshandler = Controller -->
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
....
    </bean>

     <!-- das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/liste.do">ListController</prop>
                <prop key="/main.do">ListController</prop>
                <prop key="/infos.do">InfosController</prop>
                <prop key="/achat.do">AchatController</prop>
                <prop key="/panier.do">VoirPanierController</prop>
                <prop key="/retirerachat.do">RetirerAchatController</prop>
                <prop key="/validerpanier.do">ValiderPanierController</prop>
            </props>
        </property>
    </bean>

</beans>
  • Diese Datei teilt ihm mit, dass die Aktion [/main.do] von der Bean [ListController] verarbeitet werden muss.
  • Die Anfrage des Clients wird an die Methode [handleRequest] der Bean [ListController] weitergeleitet. Diese führt ihre Aufgabe aus und gibt dem Controller den Schlüssel für die anzuzeigende Ansicht zurück. Wenn alles gut läuft, lautet dieser Schlüssel hier [liste].
  • Der Spring-Controller verwendet die Bean [viewResolver] aus der Konfigurationsdatei [springwebarticles-servlet.xml], um die diesem Schlüssel zugeordnete Ansicht zu ermitteln. In diesem Fall ist dies die Ansicht [/vues/liste.jsp]
  • Die Ansicht [/vues/liste.jsp] wird an den Client gesendet

3.7.6. Initialisierung der Spring-Anwendung

Wir haben bereits erwähnt, dass beim Start der Anwendung die Beans aus der Datei [applicationContext.xml] instanziiert werden:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- die Datenzugriffsklasse -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- die Geschäftsklasse -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!-- die Konfiguration der Webanwendung-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>

Wir kennen die Beans [articlesDao, articlesDomain], nicht jedoch die Bean [config]. Diese wird durch die folgende Java-Klasse definiert:

package istia.st.articles.web.spring;

import java.util.Hashtable;
import istia.st.articles.domain.IArticlesDomain;

/**
 * @author ST - ISTIA
 */

public class Config {

     // private Felder
    private IArticlesDomain articlesDomain = null;
    private final String ACTION_LISTE = "liste.do";
    private final String ACTION_PANIER = "panier.do";
    private final String ACTION_VALIDATION_PANIER = "validerpanier.do";
    private final String lienActionListe = "Liste des articles";
    private final String lienActionPanier = "Voir le panier";
    private final String lienActionValidationPanier = "Valider le panier";
    private Hashtable hActionListe = new Hashtable(2);
    private Hashtable hActionPanier = new Hashtable(2);
    private Hashtable hActionValidationPanier = new Hashtable(2);

     // Getter und Setter
    public IArticlesDomain getArticlesDomain() {
        return articlesDomain;
    }

    public void setArticlesDomain(IArticlesDomain articlesDomain) {
        this.articlesDomain = articlesDomain;
    }

    public Hashtable getHActionListe() {
        return hActionListe;
    }

    public Hashtable getHActionPanier() {
        return hActionPanier;
    }

    public Hashtable getHActionValidationPanier() {
        return hActionValidationPanier;
    }

     // Initialisierung der Webanwendung
    public void init() {
         // Wir speichern bestimmte URLs der Anwendung
        hActionListe.put("href", ACTION_LISTE);
        hActionListe.put("lien", lienActionListe);
        hActionPanier.put("href", ACTION_PANIER);
        hActionPanier.put("lien", lienActionPanier);
        hActionValidationPanier.put("href", ACTION_VALIDATION_PANIER);
        hActionValidationPanier.put("lien", lienActionValidationPanier);

         // Fertig
        return;
    }
}

Diese Klasse führt das aus, was die Methode [init] eines Web-Anwendungs-Servlets tut. Sie initialisiert die Anwendung. Dies geschieht hier wie folgt:

  • da die Bean [config] in [applicationContext.xml] wie folgt definiert ist:
     <!-- Konfiguration der Webanwendung-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>

Bei der Erstellung wurde sein privates Feld [articlesDomain] initialisiert

  • Anschließend wird aufgrund des Attributs [init-method="init"] des oben genannten Beans die Methode [init] der mit dem Bean verknüpften Klasse ausgeführt. Dort initialisiert sie die drei Wörterbücher [hActionListe, hActionPanier, hActionValidationPanier], die zur Generierung der drei möglichen Links des dem Benutzer angebotenen Menüs verwendet werden.
  • Es werden öffentliche Accessoren erstellt, um diese privaten Felder für Instanzen vom Typ [Controller] zugänglich zu machen, die die Aktionen verarbeiten werden.

3.7.7. Die Aktionen [Controller] der Spring-Anwendung

3.7.7.1. Einführung

Jede Spring-Aktion wird durch eine Klasse vom Typ [Controller] abgedeckt. In der Struts-Version wurde jede Aktion durch eine Klasse vom Typ [Action] abgedeckt. Das Erstellen der Klasse [Controller] besteht meist darin,

  • aus dem Kopieren und Einfügen der Klasse [Action], die in der Struts-Version verwendet wurde,
  • den Code an die Spring-Konventionen anzupassen

3.7.7.2. main.do, liste.do

Diese beiden Aktionen sind identisch und in [springwebarticles-servlet.xml] wie folgt definiert:

     <!-- Das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/liste.do">ListController</prop>
                <prop key="/main.do">ListController</prop>
...
            </props>
        </property>
    </bean>

    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Sie sind mit der Klasse [ istia.st.articles.web.spring.ListController] verknüpft, auf die wir im Folgenden näher eingehen werden. Wenn eine dieser Aktionen in einem Browser ausgeführt wird, erhält man das folgende Ergebnis:

Image

Der Code der Klasse [istia.st.articles.web.spring.ListController] lautet wie folgt:

package istia.st.articles.web.spring;

import istia.st.articles.exception.UncheckedAccessArticlesException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class ListController implements Controller {

     // Konfiguration der Web-App
    Config config;

    public void setConfig(Config config) {
        this.config = config;
    }

     // Bearbeitung der Anfrage
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

         // Die Artikelliste wird angefordert
        List articles = null;
        try {
            articles = config.getArticlesDomain().getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // Der Fehler wird gespeichert
            ArrayList erreurs = new ArrayList();
            erreurs.add("Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
             // Die Fehlerseite wird angezeigt
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { config
                    .getHActionListe() });
             // die Fehleransicht senden
            return new ModelAndView("erreurs");
        }
         // Die Artikelliste wird angezeigt
        request.setAttribute("listarticles", articles);
        request.setAttribute("message", "");
        request.setAttribute("actions", new Hashtable[] { config
                .getHActionPanier() });
         // Ansicht senden
        return new ModelAndView("liste");
    }

}

Anmerkungen:

  • Die Klasse verfügt über ein privates Feld [config]. Dieses Feld wurde von Spring initialisiert, als die Bean [ListController] instanziiert wurde:
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Wie oben zu sehen ist, wird das Feld [config] von [ListController] mit dem Bean [config] initialisiert. Worum handelt es sich dabei? Es handelt sich um die Bean [config], die in [applicationContext.xml] und c.a.d definiert ist – eine Instanz von [istia.st.articles.web.spring.Config], die weiter oben beschrieben wurde.

  • Das Schreiben des Codes für eine Klasse [Controller] besteht im Wesentlichen darin, den Code ihrer Methode [handleRequest] zu schreiben
  • Man fordert die Liste der Artikel vom Modell an. Dieses ist über [config.getArticlesDomain()] zugänglich. Tritt eine Ausnahme auf, wird die Ansicht [ERREURS] gesendet. Das von [handleRequest] zurückgegebene Ergebnis muss vom Typ [ModelAndView] sein. Diese Klasse kann auf verschiedene Arten instanziiert werden. Hier – und das wird immer der Fall sein – wird eine Instanz von [ModelAndView] erstellt, indem ihr der Schlüssel der anzuzeigenden Ansicht übergeben wird. Zur Erinnerung: Aufgrund der Konfiguration des Beans [viewResolver] führt die Anforderung der Ansicht mit dem Schlüssel XX dazu, dass die Ansicht [/vues/XX.jsp] gesendet wird.
         // die Artikelliste wird angefordert
        List articles = null;
        try {
            articles = config.getArticlesDomain().getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // Der Fehler wird gespeichert
            ArrayList erreurs = new ArrayList();
            erreurs.add("Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
             // Die Fehlerseite wird angezeigt
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { config
                    .getHActionListe() });
             // Fehlerseite senden
            return new ModelAndView("erreurs");
        }
  • Wenn keine Fehler vorliegen, wird die Ansicht [LISTE] gesendet:
1
2
3
4
5
6
7
         // Die Artikelliste wird angezeigt
        request.setAttribute("listarticles", articles);
        request.setAttribute("message", "");
        request.setAttribute("actions", new Hashtable[] { config
                .getHActionPanier() });
         // Ansicht senden
        return new ModelAndView("liste");

3.7.7.3. infos.do

Diese Aktion dient dazu, Informationen zu einem der in der Ansicht [LISTE] angezeigten Artikel bereitzustellen:

Diese Aktion ist in [springwebarticles-servlet.xml] wie folgt definiert:

     <!-- das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/infos.do">InfosController</prop>
...
            </props>
        </property>
    </bean>
...
    <bean id="InfosController" 
        class="istia.st.articles.web.spring.InfosController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Der Code der Klasse [InfosController] lautet wie folgt:

package istia.st.articles.web.spring;

import istia.st.articles.dao.Article;
import istia.st.articles.exception.UncheckedAccessArticlesException;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class InfosController implements Controller {

     // Konfiguration der Webanwendung
    Config config;

    public void setConfig(Config config) {
        this.config = config;
    }

     // Bearbeitung der Anfrage
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

     // Fehlerliste
    ArrayList erreurs = new ArrayList();
     // Die angeforderte ID wird abgerufen
    String strId = request.getParameter("id");
     // Irgendetwas?
    if (strId == null) {
       // nicht normal
      erreurs.add("action incorrecte([infos,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     // strId wird vollständig umgewandelt
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // nicht normal
      erreurs.add("action incorrecte([infos,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     // Der Schlüssel-ID-Eintrag wird angefordert
    Article article = null;
    try {
      article = config.getArticlesDomain().getArticleById(id);
    } catch (UncheckedAccessArticlesException ex) {
       // nicht normal
      erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
    if (article == null) {
       // nicht normal
      erreurs.add("Article de clé [" + id + "] inexistant");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     // Der Artikel wird in die Sitzung aufgenommen
    request.getSession().setAttribute("article", article);
     // Die Informationsseite wird angezeigt
    request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
        return new ModelAndView("infos");
    }
}

Anmerkungen:

  • Die Methode [handleRequest] ruft den Parameter [id] ab, der normalerweise in der URL enthalten sein muss. Diese muss tatsächlich die Form [/infos.do?id=X] haben. Es werden verschiedene Tests durchgeführt, um das Vorhandensein und die Gültigkeit des Parameters [id] zu prüfen. Im Falle eines Problems wird die Ansicht [ERREURS] gesendet.
  • Ist [id] gültig, wird der entsprechende Artikel von der Ebene [domain] angefordert. Löst diese eine Ausnahme aus oder wird der Artikel nicht gefunden, wird wiederum die Ansicht [ERREURS] gesendet.
  • Wenn alles gut geht, wird der abgerufene Artikel in die Sitzung gespeichert. Dies ist ein strittiger Punkt. Hier geht man davon aus, dass der Kunde diesen Artikel möglicherweise kaufen wird. Tut er dies, wird der Artikel aus der Sitzung abgerufen, anstatt ihn erneut bei der Ebene [domain] anzufordern.
  • Schließlich wird die Ansicht [INFOS] angezeigt.

3.7.7.4. achat.do

Diese Aktion dient dazu, den Artikel zu kaufen, der in der vorherigen Ansicht [INFOS] angezeigt wurde:

Image

Betrachtet man den Code HTML dieser Ansicht, so stellt man fest, dass das Tag <form> wie folgt definiert ist:

        <form method="post" action="achat.do?id=3"/>
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qte <input type="text" name="qte" size="3" value=""></td>
                    <td></td>
                </tr>
            </table>
        </form>

Man sieht also, dass das Formular mit der Aktion [achat.do] an den Controller gesendet wird.

Diese Aktion ist in [springwebarticles-servlet.xml] wie folgt konfiguriert:

     <!-- das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
...
                <prop key="/achat.do">AchatController</prop>
...
            </props>
        </property>
    </bean>

    <bean id="AchatController" 
        class="istia.st.articles.web.spring.AchatController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Der Code der Klasse [AchatController] lautet wie folgt:

package istia.st.articles.web.spring;

import istia.st.articles.dao.Article;
import istia.st.articles.domain.Achat;
import istia.st.articles.domain.Panier;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class AchatController implements Controller {

   // Konfiguration der Webanwendung
  Config config;

  public void setConfig(Config config) {
    this.config = config;
  }

   // Bearbeitung der Anfrage
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // die Liste der Fehler bei dieser Aktion
    ArrayList erreurs = new ArrayList();
     // Die gekaufte Menge wird abgerufen
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // Falsche Menge
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("infos");
    }
     // Abruf der Kundensitzung
    HttpSession session = request.getSession();
     // Abruf des in der Sitzung gespeicherten Artikels
    Article article = (Article) session.getAttribute("article");
     // Ist die Sitzung abgelaufen?
    if (article == null) {
       // Die Fehlerseite wird angezeigt
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // Neuer Kauf wird angelegt
    Achat achat = new Achat(article, qté);
     // Der Einkauf wird in den Warenkorb des Kunden gelegt
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
     // Zurück zur Artikelliste
    return new ModelAndView("index");
  }
}

Anmerkungen:

  • Zur Erinnerung: Das an den Controller gesendete Formular sieht wie folgt aus:
        <form method="post" action="achat.do?id=3"/>
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qte <input type="text" name="qte" size="3" value=""></td>
                    <td></td>
                </tr>
            </table>
        </form>
  • Die Anfrage enthält zwei Parameter: [id]: Nummer des gekauften Artikels, [qte]: gekaufte Menge.
  • Das Vorhandensein und die Gültigkeit des Parameters [qte] werden geprüft. Wird dieser Parameter als fehlerhaft erkannt, wird dem Benutzer die Ansicht [INFOS] zusammen mit einer Fehlermeldung zurückgegeben:
     // die Fehlerliste zu dieser Aktion
    ArrayList erreurs = new ArrayList();
     // Die gekaufte Menge wird abgerufen
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // Falsche Menge
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("infos");
    }
  • Der gekaufte Artikel wird aus der Sitzung abgerufen. Diese ist möglicherweise abgelaufen. In diesem Fall wird die Ansicht [ERREURS] gesendet:
     // Die Sitzung des Kunden wird abgerufen
    HttpSession session = request.getSession();
     // Abruf des in der Sitzung gespeicherten Artikels
    Article article = (Article) session.getAttribute("article");
     // Ist die Sitzung abgelaufen?
    if (article == null) {
       // Die Fehlerseite wird angezeigt
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
  • Wenn die Sitzung nicht abgelaufen ist, wird der Artikel in den Warenkorb gelegt, der ebenfalls aus der Sitzung abgerufen wird:
1
2
3
4
5
6
7
8
9
     // Neuer Kauf wird angelegt
    Achat achat = new Achat(article, qté);
     // Der Einkauf wird in den Warenkorb des Kunden gelegt
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
  • Schließlich wird die Ansicht [LISTE] gesendet:
     // Zurück zur Artikelliste
    return new ModelAndView("index");
  • Oben wird die Ansicht [/vues/index.jsp] gesendet. Wir wissen, dass diese den Client-Browser anweist, zur URL [/main.do] umzuleiten. Durch diese Weiterleitung wird die Liste der Artikel angezeigt.

3.7.7.5. panier.do

Diese Aktion dient dazu, alle Einkäufe des Kunden anzuzeigen. Sie ist über das Menü verfügbar:

Image

Der dem obigen Link zugeordnete Code HTML lautet wie folgt:

<a href="panier.do">Voir le panier</a>

Die über diesen Link aufgerufene Seite lautet wie folgt:

Image

Diese Aktion ist in [springwebarticles-servlet.xml] wie folgt konfiguriert:

     <!-- das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
...
                <prop key="/panier.do">VoirPanierController</prop>
...
            </props>
        </property>
    </bean>
...
    <bean id="VoirPanierController" 
        class="istia.st.articles.web.spring.VoirPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Der Code der Klasse [VoirPanierController] lautet wie folgt:

package istia.st.articles.web.spring;

import istia.st.articles.domain.Panier;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class VoirPanierController implements Controller {

   // Konfiguration der Web-App
  Config config;

  public void setConfig(Config config) {
    this.config = config;
  }

   // Bearbeitung der Anfrage
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // Der Warenkorb wird angezeigt
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null || panier.getAchats().size() == 0) {
       // Warenkorb leer
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("paniervide");
    } else {
       // Der Warenkorb enthält etwas
      request.setAttribute("actions", new Hashtable[] {
          config.getHActionListe(), config.getHActionValidationPanier() });
      return new ModelAndView("panier");
    }
  }
}

Anmerkungen:

  • Der Warenkorb wird aus der Sitzung übernommen, in der er sich normalerweise befindet. Die Sitzung ist möglicherweise abgelaufen, sodass kein Warenkorb vorhanden ist. Dieser Fall wird nicht als Fehler behandelt, sondern es wird einfach davon ausgegangen, dass der Warenkorb leer ist.
  • Ist der Warenkorb leer, wird die Ansicht [PANIERVIDE] angezeigt
  • Andernfalls wird die Ansicht [PANIER] angezeigt

3.7.7.6. retirerachat.do

Diese Aktion dient dazu, einen Artikel aus dem Warenkorb zu entfernen:

Image

Betrachtet man den Code HTML aus dem obigen Link, so ergibt sich Folgendes:

<a href="retirerachat.do?id=3">Retirer</a>

Die Aktion [retirerachat.do] erhält also als Parameter die ID des Artikels, der aus dem Warenkorb entfernt werden soll. Diese Aktion ist in [springwebarticles-servlet.xml] wie folgt konfiguriert:

     <!-- Anwendungs-Mapping-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
...
                <prop key="/retirerachat.do">RetirerAchatController</prop>
            </props>
        </property>
    </bean>
...
    <bean id="RetirerAchatController" 
        class="istia.st.articles.web.spring.RetirerAchatController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Der Code der Klasse [RetirerAchatController] lautet wie folgt:

package istia.st.articles.web.spring;

import istia.st.articles.domain.Panier;

import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class RetirerAchatController implements Controller {

   // Konfiguration der Webanwendung
  Config config;

  public void setConfig(Config config) {
    this.config = config;
  }

   // Bearbeitung der Anfrage
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // die Liste der Fehler bei dieser Aktion
    ArrayList erreurs = new ArrayList();
     // Der Warenkorb wird abgerufen
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // Sitzung abgelaufen
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // Die ID des zu entfernenden Artikels wird abgerufen
    String strId = request.getParameter("id");
     // Etwas?
    if (strId == null) {
       // nicht normal
      erreurs.add("action incorrecte([retirerachat,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // strId wird in eine Ganzzahl umgewandelt
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // nicht normal
      erreurs.add("action incorrecte([retirerachat,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // Den Kauf stornieren
    panier.enlever(id);
     // Der Warenkorb wird erneut angezeigt
    request.setAttribute("actions",
        new Hashtable[] { config.getHActionListe() });
    return new ModelAndView("redirpanier");
  }
}

Anmerkungen:

  • Der Code überprüft das Vorhandensein und die Gültigkeit des Parameters [id]. Sollte dieser fehlerhaft sein, wird die Ansicht [ERREURS] gesendet.
  • Andernfalls wird der Artikel aus dem Warenkorb entfernt:
     // Kauf wird entfernt
    panier.enlever(id);
  • Anschließend wird der Warenkorb erneut angezeigt:
     // Warenkorb wird erneut angezeigt
    request.setAttribute("actions",
        new Hashtable[] { config.getHActionListe() });
    return new ModelAndView("redirpanier");

Hier noch einmal der Code der Ansicht [/vues/redirpanier.jsp]:

<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/panier.do"/>

Man sieht, dass der Kunde zur Aktion [/panier.do] weitergeleitet wird. Diese wurde bereits beschrieben. Je nach Status des Warenkorbs wird dabei die Ansicht [PANIER] oder [PANIERVIDE] angezeigt.

3.7.7.7. validerpanier.do

Diese Aktion dient dazu, die vom Kunden getätigten Einkäufe zu bestätigen. Konkret bedeutet dies einen einzigen Vorgang: Die Bestände der gekauften Artikel werden in der Datenbank um die gekauften Mengen verringert. Diese Aktion stammt aus dem folgenden Menü:

Image

Der Code HTML des Links [Valider le panier] lautet wie folgt:

<a href="validerpanier.do">Valider le panier</a>

Wenn dieser Link aktiviert wird, werden die Bestände verringert und die Artikelliste erneut angezeigt.

Diese Aktion ist in [springwebarticles-servlet.xml] wie folgt konfiguriert:

     <!-- das Mapping der Anwendung-->
    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
...
                <prop key="/validerpanier.do">ValiderPanierController</prop>
            </props>
        </property>
    </bean>
...
    <bean id="ValiderPanierController" 
        class="istia.st.articles.web.spring.ValiderPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Der Code der Klasse [ValiderPanierController] lautet wie folgt:

package istia.st.articles.web.spring;

import istia.st.articles.domain.Panier;
import istia.st.articles.exception.UncheckedAccessArticlesException;

import java.util.ArrayList;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class ValiderPanierController implements Controller {

   // Konfiguration der Web-App
  Config config;

  public void setConfig(Config config) {
    this.config = config;
  }

   // Bearbeitung der Anfrage
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // die Liste der Fehler bei dieser Aktion
    ArrayList erreurs = new ArrayList();
     // Der Käufer hat seinen Warenkorb bestätigt
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // Sitzung abgelaufen
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // Der Warenkorb wird bestätigt
    try {
      config.getArticlesDomain().acheter(panier);
    } catch (UncheckedAccessArticlesException ex) {
       // nicht normal
      erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // Eventuelle Fehler werden abgerufen
    erreurs = config.getArticlesDomain().getErreurs();
    if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe(), config.getHActionPanier() });
      return new ModelAndView("erreurs");
    }
     // Alles scheint in Ordnung zu sein OK – Die Artikelliste wird angezeigt
    return new ModelAndView("index");
  }
}

Anmerkungen:

  • Der Warenkorb wird aus der Sitzung abgerufen. Ist diese abgelaufen, wird die Ansicht [ERREURS] gesendet:
     // die Liste der Fehler bei dieser Aktion
    ArrayList erreurs = new ArrayList();
     // Der Käufer hat seinen Warenkorb bestätigt
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // Sitzung abgelaufen
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
  • Die Artikel im Warenkorb werden bestätigt. Es können Fehler auftreten, wenn die Lagerbestände nicht ausreichen, um die Bestellungen zu erfüllen. In diesem Fall wird die Ansicht [ERREURS] gesendet:
     // Der Warenkorb wird bestätigt
    try {
      config.getArticlesDomain().acheter(panier);
    } catch (UncheckedAccessArticlesException ex) {
       // nicht normal
      erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // Eventuelle Fehler werden abgerufen
    erreurs = config.getArticlesDomain().getErreurs();
    if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe(), config.getHActionPanier() });
      return new ModelAndView("erreurs");
    }
  • Wenn alles erfolgreich verlaufen ist, wird die Artikelliste erneut angezeigt:
     // Alles scheint in Ordnung zu sein OK – Die Artikelliste wird angezeigt
    return new ModelAndView("index");

Es ist bekannt, dass die Ansicht [/vues/index.jsp] den Kunden zur Aktion [/main.do] weiterleitet. Diese Aktion zeigt die Ansicht [LISTE] an.