Skip to content

3. Madde 2 - Üç katmanlı web mimarisi örnekleri

Makalenin amaçları:

  • 3 katmanlı mimariler
  • temel MVC web mimarisi
  • Struts mimarisi MVC
  • Spring mimarisi MVC

Kullanılan araçlar:

  • 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, sürücü JDBC. Aslında, herhangi bir JDBC kaynağı iş görür.
  • IBExpert, kişisel sürüm: http://www.hksoftware.net/download/ibep_2005.2.14.1_full.exe (Mart 2005). IBExpert, SGBD Firebird'ü grafiksel olarak yönetmenizi sağlar.
  • Tomcat: http://jakarta.apache.org/tomcat/
  • Eclipse için Tomcat eklentisi: http://www.sysdeo.com/eclipse/tomcatPlugin.html. Ayrıca şu belgeye de bakınız: https://tahe.developpez.com/java/eclipse/

Bu belgeyi anlamak için çeşitli ön koşullar gereklidir. Bunların bazıları, benim yazdığım belgelerde ele alınmıştır. Bu durumda, bunlara atıfta bulunacağım. Elbette bu sadece bir öneridir ve okuyucu kendi tercih ettiği belgeleri kullanabilir.

  • Java dili: [https://tahe.developpez.com/java/cours]
  • Java ile web programlama: [https://tahe.developpez.com/java/web/]
  • Java, Eclipse ve Tomcat ile web programlama: [https://tahe.developpez.com/java/eclipse/]
  • Struts ile web programlama: [https://tahe.developpez.com/java/struts/]
  • Spring'in IoC özelliğinin kullanımı: [https://tahe.developpez.com/java/springioc]
  • JSTL etiket kütüphanesi: [https://tahe.developpez.com/java/eclipse/] (kısmen)
  • Ibatis SqlMap belgeleri: [https://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
  • Firebird: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf] (Mart 2005).

Bu belgedeki fikirler, 2004 yazında okuduğum, Rod Johnson'ın Wrox yayınevinden çıkan muhteşem eseri olan J2EE Development without EJB kitabından esinlenmiştir.


3.1. webarticles uygulaması

Burada, bir e-ticaret web uygulamasının bazı unsurlarını sunmak istiyoruz. Bu uygulama, web kullanıcılarının

  • bir veritabanından gelen ürün listesini görüntülemelerine
  • bazılarını elektronik sepetlerine ekleyebilmelerini
  • ve sepeti onaylamalarına olanak tanıyacaktır. Bu onay işleminin tek etkisi, satın alınan ürünlerin stok bilgilerinin veritabanında güncellenmesidir.

Kullanıcıya sunulan farklı görünümler şunlar olacaktır:

  • satışta olan ürünlerin listesini gösteren [LISTE] görünümü

Image

  • bir ürün hakkında ek bilgiler veren [INFOS] görünümü:

Image

  1. Müşterinin sepetinin içeriğini gösteren [PANIER] görünümü

Image

  1. Müşterinin sepeti boş olduğunda görüntülenen [PANIERVIDE]

Image

  1. uygulamadaki tüm hataları bildiren [ERREURS] görünümü

Image

3.2. Uygulamanın genel mimarisi

Aşağıdaki üç katmanlı yapıya sahip bir uygulama oluşturmak istiyoruz:

  • Java arayüzlerinin kullanılmasıyla üç katman birbirinden bağımsız hale getirilmiştir
  • Farklı katmanların entegrasyonu Spring ile gerçekleştirilmektedir
  • Her katman, web (kullanıcı arayüzü katmanı), domain (iş mantığı katmanı) ve DAO (veri erişim katmanı) olmak üzere ayrı paketler halinde düzenlenmiştir.

Burada, [domain] ve [dao] katmanlarının hazır olduğunu varsayacağız. Yalnızca [web] katmanıyla ilgileneceğiz ve bu katmanı çeşitli şekillerde oluşturmayı planlıyoruz:

  • klasik servlet denetleyici teknolojisi kullanılarak - JSP sayfaları
  • Struts teknolojisi kullanılarak MVC
  • Spring teknolojisi kullanılarak MVC

Her durumda, uygulama MVC mimarisine (Model - Görünüm - Denetleyici) uygun olacaktır. Yukarıdaki katman şemasını ele alırsak, MVC mimarisi bu şemaya şu şekilde entegre olur:

Bir istemciden gelen isteğin işlenmesi aşağıdaki adımlara göre gerçekleşir:

  1. müşteri, denetleyiciye bir istek gönderir. Bu denetleyici, tüm müşteri isteklerini alan bir servlettir. Uygulamanın giriş kapısıdır. MVC'teki C harfini temsil eder.
  2. denetleyici bu talebi işler. Bunu yapmak için iş katmanının yardımına ihtiyaç duyabilir; bu, MVC yapısında M modeli olarak adlandırılır.
  3. Denetleyici, iş katmanından bir yanıt alır. Müşterinin isteği işlenmiştir. Bu, birkaç farklı yanıtı tetikleyebilir. Klasik bir örnek,
    • isteğin düzgün bir şekilde işlenememesi durumunda bir hata sayfası
    • aksi takdirde bir onay sayfası
  4. denetleyici, müşteriye gönderilecek yanıtı (= görünüm) seçer. Bu, çoğu zaman dinamik öğeler içeren bir sayfadır. Denetleyici, bu öğeleri görünüme sağlar.
  5. Görünüm, müşteriye gönderilir. Bu, MVC'teki V'dir.

3.3. Model

Burada MVC'in M'sini inceliyoruz. Model burada aşağıdaki öğelerden oluşur:

  1. iş sınıfları
  2. veri erişim sınıfları
  3. veritabanı

3.3.1. Veritabanı

Veritabanı, yalnızca ARTICLES adlı bir tablo içermektedir. Bu tablo, aşağıdaki SQL komutlarıyla oluşturulmuştur:

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
);


/* kısıtlamalar */
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<>'');

/* birincil anahtar */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
bir ürünü benzersiz bir şekilde tanımlayan birincil anahtar
nom
ürün adı
prix
ürünün fiyatı
stockactuel
mevcut stok durumu
stockminimum
yeniden sipariş verilmesi gereken stok seviyesi

Aşağıdaki testlerde [Firebird] veritabanı kullanılmıştır. [Firebird], SGBD'in "açık kaynak" versiyonudur. JDBC [firebirdsql-full.jar] sürücüsü, web uygulamasının [WEB-INF/lib] klasörüne yerleştirilmiştir.

3.3.2. Şablonun paketleri

M şablonu burada üç arşiv şeklinde sunulmaktadır:

  • istia.st.articles.dao: [dao] katmanının veri erişim sınıflarını içerir
  • istia.st.articles.exception: bu ürün yönetimi için bir istisna sınıfı içerir
  • istia.st.articles.domain: [domain] katmanının iş sınıflarını içerir
archive
contenu
rôle
istia.st.articles.dao
- [istia.st.articles.dao] paketini içerir; bu paket ise aşağıdaki öğeleri içerir:
- [IArticlesDao]: Dao katmanına erişim arayüzü. Bu, [domain] katmanının gördüğü tek arayüzdür. Başka bir arayüz görmez.
- [Article]: bir makaleyi tanımlayan sınıf
- [ArticlesDaoSqlMap]: [IArticlesDao] arayüzünün, SqlMap aracıyla gerçekleştirilen sınıfı
veri erişim katmanı – tamamen web uygulamasının 3-katmanlı mimarisinin [dao] katmanında yer alır
istia.st.articles.domain
- [istia.st.articles.domain] paketini içerir; bu paket ise aşağıdaki öğeleri içerir:
- [IArticlesDomain]: [domain] katmanına erişim arayüzüdür. Web katmanının gördüğü tek arayüzdür. Başka bir arayüz görmez.
- [AchatsArticles]: [IArticlesDomain]'i uygulayan bir sınıftır
- [Achat]: Bir müşterinin satın alma işlemini temsil eden sınıf
- [Panier]: Bir müşterinin tüm satın alımlarını temsil eden sınıf
web üzerindeki satın alma işlemlerinin modelini temsil eder - web uygulamasının 3 katmanlı mimarisinin [domain] katmanında tamamen yer alır
istia.st.articles.exception
- [istia.st.articles.exception] paketini içerir; bu paket ise aşağıdaki öğeleri içerir:
- [UncheckedAccessArticlesException]: [RuntimeException] türünde bir istisna tanımlayan sınıf. Bu istisna türü, verilere erişimde bir sorun meydana geldiğinde [dao] katmanı tarafından tetiklenir.
 

3.3.3. [istia.st.articles.dao] paketi

Bir öğeyi tanımlayan sınıf şöyledir:

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) {
     // örnek özniteliklerini başlat
    setId(id);
    setNom(nom);
    setPrix(prix);
    setStockActuel(stockActuel);
    setStockMinimum(stockMinimum);
  }

     // getter'lar - setter'lar
  public int getId() {
    return id;
  }

  public void setId(int id) {
     // geçerli kimlik mi?
    if (id < 0)
      throw new UncheckedAccessArticlesException("id[" + id + "] invalide");
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
     // ad geçerli mi?
    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) {
     // fiyat geçerli mi?
    if(prix<0) throw new UncheckedAccessArticlesException("Prix["+prix+"]invalide");
    this.prix = prix;
  }

  public int getStockActuel() {
    return stockActuel;
  }

  public void setStockActuel(int stockActuel) {
     // stok geçerli mi?
    if (stockActuel < 0)
      throw new UncheckedAccessArticlesException("stockActuel[" + stockActuel + "] invalide");
    this.stockActuel = stockActuel;
  }

  public int getStockMinimum() {
    return stockMinimum;
  }

  public void setStockMinimum(int stockMinimum) {
     // stok geçerli mi?
    if (stockMinimum < 0)
      throw new UncheckedAccessArticlesException("stockMinimum[" + stockMinimum + "] invalide");
    this.stockMinimum = stockMinimum;
  }

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

Bu sınıf şunları sunar:

  1. bir öğenin 5 bilgisini ayarlamaya yarayan bir oluşturucu
  2. 5 bilgiyi okumak ve yazmak için kullanılan, genellikle "getter/setter" olarak adlandırılan erişim yöntemleri. Bu yöntemlerin adları JavaBean standardına uygundur. JavaBean nesnelerinin, DAO katmanında SGBD verileriyle arayüz oluşturmak için kullanılması yaygın bir uygulamadır.
  3. Makaleye girilen verilerin doğrulanması. Hatalı veriler olması durumunda bir istisna atılır.
  4. Bir toString yöntemi, bir maddenin değerini karakter dizisi biçiminde almayı sağlar. Bu, genellikle bir uygulamanın hata ayıklamasında yararlıdır.

[IArticlesDao] arayüzü şu şekilde tanımlanmıştır:

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);
}

Arayüzdeki farklı yöntemlerin işlevleri şunlardır:

getAllArticles
ARTICLES tablosundaki tüm kalemleri bir [Article] nesne listesine dönüştürür
clearAllArticles
ARTICLES tablosunu boşaltır
getArticleById
birincil anahtarıyla tanımlanan [Article] nesnesini döndürür
ajouteArticle
ARTICLES tablosuna bir öğe ekler
modifieArticle
[ARTICLES] tablosundaki bir öğeyi değiştirmeye olanak tanır
supprimerArticle
[ARTICLES] tablosundan bir ürünü silmeye yarar
changerStockArticle
[ARTICLES] tablosundaki bir ürünün stokunu değiştirmeye olanak tanır

Arayüz, istemci programlarına yalnızca imzalarıyla tanımlanan bir dizi yöntem sunar. Bu yöntemlerin gerçekte nasıl uygulanacağıyla ilgilenmez. Bu, uygulamaya esneklik kazandırır. İstemci programı, arayüzün belirli bir uygulamasına değil, arayüze çağrı yapar.

Belirli bir uygulamanın seçimi, bir Spring yapılandırma dosyası aracılığıyla yapılacaktır. Burada, IArticlesDao arayüzünü, SqlMap adlı bir açık kaynak ürünü kullanarak uygulamayı öneriyoruz. Bu sayede, Java kodundan tüm SQL komutlarını kaldırabileceğiz.

[ArticlesDaoSqlMap] uygulama sınıfı şu şekilde tanımlanmıştır:

package istia.st.articles.dao;

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

public class ArticlesDaoSqlMap implements IArticlesDao {

   // Alanlar
  private SqlMapClient sqlMap;

   // Yapıcılar
  public ArticlesDaoSqlMap(String sqlMapConfigFileName) { }

   // Yöntemler
  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) {}
}

Veri kaynağına eşzamanlı erişim sorunlarını önlemek için tüm veri erişim yöntemleri senkronize edilmiştir. Herhangi bir anda, belirli bir yönteme yalnızca tek bir iş parçacığı erişebilir.

[ArticlesDaoSqlMap] sınıfı, [Ibatis SqlMap] aracını kullanır. Bu aracın amacı, Java kodundaki veri erişim kodunu (SQL) ayıklamaktır. Bu kod daha sonra bir yapılandırma dosyasına yerleştirilir. Bu konuya daha sonra tekrar değineceğiz. [ArticlesDaoSqlMap] sınıfının oluşturulabilmesi için, sınıfın oluşturucusuna parametre olarak adı geçirilen bir yapılandırma dosyasına ihtiyaç vardır. Bu yapılandırma dosyası, aşağıdakiler için gerekli bilgileri tanımlar:

  • içinde makalelerin bulunduğu SGBD'e erişmek
  • bağlantı havuzunu yönetmek
  • işlemleri yönetmek

Örneğimizde, bu dosyanın adı [sqlmap-config-firebird.xml] olacak ve bir Firebird veritabanına erişimi tanımlayacaktır:

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

Yukarıda bahsedilen [articles.xml] yapılandırma dosyası, SGBD'teki [ARTICLES] tablosundaki bir satırdan [istia.st.articles.dao.Article] sınıfının bir örneğinin nasıl oluşturulacağını tanımlamaya olanak tanır. Ayrıca, [dao] katmanının Firebird veri kaynağından verileri almasını sağlayacak SQL sorgularını da tanımlar.

<?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">

     <!-- istia.st.articles.dao.Article sınıfı üzerinde bir takma ad -->
  <typeAlias alias="article" type="istia.st.articles.dao.Article"/>

     <!-- eşleme ORM :  ARTICLES tablosu satırı - Article sınıfı örneği -->
  <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>

     <!-- tüm makaleleri almak için SQL sorgusu -->
  <statement id="getAllArticles" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum from ARTICLES
</statement>

     <!-- tüm makaleleri silmek için SQL sorgusu -->
  <statement id="clearAllArticles">delete from ARTICLES</statement>

     <!-- bir ürün eklemek için SQL sorgusu -->
  <statement id="insertArticle">
    insert into ARTICLES (id, nom, prix,
    stockactuel, stockminimum) values
    (#(id#,#ad#,#fiyat#,#mevcutstok#,#minimumstok#)
</statement>

     <!-- Belirli bir ürünü silmek için SQL sorgusu -->
  <statement id="deleteArticle">delete FROM ARTICLES where id=#id#</statement>

     <!-- belirli bir ürünü değiştirmek için SQL sorgusu -->
  <statement id="modifyArticle">
    update ARTICLES set nom=#isim#,
    prix=#fiyat#,mevcutstok=#mevcutstok#,minimumstok=#minimumstok# where
    id=#id#
</statement>

     <!-- belirli bir ürünü almak için SQL sorgusu -->
  <statement id="getArticleById" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum FROM ARTICLES where id=#id#
</statement>

     <!-- belirli bir ürünün stokunu değiştirmek için SQL sorgusu -->
  <statement id="changerStockArticle">
    update ARTICLES set
    stockActuel=stockActuel+#hareket#
    where id=#id# ve stockActuel+#hareket#&gt;=0
</statement>
</sqlMap>

[dao] paketinin kodu ekte yer almaktadır.

3.3.4. [istia.st.articles.domain] paketi

[IArticlesDomain] arayüzü, [métier] katmanını [web] katmanından ayırır. [web] katmanı, bu arayüz aracılığıyla [métier/domain] katmanına erişir ve bunu gerçekleştiren sınıfla ilgilenmez. Arayüz, iş katmanına erişim için aşağıdaki eylemleri tanımlar:

 package istia.st.articles.domain;

// İthalatlar
import java.util.ArrayList;
import java.util.List;

public abstract interface IArticlesDomain {

   // Yöntemler
  void acheter(Panier panier);
  List getAllArticles();
  Article getArticleById(int idArticle);
  ArrayList getErreurs();
}
List getAllArticles()
[Article] nesne listesini müşteriye sunulmak üzere döndürür
Article getArticleById(int idArticle)
[idArticle] ile tanımlanan [Article] nesnesini döndürür
void acheter(Panier panier)
müşterinin sepetini, satın alınan ürünlerin stoklarından satın alınan miktarı düşerek onaylar – stok yetersizse işlem başarısız olabilir
ArrayList getErreurs()
meydana gelen hataların listesini döndürür - hata yoksa boş

Burada, [IArticlesDomain] arayüzü aşağıdaki [AchatsArticles] sınıfı tarafından uygulanacaktır:

package istia.st.articles.domain;

// İthalatlar
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 {

   // Alanlar
  private IArticlesDao articlesDao;
  private ArrayList erreurs;

   // Oluşturucular
  public AchatsArticles(IArticlesDao articlesDao) { }

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

Bu sınıf, [IArticlesDomain] arayüzünün dört yöntemini uygular. İki özel alanı vardır:

IArticlesDao articlesDao
veri erişim katmanı tarafından sağlanan veri erişim nesnesi
ArrayList erreurs
olası hataların listesi

Sınıfın bir örneğini oluşturmak için, SGBD verilerine erişim sağlayan nesneyi belirtmek gerekir:

public AchatsArticles(IArticlesDao articlesDao)
yapıcı

[Achat] sınıfı, müşterinin bir satın alma işlemini temsil eder:

package istia.st.articles.domain;

public class Achat {

   // Alanlar
  private Article article;
  private int qte;

  // Oluşturucular
  public Achat(Article article, int qte) { }

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

[Achat] sınıfı, aşağıdaki alanlara ve yöntemlere sahip bir JavaBean sınıfıdır:

article
satın alınan ürün
qte
satın alınan miktar
double getTotal()
satın alma tutarını gösterir
String toString()
nesnenin kimlik dizesi

[Panier] sınıfı, müşterinin tüm satın alımlarını temsil eder:

package istia.st.articles.domain;

// İthalatlar
import java.util.ArrayList;

public class Panier {

  // Alanlar
  private ArrayList achats;

   // Oluşturucular
  public Panier() { }

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

[Panier] sınıfı, aşağıdaki alanlara ve yöntemlere sahip bir JavaBean sınıfıdır:

achats
müşterinin satın alma listesi - [Achat] türündeki nesnelerin listesi
void ajouter(Achat unAchat)
satın alma listesine bir satın alma ekler
void enlever(int idArticle)
idArticle ürününü alışveriş listesinden çıkar
double getTotal()
alışverişlerin toplam tutarını hesaplar
String toString()
sepetin kimlik dizesini göster
ArrayList getAchats()
alışveriş listesini döndürür

[domain] paket kodunu ekte bulabilirsiniz.

3.3.5. [istia.st.articles.exception] paketi

Bu paket, [dao] katmanı veri kaynağına erişimde bir sorunla karşılaştığında attığı istisnayı tanımlayan sınıfı içerir:

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. Model testleri

M modeli, Eclipse ortamında aşağıdaki yapılandırma ile test edilmiştir:

Image

Yorumlar:

  • [WEB-INF/lib] içinde şunlar bulunur:
    • [ibatis SqlMap] aracının SGBD Firebird'e erişmesi için gerekli dosyalar: ibatis-*.jar
    • [spring] aracı için gerekli olan dosya: spring.jar
    • SGBD ve [Firebird] için JDBC sürücüsü: firebirdsql-full.jar
    • günlükler için gerekli dosyalar: log4-*.jar, commons-logging.jar
    • test edilen modelin üç dosyası: istia.st.articles.*.jar
    • test aracı için gerekli dosya: [junit]
  • [WEB-INF/src] içinde, Eclipse tarafından otomatik olarak [WEB-INF/classes]'e kopyalanacak yapılandırma dosyaları bulunur:
    • [sqlmap] aracının yapılandırma dosyaları: sqlmap-config-firebird.xml, articles.xml
    • [spring] aracının yapılandırma dosyaları: spring-config-test-dao.xml, spring-config-test-domain.xml
    • [log4j] aracının yapılandırma dosyası: log4j.properties
  • [istia.st.articles.tests] paketinde, modelin test sınıfları bulunur

3.3.6.1. [dao] katmanının testleri

[dao] katmanına ait JUnit test sınıfı aşağıdaki gibidir. Bu sınıfı inceleyerek, [IArticlesDao] arayüzündeki yöntemlerin nasıl kullanıldığını anlayabilirsiniz:

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;

// ArticlesDaoSqlMap sınıfının testi
public class JunitModeleDaoArticles extends TestCase {

     // test edilen sınıfın bir örneği
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // veri erişim örneğini alır
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-dao.xml"))).getBean("articlesDao");
    }

    public void testGetAllArticles() {
         // ürünleri görüntüler
        listArticles();
    }

    public void testClearAllArticles() {
         // makale tablosunu boşaltır
        articlesDao.clearAllArticles();
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
    }

    public void testAjouteArticle() {
         // ARTICLES tablosunun içeriğini siler
        articlesDao.clearAllArticles();
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // ekleme
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // ARTICLES tablosunu okur
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         //görüntüler
        listArticles();
    }

    public void testSupprimeArticle() {
         // ARTICLES tablosunun içeriğini sil
        articlesDao.clearAllArticles();
         //, ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // ekleme
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // ARTICLES tablosunu okur
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         // silme
        articlesDao.supprimeArticle(4);
         // ARTICLES tablosunu okur
        articles = articlesDao.getAllArticles();
        assertEquals(1, articles.size());
         // tabloyu görüntüler
        listArticles();
    }

    public void testModifieArticle() {
         // ARTICLES tablosunun içeriğini sil
        articlesDao.clearAllArticles();
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // ekleme
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // ARTICLES tablosunu okur
        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");
         // değişiklik
        articlesDao.modifieArticle(new Article(4, "article4", 44, 44, 44));
         // getById
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getPrix(), 44, 1e-6);
         // tabloyu görüntüler
        listArticles();
    }

    public void testGetArticleById() {
         // ARTICLES içeriğinin silinmesi
        articlesDao.clearAllArticles();
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // ekleme
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // ARTICLES tablosunu okur
        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() {
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
         // okunan makaleler görüntülenir
        for (int i = 0; i < articles.size(); i++) {
            System.out.println(((Article) articles.get(i)).toString());
        }
    }

    public void testChangerStockArticle() throws InterruptedException {
         // ARTICLES içeriğinin silinmesi
        articlesDao.clearAllArticles();
         // ekleme
        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);
         // 3 numaralı ürünün stok güncelleme iş parçacıklarından 100 tanesinin oluşturulması
        Thread[] taches = new Thread[100];
        for (int i = 0; i < taches.length; i++) {
            taches[i] = new ThreadMajStock("thread-" + i, articlesDao);
            taches[i].start();
        }
         // iş parçacıklarının bitmesini bekliyoruz
        for (int i = 0; i < taches.length; i++) {
            taches[i].join();
        }
         // 3 numaralı ürünü alıp stok durumunu kontrol etme
        Article unArticle = articlesDao.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        assertEquals(1, unArticle.getStockActuel());
         // 4 numaralı ürünün stokunda değişiklik
        boolean erreur = false;
        int nbLignes = articlesDao.changerStockArticle(4, -100);
        assertEquals(0, nbLignes);
         // tabloyu görüntüler
        listArticles();
    }
}

Açıklamalar:

  • Test sınıfı, setUp yöntemi sayesinde test edilecek sınıfın bir örneğini depolar:
1
2
3
4
5
6
7
8
     // test edilen sınıfın bir örneğini
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // veri erişim örneğini alır
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-dao.xml"))).getBean("articlesDao");
    }
  • Test edilecek nesne, [Spring] tarafından sağlanır. Yukarıda, [articlesDao] adlı Spring bean'i talep ediyoruz. Bu bean, Spring yapılandırma dosyası [spring-config-test-dao.xml]'te tanımlanmıştır:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
</beans>

Yukarıda görüldüğü gibi, [articlesDao] bean'i, [istia.st.articles.dao.ArticlesDaoSqlMap] sınıfının bir örneğidir. Bu sınıfın, [SqlMap] aracının yapılandırma dosyasının adını parametre olarak bekleyen bir yapıcıya sahiptir. Bu ad burada sağlanmıştır. Adı [sqlmap-config-firebird.xml]'tir. Bu dosya daha önce açıklanmıştır. SGBD'in verilerine erişmek için gerekli tüm bilgileri sağlar.

[testChangerStockArticle] yöntemi, belirli bir ürünün stokunu azaltmakla görevli 100 iş parçacığı oluşturur. Burada amaç, SGBD'e eşzamanlı erişimleri test etmektir. [istia.st.articles.dao.ArticlesDaoSqlMap] sınıfındaki [changerStockArticle] yöntemi senkronize edildiği için bu test başarılı olur. Senkronizasyon kaldırılırsa test artık başarılı olmaz. Stoku güncellemekle görevli sınıf şöyledir:

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() {
         // izleme
        System.out.println(name + " lancé");
         // 3 numaralı ürün stok değişikliği
        articlesDao.changerStockArticle(3, -1);
         // takip
        System.out.println(name + " terminé");
    }
}
  • Yukarıdaki sınıf, 3 numaralı ürünün stok miktarını 1 azaltır

3.3.6.2. [domain] katmanının testleri

[domain] katmanına ait JUnit test sınıfı şöyledir:

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;

// ArticlesDaoSqlMap sınıfının testi
public class JunitModeleDomainArticles extends TestCase {

     // alan erişim sınıfının bir örneği
    private IArticlesDomain articlesDomain;

     // veri erişim sınıfının bir örneği
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // bir etki alanı erişim örneği alır
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");
         // bir veri erişim örneğini alır
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");
    }

     // belirli bir makaleyi alır
    public void testGetArticleById() {
         // ARTICLES içeriğinin silinmesi
        articlesDao.clearAllArticles();
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // ekleme
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         // ARTICLES tablosunu okur
        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");
    }

     // ekran görüntüleme
    private void listArticles() {
         // ARTICLES tablosunu okur
        List articles = articlesDomain.getAllArticles();
         // okunan öğeler görüntülenir
        for (int i = 0; i < articles.size(); i++) {
            System.out.println(((Article) articles.get(i)).toString());
        }
    }

     // makaleleri satın alma
    public void testAchatPanier(){
         // ARTICLES tablosunun içeriğini silme
        articlesDao.clearAllArticles();
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // ekleme
        Article article3=new Article(3, "article3", 30, 30, 3);
        articlesDao.ajouteArticle(article3);
        Article article4=new Article(4, "article4", 40, 40, 4);
        articlesDao.ajouteArticle(article4);
         // ARTICLES tablosunu okur
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         // iki ürün içeren bir sepet oluşturuluyor
        Panier panier=new Panier();
        panier.ajouter(new Achat(article3,10));
        panier.ajouter(new Achat(article4,10));
         // kontroller
        assertEquals(700.0,panier.getTotal(),1e-6);
        assertEquals(2,panier.getAchats().size());
         // sepetin onaylanması
        articlesDomain.acheter(panier);
         // kontroller
        assertEquals(0,articlesDomain.getErreurs().size());
        assertEquals(0,panier.getAchats().size());
         // 3 numaralı ürünü ara
        article3=articlesDomain.getArticleById(3);
        assertEquals(20,article3.getStockActuel());
         // 4 numaralı ürünü ara
        article4=articlesDomain.getArticleById(4);
        assertEquals(30,article4.getStockActuel());
         // yeni sepet
        panier.ajouter(new Achat(article3,100));
         // sepeti onayla
        articlesDomain.acheter(panier);
         // kontroller - fazla alışveriş yapılmış
         // bir hata olmalı
        assertEquals(1,articlesDomain.getErreurs().size());
         // ürün no. 3'ü ara
        article3=articlesDomain.getArticleById(3);
         // stok durumu değişmemiş olmalı
        assertEquals(20,article3.getStockActuel());    
    }

     // satın alımları kaldır
    public void testRetirerAchats(){
         // ARTICLES içeriğinin silinmesi
        articlesDao.clearAllArticles();
         // ARTICLES tablosunu okur
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         // ekleme
        Article article3=new Article(3, "article3", 30, 30, 3);
        articlesDao.ajouteArticle(article3);
        Article article4=new Article(4, "article4", 40, 40, 4);
        articlesDao.ajouteArticle(article4);
         // ARTICLES tablosunu okur
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         // iki ürün içeren bir sepet oluşturuluyor
        Panier panier=new Panier();
        panier.ajouter(new Achat(article3,10));
        panier.ajouter(new Achat(article4,10));
         // kontroller
        assertEquals(700.0,panier.getTotal(),1e-6);
        assertEquals(2,panier.getAchats().size());
         // daha önce satın alınmış bir ürünü ekleme
        panier.ajouter(new Achat(article3,10));
         // kontroller
         // toplam tutar 1000'e yükseltilmelidir
        assertEquals(1000.0,panier.getTotal(),1e-6);
         // sepet içinde hala 2 ürün var
        assertEquals(2,panier.getAchats().size());
         // 3 numaralı ürünün adedi 20'ye ayarlanmalı
        Achat achat=(Achat)panier.getAchats().get(0);
        assertEquals(20,achat.getQte());
         // 3 numaralı ürün sepetten çıkarılsın
        panier.enlever(3);
         // kontroller
         // toplam tutar 400'e ayarlanmalı
        assertEquals(400.0,panier.getTotal(),1e-6);
         // Sepette sadece 1 ürün olmalı
        assertEquals(1,panier.getAchats().size());
         // Bu, 4 numaralı ürün olmalı
        assertEquals(4,((Achat)panier.getAchats().get(0)).getArticle().getId());
    }
}

Açıklamalar:

  • Test sınıfı, setUp yöntemi sayesinde, test edilecek sınıfın bir örneğini ve veri erişim sınıfının bir örneğini saklar. Bu son nokta tartışmalıdır. Teorik olarak, test sınıfının, bilmesi bile gerekmeyen [dao] katmanına erişime ihtiyacı olmamalıdır. Burada, bu "etik" kuralı göz ardı ettik; bu kurala uymak için [IArticlesDomain] arayüzümüzde yeni yöntemler oluşturmamız gerekirdi.
     // alan erişim sınıfının bir örneği
    private IArticlesDomain articlesDomain;

     // veri erişim sınıfının bir örneği
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // bir etki alanı erişim örneği alır
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");
         // veri erişim sınıfının bir örneğini alır
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");
    }
  • Test edilecek nesne, [Spring] tarafından sağlanır. Yukarıda, [articlesDomain] adlı Spring bean’i talep ediyoruz. Bu bean, Spring yapılandırma dosyası [spring-config-test-domain.xml]’te tanımlanmıştır:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- iş sınıfı -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
</beans>

Yukarıda görüldüğü gibi, [articlesDomain] bean'i, [istia.st.articles.domain.AchatsArticles] sınıfının bir örneğidir. Bu sınıfın, [dao] katmanına erişim sağlayan ve [IArticlesDao] türünde bir nesneyi parametre olarak bekleyen bir yapıcıya sahiptir. Burada yapılandırma dosyası, bu nesnenin [articlesDao] adlı bean olduğunu belirtir. Bu, Spring’in bu bean’i örneklendirmesini zorunlu kılar. [articlesDao] bean’inin örneklendirilmesi daha önce açıklanmıştı. Sonuç olarak, iki bean örneklendirilmiştir:

  • [articlesDao] tipi [istia.st.articles.dao.ArticlesDaoSqlMap]
  • [articlesDomain], türü [ istia.st.articles.domain.AchatsArticles]

Bu iki örneklendirme, Spring'e yapılan ilk çağrı tarafından tetiklenir:

1
2
3
4
         // alan erişim örneğini alır
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");

Böylece [articlesDomain] bean'i alınır. Spring'e yapılan ikinci çağrıda:

1
2
3
         // veri erişim örneğini alır
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");

[Spring], önceki çağrıda zaten oluşturulmuş olan [articlesDao] bean'ine bir referans döndürmekle yetinir. Bu, singleton ilkesidir. Spring'den bir bean talep edildiğinde, eğer henüz mevcut değilse onu oluşturur, aksi takdirde mevcut bean'e bir referans döndürür.

3.4. Üç katmanlı MVC web uygulaması

Bundan sonra, aşağıdaki üç katmanlı web uygulamasını oluşturmak istiyoruz:

Uygulama, MVC mimarisine sahip olacaktır. M modeli yazılmış ve test edilmiştir. Bu, daha önce açıklanan modeldir. Bize üç arşiv halinde sağlanmıştır: [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception]. C denetleyicisini ve V görünümlerini yazmamız gerekiyor.

Öncelikle, şu klasik yöntemi ele alalım:

  • C denetleyicisi tek bir servlet tarafından sağlanır
  • V görünümleri, JSP sayfaları tarafından sağlanır

3.5. Bir kontrolcü servlet ve JSP sayfalarına dayalı MVC mimarisi

Uygulamanın MVC mimarisi şu şekilde olacaktır:

M=modèle
iş işlev sınıfları, veri erişim sınıfları ve veritabanı
V=vues
sayfalar JSP
C=contrôleur
istemci isteklerini işleyen servlet

3.5.1. Model

Daha önce tanıtılmıştı. [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception] adlı Java arşivlerinden oluşur.

3.5.2. Görünümler

Görünümler, belgenin başında tanıtılanlarla aynıdır:

LISTE
liste.jsp
Görünümler, uygulamanın [vues] klasöründe toplanmıştır
INFOS
infos.jsp
PANIER
panier.jsp
PANIERVIDE
paniervide.jsp
ERREURS
erreurs.jsp

3.5.3. Denetleyici

Denetleyici, [WebArticles] adlı tek bir servlet'ten oluşacaktır. Bu servlet, istemcilerin çeşitli isteklerini işleyecektir. Bu istekler, istemcinin HTTP isteğinde [action] parametresinin bulunmasıyla belirtilecektir:

requête
anlamı
denetleyicinin eylemi
olası yanıtlar
action=liste
müşteri şunların listesini istiyor
ürünlerin listesini
- iş katmanından ürün listesini talep ediyor
katmanından
katmanından ürün listesini talep ediyor
- [ERREURS]
action=infos
Müşteri,
görünümde görüntülenen ürünlerden biri hakkında bilgi istiyor
[LISTE]
- iş katmanından ürünü talep eder
- [INFOS]
- [ERREURS]
action=achat
müşteri bir ürün satın alır
- iş katmanından ürünü talep eder ve
ürünü müşterinin sepetine ekler
- [INFOS] (miktar hatası varsa)
- [LISTE] hata yoksa
action=retirerachat
müşteri sepetinden bir
sepetinden bir ürünü silmek istiyor
- oturumdaki sepeti alır ve değiştirir
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
müşteri,
sepetini
- oturumdan alışveriş sepetini alır
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
müşteri alışverişini tamamladı
ve ödeme aşamasına geçiyor
- satın alınan ürünlerin stok bilgilerini
satın alınan ürünlerin stok bilgilerini günceller
- müşterinin sepetinden
satın alımı onaylanan ürünleri
- [LISTE]
- [ERREURS]

3.5.4. Uygulamanın Yapılandırılması

Uygulamayı, aşağıdaki gibi değişikliklere karşı olabildiğince esnek olacak şekilde yapılandırmaya çalışacağız:

  1. farklı görünümlerin URL'lerinde yapılan değişiklikler
  2. [IArticlesDao] ve [IArticlesDomain] arayüzlerini uygulayan sınıfların değiştirilmesi
  3. SGBD, veritabanı ve ürün tablosundaki değişiklikler

3.5.5. URL değişiklikleri

Görünümlerin URL adları, diğer bazı parametrelerle birlikte uygulamanın yapılandırma dosyası olan [web.xml] dosyasına yerleştirilecektir:

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

[web.xml] dosyasında

  • uygulamanın farklı görünümlerinin URL'leri
  • iş mantığı katmanlarına ve DAO'lara erişim sağlayan tekil nesnelerin oluşturulmasını sağlayacak Spring yapılandırma dosyasının adı [springConfigFileName]
  • istemci tarafından istenen URL /<context> olduğunda görüntülenecek olan [/vues/index.jsp] görünümü; burada <context>, uygulamanın bağlamıdır

3.5.6. Arayüzlerin uygulama sınıflarındaki değişiklik

Üç katmanlı mimarilerin mantığına göre, katmanlar birbirlerinden izole olmalıdır. Bu izolasyon şu şekilde sağlanır:

  • katmanlar, somut sınıflar aracılığıyla değil, arayüzler aracılığıyla birbirleriyle iletişim kurar
  • Bir katmanın kodu, kullanmak amacıyla asla başka bir katmanın sınıfını kendisi örneklendirmez. Sadece harici bir araca (burada [Spring]) kullanmak istediği katmanın arayüzünün bir uygulama örneğini ister. Bunun için, katmanın uygulama sınıfının adını bilmesine gerek olmadığını, yalnızca referansını almak istediği Spring bean'inin adını bilmesi yeterli olduğunu biliyoruz.

Uygulamamızda Spring yapılandırma dosyası şu şekilde olabilir:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- iş sınıfı -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
</beans>

[métier] katmanına erişmek için, [Interface utilisateur, UI] katmanındaki bir sınıf, [articlesDomain] bean'ini talep edebilir. Bunun üzerine Spring, [istia.st.articles.domain.AchatsArticles] türünde bir nesne oluşturacaktır. Bu oluşturma işlemi için [articlesDao] türünde bir bean'e, yani [istia.st.articles.dao.ArticlesDaoSqlMap] türünde bir nesneye ihtiyaç duyar. Spring daha sonra böyle bir nesneyi oluşturacaktır. Bu oluşturma işlemi, [sqlmap-config-firebird.xml] dosyasında bulunan bilgilerden gerçekleştirilecektir; bu dosya, SqlMap aracılığıyla veri erişimi için bir yapılandırma dosyasıdır. İşlemin sonunda, [articlesDomain] bean'ini talep eden [UI] sınıfı, kendisini SGBD verilerine bağlayan tüm zincire sahip olur:

3.5.7. SGBD veya veritabanıyla ilgili değişiklikler

Web uygulamasının, SGBD veya veritabanıyla ilgili değişikliklerden bağımsızlığı, burada SqlMap yapılandırma dosyaları tarafından sağlanmaktadır. Bunlardan iki tane vardır:

  1. [sql-map-config-firebird.xml] dosyası
<?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>

Bu dosya burada bir Firebird veritabanını belirtir. Başka bir SGBD ile çalışmak için JDBC sürücüsünün adını değiştirmeniz yeterlidir.

  1. [articles.xml] dosyası, uygulama için gerekli olan çeşitli SQL komutlarını bir araya getirir:
<?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">

     <!-- istia.st.articles.dao.Article sınıfı üzerine bir takma ad -->
  <typeAlias alias="article" type="istia.st.articles.dao.Article"/>

     <!-- eşleme ORM :  ARTICLES tablosu satırı - Article sınıfı örneği -->
  <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>

     <!-- tüm makaleleri almak için SQL sorgusu -->
  <statement id="getAllArticles" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum from ARTICLES
</statement>

     <!-- tüm makaleleri silmek için SQL sorgusu -->
  <statement id="clearAllArticles">delete from ARTICLES</statement>

     <!-- bir ürün eklemek için SQL sorgusu -->
  <statement id="insertArticle">
    insert into ARTICLES (id, nom, prix,
    stockactuel, stockminimum) values
    (#(id#,#ad#,#fiyat#,#mevcutstok#,#minimumstok#)
</statement>

     <!-- Belirli bir ürünü silmek için SQL sorgusu -->
  <statement id="deleteArticle">delete FROM ARTICLES where id=#id#</statement>

     <!-- belirli bir ürünü değiştirmek için SQL sorgusu -->
  <statement id="modifyArticle">
    update ARTICLES set nom=#isim#,
    prix=#fiyat#,mevcutstok=#mevcutstok#,minimumstok=#minimumstok# where
    id=#id#
</statement>

     <!-- belirli bir ürünü almak için SQL sorgusu -->
  <statement id="getArticleById" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum FROM ARTICLES where id=#id#
</statement>

     <!-- belirli bir ürünün stokunu değiştirmek için SQL sorgusu -->
  <statement id="changerStockArticle">
    update ARTICLES set
    stockActuel=stockActuel+#hareket#
    where id=#id# ve stockActuel+#hareket#&gt;=0
</statement>
</sqlMap>

Ürün tablosunun veya sütunların adları değişirse, Java kodunu değiştirmeye gerek kalmadan bu yapılandırma dosyasındaki sorguları yeniden yazmak zorunda kalırız. Performans nedenleriyle bir sorgunun depolanmış bir yordamla değiştirilmesi durumunda da durum aynı olur.

3.5.8. [webarticles] uygulamasının genel mimarisi

Bir Java web uygulaması, çok sayıda öğeden oluşan bir yapbozdur. Uygulamaya bir mimari kazandırmak, genellikle bu öğelerin sayısını artırır. [eclipse] altında yer alan [webarticles] uygulamasının yapısı şöyledir:

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. JSP görünümleri

JSP görünümleri, JSTL etiket kütüphanesini kullanır.

3.5.9.1. entete.jsp

Farklı görünümler arasında bir tutarlılık sağlamak amacıyla, bu görünümler, uygulamanın adını ve menüyü gösteren aynı başlığı paylaşacaktır:

Menü dinamiktir ve denetleyici tarafından belirlenir. Denetleyici, JSP sayfasına iletilen isteğe, "actions" anahtar özniteliğini ekler; bu özniteliğin ilişkili değeri, Hastable[] türünde bir dizidir. Bu dizinin her bir elemanı, başlıktaki menü seçeneklerinden birini oluşturmaya yönelik bir sözlüktür. Her sözlük iki anahtara sahiptir:

  • href: menü seçeneğiyle ilişkili URL
  • lien: menü metni

Uygulamanın diğer görünümleri, [entete.jsp] tarafından tanımlanan başlığı, aşağıdaki JSP etiketini kullanarak kullanacaktır:

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

Çalıştırıldığında bu etiket, onu içeren JSP sayfasının koduna [entete.jsp] sayfasının kodunu ekleyecektir. Sayfanın URL’si göreceli bir URL olduğundan (/ işareti bulunmadığından), [entete.jsp] sayfası, <jsp:include> etiketini içeren sayfanın bulunduğu klasörde aranacaktır.

Kod:

<%@ 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

Bu görünüm, satışa sunulan ürünlerin listesini gösterir:

Bu görünüm, /main?action=liste veya /main?action=validationpanier isteği sonrasında görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
listarticles
ArrayList adet [Article] türünde nesne
message
String nesnesi - sayfanın altında görüntülenecek mesaj

[Infos] dizisindeki her bir HTML bağlantısı, [?action=infos&id=ID] biçiminde bir URL'ye sahiptir; burada ID, görüntülenen makalenin id alanıdır.

Kod:

<%@ 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

Bu görünüm, bir ürünle ilgili bilgileri gösterir ve ürünün satın alınmasına da olanak tanır:

Image

Bu sayfa, /main?action=infos&id=ID isteği veya satın alınan miktar hatalı olduğunda /main?action=achat&id=ID isteği sonrasında görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
article
[Article] türü nesne - görüntülenecek ürün
msg
String nesnesi - miktar hatası durumunda görüntülenecek mesaj
qte
String nesnesi - giriş alanında görüntülenecek değer [Qte]

[msg] ve [qte] alanları, miktar girişinde hata olması durumunda kullanılır:

Image

Bu sayfa, [Acheter] düğmesi ile gönderilen bir form içerir. POST'in hedef URL'si [?action=achat&id=ID]'tir; burada ID, satın alınan ürünün kimliğidir.

Kod:

<%@ 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

Bu görünüm, sepetin içeriğini gösterir:

Image

Bu görünüm, /main?action=panier veya /main?action=retirerachat&id=ID isteği sonrasında görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
panier
[Panier] türü nesne - görüntülenecek alışveriş sepeti

Sepetteki satın alma işlemlerinin bulunduğu HTML dizisindeki her bir [Retirer] bağlantısı, [?action=retirerachat&id=ID] biçiminde bir URL'ye sahiptir; burada ID, sepetten çıkarılmak istenen ürünün [id] alanıdırsepetten çıkarmak istediğimiz ürünün [id] alanını ifade eder.

Kod:

<%@ 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

Bu görünüm, sepetin boş olduğunu belirten bilgileri gösterir:

Image

Bu, /main?action=panier veya /main?action=retirerachat&id=ID isteğinin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi

Kod:

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

Hata durumunda bu görünüm görüntülenir:

Image

Bu görünüm, [INFOS] görünümü tarafından işlenen hatalı miktarlı satın alma işlemi hariç, hataya yol açan tüm isteklerin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
erreurs
ArrayList - görüntülenecek hata mesajlarını temsil eden String nesneleri

Kod:

<%@ 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

Bu sayfa, uygulamanın [web.xml] dosyasında uygulamanın ana sayfası olarak tanımlanmıştır:

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

[index.jsp] görünümü, istemciyi uygulamanın giriş noktasına yönlendirmekle yetinir:

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

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

3.5.10. Denetleyici

Şimdi web uygulamamızın kalbini oluşturan denetleyiciyi yazmamız gerekiyor. Denetleyicinin görevi şunlardır:

  • istemciden gelen isteği almak,
  • bu tarafın talep ettiği işlemi iş sınıfları yardımıyla işlemek,
  • yanıt olarak uygun görünümü göndermek.

3.5.10.1. Denetleyicinin başlatılması

Denetleyici sınıfı servlet sunucusu tarafından yüklendiğinde, [init] yöntemi yürütülür. Bu işlem yalnızca bir kez gerçekleşir. Belleğe yüklendikten sonra denetleyici bellekte kalır ve farklı istemcilerin isteklerini işler. Her istemci bir yürütme iş parçacığına sahiptir ve denetleyicinin yöntemleri farklı iş parçacıkları tarafından eşzamanlı olarak yürütülür. Bu nedenle, denetleyicinin yöntemleri tarafından değiştirilebilecek alanlara sahip olmaması gerektiğini hatırlatmak isteriz. Alanları salt okunur olmalıdır. Bu alanlar, temel görevi bu olan [init] yöntemi tarafından başlatılır. Bu yöntemin özelliği, tek bir iş parçacığı tarafından yalnızca bir kez yürütülmesidir. Dolayısıyla bu yöntemde denetleyicinin alanlarına eşzamanlı erişim sorunu yaşanmaz. [init] yöntemi, web uygulaması için gerekli olan ve tüm istemci iş parçacıkları tarafından salt okunur olarak paylaşılacak nesneleri başlatmayı amaçlar. Bu paylaşılan nesneler iki yere yerleştirilebilir:

  • denetleyicinin özel alanları
  • uygulamanın yürütme bağlamı (ServletContext)

[webarticles] uygulamasının [init] yöntemi aşağıdaki işlemleri gerçekleştirecektir:

  • [web.xml] dosyasında, uygulamanın düzgün çalışması için gerekli parametrelerin mevcut olup olmadığını kontrol edecektir. Bu parametreler 3.5.5. paragrafında açıklanmıştır.
  • Olası hataların listesini içeren özel bir [ArrayList erreurs] alanını dolduracaktır. Hata yoksa bu liste boş olacaktır, ancak yine de mevcut olacaktır.
  • Hata oluşmuşsa, [init] yöntemi burada sonlanır. Aksi takdirde, denetleyicinin ihtiyaçları için kullanacağı iş nesnesi olacak [IArticlesDomain] türünde bir nesne oluşturur. 3.5.6 bölümünde açıklandığı gibi, denetleyici Spring çerçevesinden ihtiyaç duyduğu bean'i talep edecektir. Bu örneklendirme işlemi çeşitli hatalara yol açabilir. Böyle bir durumda, bu hatalar yine denetleyicinin [erreurs] alanında kaydedilecektir.

3.5.10.2. doGet ve doPost Yöntemleri

Bu iki yöntem, istemcilerden gelen HTTP, GET ve POST isteklerini işler. Bu istekler birbirinin yerine kullanılabilir. Dolayısıyla, [doPost] yöntemi, [doGet] yöntemine yönlendirebilir veya tam tersi de geçerlidir. Müşteri isteği şu şekilde işlenecektir:

  • [erreurs] alanı kontrol edilecektir. Bu alan boş değilse, bu durum uygulamanın başlatılması sırasında hatalar oluştuğu ve uygulamanın çalışamayacağı anlamına gelir. Bu durumda yanıt olarak [ERREURS] görünümü gönderilecektir.
  • İsteğin [action] parametresi alınacak ve kontrol edilecektir. Bilinen bir eyleme karşılık gelmiyorsa, uygun bir hata mesajıyla birlikte [ERREURS] görünümü gönderilir.
  • [action] parametresi geçerliyse, istemcinin isteği işlenmek üzere eyleme özgü bir prosedüre aktarılır. [uneAction] eylemini işleyen prosedürün imzası şu şekilde olacaktır:
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. Farklı eylemlerin işlenmesi

Uygulamanın olası farklı eylemlerini işleyen yöntemler şunlardır:

yöntem
istek
işleme
olası yanıtlar
doListe
GET /main?action=liste
- iş sınıfına ait makalelerin listesini iste
iş sınıfından
- listesini görüntülemek
[LISTE] veya [ERREURS]
doInfos
GET /main?action=infos&id=ID
- id=ID olan makaleyi
iş sınıfına
- görüntülemek
[INFOS] veya [ERREURS]
doAchat
POST /main?action=satınalma&id=ID
- satın alınan miktar, gönderilen parametreler arasında yer alır
- id=ID olan ürünü
iş sınıfına
- bunu
müşteri oturumunda
[LISTE] veya [INFOS]
veya [ERREURS]
doRetirerAchat
GET /main?action=retirerachat&id=ID
- id=ID olan ürünü
müşterinin sepetindeki
müşteri oturumundan
[PANIER]
doPanier
GET /main?action=panier
- müşteri oturumunun
müşteri oturumu
[PANIER] veya [PANIERVIDE]
doValidationPanier
GET /main?action=sepetonay
- Veritabanında
sepet içinde bulunan tüm ürünlerin
azaltın
GET
[LISTE] veya [ERREURS]

3.5.10.4. Kod

  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 {

     // özel alanlar
    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() {
         // servletin başlatma parametreleri alınır
        config = getServletConfig();
        String param = null;
        for (int i = 0; i < parameters.length; i++) {
            param = config.getInitParameter(parameters[i]);
            if (param == null) {
                 // hatayı kaydediyoruz
                erreurs.add(
                    "Paramètre ["
                        + parameters[i]
                        + "] absent dans le fichier [web.xml]");
            }
        }
         // hata var mı?
        if (erreurs.size() != 0) {
            return;
        }
         // iş katmanına erişim için bir IArticlesDomain nesnesi oluşturulur
        try {
            articlesDomain =
                (IArticlesDomain)
                    (
                        new XmlBeanFactory(
                            new ClassPathResource(
                                (String) config.getInitParameter(
                                    SPRING_CONFIG_FILENAME)))).getBean(
                    "articlesDomain");
        } catch (Exception ex) {
             // hata kaydedilir
            erreurs.add(
                "Erreur de configuration de l'accès aux données : "
                    + ex.toString());
            return;
        }
         // uygulamanın belirli URL'leri kaydedilir
        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);

         // işlem tamamlandı
        return;
    }


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

         // serveletin başlatılmasının nasıl gerçekleştiği kontrol ediliyor
        if (erreurs.size() != 0) {
             // hata sayfasının URL'si var mı?
            if (config.getInitParameter(URL_ERREURS) == null) {
                throw new ServletException(erreurs.toString());
            }
             // hata sayfası görüntüleniyor
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] {
            });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // son
            return;
        }
         // eylemi işliyoruz
        String action = request.getParameter("action");
        if (action == null) {
             // ürün listesi
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_LISTE)) {
             // makalelerin listesi
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_INFOS)) {
             // ürün bilgileri
            doInfos(request, response);
            return;
        }
        if (action.equals(ACTION_ACHAT)) {
             // ürün satın alma
            doAchat(request, response);
            return;
        }
        if (action.equals(ACTION_PANIER)) {
             // sepeti görüntüleme
            doPanier(request, response);
            return;
        }
        if (action.equals(ACTION_RETIRER_ACHAT)) {
             // sepetten ürün silme
            doRetirerAchat(request, response);
            return;
        }
        if (action.equals(ACTION_VALIDATION_PANIER)) {
             // sepetin onaylanması
            doValidationPanier(request, response);
            return;
        }
         // bilinmeyen işlem
        ArrayList erreurs = new ArrayList();
        erreurs.add("action [" + action + "] inconnue");
         // hata sayfası görüntüleniyor
        request.setAttribute("actions", new Hashtable[] { hActionListe });
        afficheErreurs(request, response, erreurs);
         // son
        return;
    }


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

         // Alıcı sepetini onayladı
        Panier panier = (Panier) request.getSession().getAttribute("panier");
         // sepet onaylanıyor
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // normal değil
            erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // hatalar alınır
        ArrayList erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
            request.setAttribute(
                "actions",
                new Hashtable[] { hActionListe, hActionPanier });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // ürün listesi görüntüleniyor
        request.setAttribute("message", "Votre panier a été validé");
        doListe(request, response);
         // son
        return;
    }


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

         // sepetten bir ürünü çıkarıyoruz
        try {
            Panier panier =
                (Panier) request.getSession().getAttribute("panier");
            String strIdAchat = request.getParameter("id");
            panier.enlever(Integer.parseInt(strIdAchat));
        } catch (NumberFormatException ignored) {
        } catch (NullPointerException ignored) {
        }
         // sepet görüntüleniyor
        doPanier(request, response);
    }


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

         // sepet görüntüleniyor
        Panier panier = (Panier) request.getSession().getAttribute("panier");
         // sepet boş mu?
        if (panier == null || panier.getAchats().size() == 0) {
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_PANIER_VIDE))
                .forward(request, response);
             // son
            return;
        }
         // sepet içinde bir şey var
        request.setAttribute("panier", panier);
        request.setAttribute(
            "actions",
            new Hashtable[] { hActionListe, hActionValidationPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_PANIER))
            .forward(request, response);
         // son
        return;
    }


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

         // bir ürün satın al
         // miktar alınır
        int qté = 0;
        try {
            qté = Integer.parseInt(request.getParameter("qte"));
            if (qté <= 0)
                throw new NumberFormatException();
        } catch (NumberFormatException ex) {
             // miktar hatalı
            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);
             // son
            return;
        }
         // müşterinin oturumu alınır
        HttpSession session = request.getSession();
         // satın alma işlemi oluşturuluyor
        Article article = (Article) session.getAttribute("article");
        Achat achat = new Achat(article, qté);
         // satın alma işlemi müşterinin sepetine ekleniyor
        Panier panier = (Panier) session.getAttribute("panier");
        if (panier == null) {
            panier = new Panier();
            session.setAttribute("panier", panier);
        }
        panier.ajouter(achat);
         // ürün listesine geri dönülüyor
        String url = config.getInitParameter(URL_MAIN) + "?action=liste";
        getServletContext().getRequestDispatcher(url).forward(
            request,
            response);
         // son
        return;
    }


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

         // ürün listesi görüntülenir
        request.setAttribute("infos", infos);
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_DEBUG))
            .forward(request, response);
         // son
        return;
    }


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

         // aynı, get
        doGet(request, response);
    }


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

         // hata listesi
        ArrayList erreurs = new ArrayList();
         // istenen kimliği alınıyor
        String strId = request.getParameter("id");
         // bir şey mi?
        if (strId == null) {
             // normal değil
            erreurs.add("action incorrecte([infos,id=null]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // strId'i tamamen dönüştürüyoruz
        int id = 0;
        try {
            id = Integer.parseInt(strId);
        } catch (Exception ex) {
             // normal değil
            erreurs.add("action incorrecte([infos,id=" + strId + "]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // anahtar kimliği öğesi isteniyor
        Article article = null;
        try {
            article=articlesDomain.getArticleById(id);
        } catch (UncheckedAccessArticlesException ex) {
             // normal değil
            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) {
             // normal değil
            erreurs.add("Article de clé [" + id + "] inexistant");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // ürün oturuma ekleniyor
        request.getSession().setAttribute("article", article);
         // bilgi sayfası görüntüleniyor
        request.setAttribute("actions", new Hashtable[] { hActionListe });
         /   / request.setAttribute("urlMain",config.getInitParameter(URL_MAIN));
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_INFOS))
            .forward(request, response);
         // son
        return;
    }

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

         // hata sayfası görüntüleniyor
        request.setAttribute("erreurs", erreurs);
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
            .forward(request, response);
         // son
        return;
    }


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

         // hata listesi
        ArrayList erreurs = new ArrayList();
         // ürün listesi isteniyor
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // hata kaydediliyor
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
         // Hata var mı?
        if (erreurs.size() != 0) {
             // hata sayfası görüntüleniyor
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // son
            return;
        }
         // ürün listesi görüntüleniyor
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);
         // son
        return;
    }

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

Okuyucunun bu kodu okumak ve anlamak için zaman ayırmasını istiyoruz. Açıklamaların bu konuda yardımcı olacağını umuyoruz.

3.5.10.5. Uygulama testleri

Testlere ait birkaç ekran görüntüsü gösterelim. Öncelikle, uygulamanın ana sayfası:

Image

İstenen URL aslında [http://localhost:8080/webarticles] idi. Okuyucu, [web.xml] dosyasında uygulama için bir ana sayfa tanımladığımızı görecektir:

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

[index.jsp] görünümü şu şekilde tanımlanmıştır:

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

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

Dolayısıyla, [http://localhost:8080/webarticles/main?action=liste] URL'sine yönlendirme yapılmış; bu durum, ekran görüntüsündeki tarayıcı URL'sinden de anlaşılmaktadır. Dolayısıyla [/main?action=liste] URL’si talep edilmiştir. Yine [web.xml] içinde, /main URL’si [webarticles] servlet’iyle ilişkilendirilmiştir:

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

Yine [web.xml]'te, [webarticles] servleti, [ istia.st.articles.web.WebArticles] servletiyle ilişkilendirilmiştir:

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

Dolayısıyla, [ istia.st.articles.web.WebArticles] servleti, henüz yüklenmemişse Tomcat servlet konteyneri tarafından yüklenir ve [init] yöntemi yürütülür:

    public void init() {
         // servletin başlatma parametreleri alınır
        config = getServletConfig();
        String param = null;
        for (int i = 0; i < parameters.length; i++) {
            param = config.getInitParameter(parameters[i]);
            if (param == null) {
                 // hata kaydediliyor
                erreurs.add(
                    "Paramètre ["
                        + parameters[i]
                        + "] absent dans le fichier [web.xml]");
            }
        }
         // Hata var mı?
        if (erreurs.size() != 0) {
            return;
        }
         // iş katmanına erişim için bir IArticlesDomain nesnesi oluşturulur
        try {
            articlesDomain =
                (IArticlesDomain)
                    (
                        new XmlBeanFactory(
                            new ClassPathResource(
                                (String) config.getInitParameter(
                                    SPRING_CONFIG_FILENAME)))).getBean(
                    "articlesDomain");
        } catch (Exception ex) {
             // hata kaydediliyor
            erreurs.add(
                "Erreur de configuration de l'accès aux données : "
                    + ex.toString());
            return;
        }
         // uygulamanın belirli URL'leri kaydediliyor
        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);

         // işlem tamamlandı
        return;
    }

Açıklamalar: [init] yöntemi

  • belirli yapılandırma parametrelerinin varlığını kontrol eder
  • Spring aracılığıyla uygulama etki alanına erişim hizmeti başlatır
  • olası başlatma hatalarını belirtmek için bir hata listesi oluşturur
  • bir dizi özel alan:
    • hatalar: [init] tarafından tespit edilen hataların listesi
    • [hActionListe, hActionPanier, hActionValidationPanier]: sözlükler. Her biri, [entete.jsp] görünümü tarafından görüntülenen ana menüdeki bir seçeneği görüntülemek için gerekli bilgileri içerir
    • acticlesDomain: uygulama modeline erişim hizmeti

[init] yöntemi, servletin ilk yüklenmesi sırasında yalnızca bir kez yürütülür. Ardından, istemcinin isteğinin [GET, POST] türüne göre [doGet, doPost] yöntemlerinden biri yürütülür. Burada her iki yöntem de aynı işlevi görür ve kod [doGet] yöntemine yerleştirilmiştir:

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {
         // serveletin başlatılmasının nasıl gerçekleştiği kontrol ediliyor
        if (erreurs.size() != 0) {
             // hata sayfasının URL'si var mı?
            if (config.getInitParameter(URL_ERREURS) == null) {
                throw new ServletException(erreurs.toString());
            }
             // hata sayfası görüntüleniyor
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] {
            });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // son
            return;
        }
         // eylemi işliyoruz
        String action = request.getParameter("action");
        if (action == null) {
             // ürün listesi
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_LISTE)) {
             // ürün listesi
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_INFOS)) {
             // ürün bilgileri
            doInfos(request, response);
            return;
        }
        if (action.equals(ACTION_ACHAT)) {
             // ürün satın alma
            doAchat(request, response);
            return;
        }
        if (action.equals(ACTION_PANIER)) {
             // sepeti görüntüleme
            doPanier(request, response);
            return;
        }
        if (action.equals(ACTION_RETIRER_ACHAT)) {
             // sepetten ürün silme
            doRetirerAchat(request, response);
            return;
        }
        if (action.equals(ACTION_VALIDATION_PANIER)) {
             // sepetin onaylanması
            doValidationPanier(request, response);
            return;
        }
         // bilinmeyen eylem
        ArrayList erreurs = new ArrayList();
        erreurs.add("action [" + action + "] inconnue");
         // hata sayfası görüntüleniyor
        request.setAttribute("actions", new Hashtable[] { hActionListe });
        afficheErreurs(request, response, erreurs);
         // son
        return;
    }
  • [doGet] yöntemi, [init] yönteminin tamamlanmasının ardından herhangi bir başlatma hatası olup olmadığını kontrol ederek başlar. Varsa, [ERREURS] görünümünü görüntüler ve işlem sona erer.
  • Aksi takdirde, istemcinin isteğinden [action] parametresini alır. Uygulamanın, içinde zorunlu olarak [action] parametresinin bulunduğu isteklere yanıt vermek üzere geliştirildiğini hatırlatmak gerekir.
  • Uygulama, eylemle ilişkili yöntemi çalıştırır. Burada bu, [doListe] yöntemi olacaktır.

[doListe] yöntemi şu şekildedir:

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

         // hata listesi
        ArrayList erreurs = new ArrayList();
         // ürün listesi isteniyor
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // hata kaydediliyor
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
         // Hata var mı?
        if (erreurs.size() != 0) {
             // hata sayfası görüntüleniyor
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // son
            return;
        }
         // ürün listesi görüntüleniyor
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);
         // son
        return;
    }
  • [init] yönteminin, uygulama modeline (alan katmanı) erişim hizmetini servletin özel bir alanında sakladığını hatırlayalım:
     // özel alanlar
    private IArticlesDomain articlesDomain = null;
  • Bu erişim hizmeti aracılığıyla makale listesi istenebilir:
         // hata listesi
        ArrayList erreurs = new ArrayList();
         // ürün listesi isteniyor
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // hata kaydediliyor
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
  • Hata oluşması durumunda, [ERREURS] görünümü gönderilir:
         // Hata var mı?
        if (erreurs.size() != 0) {
             // hata sayfası görüntüleniyor
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // son
            return;
        }
  • Aksi takdirde [LISTE] görünümü gönderilir:
1
2
3
4
5
6
7
         // ürün listesi görüntüleniyor
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);

Örneğimizde her şey yolunda gitti ve [LISTE] görünümünü aldık. Okuyucunun, [LISTE] görünümünün kodunu tekrar gözden geçirmesi önerilir; böylece bu görünümün beklediği dinamik parametrelerin yukarıda denetleyici tarafından sağlandığını görebilir. Aynı türden bir kontrol her görünüm için yapılmalıdır:

  • görünümün dinamik parametrelerini bulmak
  • denetleyicinin bunları, görünüme iletilen isteğin özniteliklerine eklediğinden emin olmak

Şimdi, uygulamanın bir kullanıcısının karşılaştığı ekranların akışını basitçe veriyoruz. Okuyucunun her seferinde öncekine benzer bir mantık izlemesi önerilir:

Makale listesinden kullanıcı bir makale seçebilir:

Alıcı, burada 3 numaralı ürünü satın alabilir. Miktar girerken bir hata yapalım:

Hata bildirildi. Şimdi birkaç ürün satın alalım:

Satın alma işlemi kaydedildi ve ürün listesi yeniden görüntülendi. Sepeti kontrol edelim:

Satın alma işlemi sepetinize başarıyla eklendi. Şimdi onu sepetten çıkaralım:

Ürün sepetten çıkarıldı ve sepet yeniden görüntülendi. Burada sepet boş.

3 numaralı üründen 100 adet ve 4 numaralı üründen 2 adet satın alalım:

3 numaralı ürünü satın almak mümkün olmadı çünkü 100 adet satın almak istendi, ancak stokta sadece 30 adet vardı. Bu ürün sepet içinde kaldı:

4 numaralı ürün ise satın alındı; bu durum, ürünün yeni stok sayısının 39 (40-1) olmasıyla da anlaşılmaktadır:

3.6. Struts ile MVC mimarisi

3.6.1. Uygulamanın genel mimarisi

Uygulamanın MVC mimarisine tekrar dönelim:

Önceki sürümde:

  • denetim bir servlet tarafından sağlanıyordu
  • görünümler JSP sayfaları tarafından sağlanıyordu
  • model, üç .jar dosyasından oluşan bir set tarafından sağlanıyordu

Struts sürümünde:

  • denetleyici, Struts'un genel [ActionServlet] servletinden türetilmiş bir servlet tarafından sağlanacaktır
  • görünümler, birkaç küçük ayrıntı dışında, daha önce kullanılanlarla aynı JSP sayfaları tarafından sağlanacaktır
  • Model, yine aynı üç arşiv dosyası tarafından sağlanacaktır

Önceki uygulamanın Struts'a geçişinin aşağıdaki görevlerden oluştuğunu göreceğiz:

  • Daha önce servlet/denetleyicinin belirli yöntemlerinde işlenen eylemler, artık Struts’un [Action] sınıfından türetilen sınıfların örnekleri tarafından işlenmektedir
  • [web.xml] ve [struts-config.xml] yapılandırma dosyalarını yazın
  • JSP sayfalarında bazı değişiklikler yapın

STRUTS tarafından kullanılan genel MVC mimarisini hatırlayalım:

M=modèle
iş işlev sınıfları, veri erişim sınıfları ve veritabanı
V=vues
sayfalar JSP
C=contrôleur
istemci isteklerini işleyen servlet, [Action] nesneleri ve formlarla ilişkili [ActionForm] bean'leri.
  • Denetleyici, uygulamanın kalbidir. Tüm istemci istekleri buradan geçer. Bu, STRUTS tarafından sağlanan genel bir servlettir. Bazı durumlarda bu servleti türetmek gerekebilir. Basit durumlarda ise buna gerek yoktur. Bu genel amaçlı servlet, ihtiyaç duyduğu bilgileri genellikle struts-config.xml olarak adlandırılan bir dosyadan alır.
  • Müşterinin isteği form parametreleri içeriyorsa, bunlar denetleyici tarafından bir Bean nesnesine aktarılır. Zaman içinde bu şekilde oluşturulan Bean nesneleri, oturumda veya müşterinin isteğinde saklanır. Bu ayar değiştirilebilir. Daha önce oluşturulmuşlarsa, yeniden oluşturulmaları gerekmez.
  • struts-config.xml yapılandırma dosyasında, program tarafından işlenmesi gereken her URL dosyasına (yani doğrudan istenebilecek bir JSP görünümüne karşılık gelmeyenlere) belirli bilgiler atanır:
    • isteği işlemekle görevli Action türü sınıfın adı. Burada da, örneklendirilmiş Action nesnesi oturumda veya istekte saklanabilir.
    • Talep edilen URL parametreli ise (örneğin, denetleyiciye bir formun gönderilmesi durumu), form bilgilerini depolamaktan sorumlu bean'in adı belirtilir.
  • Yapılandırma dosyası tarafından sağlanan bu bilgilerle donatılmış olan denetleyici, bir istemciden URL isteği aldığında, oluşturulacak bir bean olup olmadığını ve hangisinin oluşturulacağını belirleyebilir. Oluşturulduktan sonra, bean, formdan gelen ve depoladığı verilerin geçerli olup olmadığını kontrol edebilir. Bean'in validate adlı yöntemi, denetleyici tarafından otomatik olarak çağrılır. Bean, geliştirici tarafından oluşturulur. Geliştirici, form verilerinin geçerliliğini kontrol eden kodu validate yöntemine yerleştirir. Veriler geçersiz çıkarsa, denetleyici işlemi daha ileriye götürmez. Denetleyici, yapılandırma dosyasında adını bulduğu bir görünüme kontrolü devreder. Böylece işlem tamamlanmış olur. Geliştiricinin, formun geçerliliğinin kontrol edilmemesini talep edebileceği unutulmamalıdır. Bunu da struts-config.xml dosyasında yapar. Bu durumda denetleyici, bean'in validate yöntemini çağırmaz.
  • Bean verileri doğruysa, doğrulama yapılmıyorsa veya bean yoksa, denetleyici kontrolü URL ile ilişkili Action türündeki nesneye devreder. Bunu, söz konusu nesnenin `execute` yönteminin çalıştırılmasını talep ederek yapar ve bu yönteme, gerekirse oluşturduğu bean'in referansını iletir. Geliştirici, işte bu noktada yapması gerekenleri gerçekleştirir: gerekirse iş mantığı sınıflarını veya veri erişim sınıflarını çağırması gerekebilir. İşlem bittiğinde, Action nesnesi, kontrolöre müşteriye yanıt olarak göndermesi gereken görünümün adını geri verir.
  • Kontrolör, yapılandırma dosyasında, görüntülemesi istenen görünüm adıyla ilişkili URL nesnesini bulur. Ardından bu görünümü gönderir. İstemciyle olan iletişim sona erer.

Uygulamamızda, istemci ile [Action] sınıfları arasında tampon nesne olarak [Bean] nesnelerini kullanmayacağız. [Action] nesnesi, alacağı [HttpServletRequest] nesnesinden doğrudan istemcinin istek parametrelerini alacaktır. Bu, ilk uygulamamızın taşınmasını kolaylaştırır. Dolayısıyla, uygulamamızın nihai mimarisi şu şekilde olacaktır:

M=modèle
iş sınıfları, veri erişim sınıfları ve veritabanı
V=vues
sayfalar JSP
C=contrôleur
istemci isteklerini işleyen servlet, nesneler [Action]

3.6.2. Model

Daha önce tanıtılmıştı. [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception] adlı Java arşivlerinden oluşur.

3.6.3. Uygulamanın yapılandırması

3.6.3.1. Genel mimari

Eclipse projesinin genel mimarisi şu şekildedir:

Image

3.6.3.2. Veri erişim yapılandırması

Veri erişim arayüzünde herhangi bir değişiklik olmadığı için, ilgili yapılandırma dosyaları önceki sürümdekilerle aynıdır. Bu dosyalar [WEB-INF/src] dosyasında tanımlanmıştır:

Image

Yukarıdaki ekran görüntüsünde, [articles.xml, spring-config-sqlmap-firebird.xml, sqlmap-config-firebird.xml, log4j.properties] dosyaları önceki sürüme aittir.

3.6.3.3. Arşiv dizini

[WEB-INF/lib]'te, önceki sürümdekilerle aynı arşivlerin yanı sıra Struts için gerekli olan arşiv de bulunur:

Image

3.6.3.4. Uygulamanın yapılandırılması

Uygulama, [WEB-INF] klasöründeki [web.xml, struts-config.xml] adlı iki dosya kullanılarak yapılandırılır:

Image

[web.xml] dosyası şu şekildedir:

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

Bu dosya ne ifade ediyor?

  • Uygulamanın ana sayfası [vues/index.jsp] (welcome-file) dosyasındadır.
  • *.do biçimindeki URL istekleri, [strutswebarticles] servletine yönlendirilecektir (servlet eşlemesi)
  • [strutswebarticles] servleti, [ istia.st.articles.web.struts.MainServlet] sınıfının bir örneğidir (servlet-name, servlet-class)
  • Bu servlet iki başlatma parametresi kabul eder
    • Struts yapılandırma dosyasının adı (config)
    • Spring yapılandırma dosyasının adı (springConfigFileName)

[struts-config.xml] dosyası şu şekildedir:

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

Bu yapılandırma dosyası ne diyor?

  • Kontrolörümüzün aşağıdaki URL'leri işleyeceğini:
main.do
makale listesini görüntülemek için
liste.do
makale listesini görüntülemek için
infos.do
belirli bir ürünle ilgili bilgileri görüntülemek için
achat.do
belirli bir ürünü satın almak için
panier.do
alışveriş sepetini görüntülemek için
retirerachat.do
sepetten bir ürünü çıkarmak için
validerpanier.do
alışveriş sepetini onaylamak için
  • Yukarıdaki eylemlerin her biri, önceki sürümde servlet tarafından işlenen eylemlerle bire bir eşleşmektedir. Her biri için aşağıdaki bilgiler belirtilmiştir:
  • bu eylemi işlemekten sorumlu sınıfın adı
  • eylemin işlenmesinden sonra olası yanıtlar (= görünümler). Denetleyici bunlardan yalnızca birini seçecektir.
  • uygulama için bir mesaj dosyasının adı (message-resources). Burada dosya mevcut olacak ancak boş olacaktır. Kullanılmayacaktır. Uygulamanın [ClassPath] klasörüne yerleştirilmelidir. Burada ise [WEB-INF/classes] klasörüne yerleştirilecektir. Eclipse ortamında, dosyayı [WEB-INF/src] klasörüne yerleştirerek şu sonuç elde edilir:

Image

3.6.4. JSP görünümleri

Kullanılan JSP görünümleri de önceki sürümünkilerle aynıdır. Çok az değişiklik yapılmıştır: [?action=XX?id=YY& ...] biçimindeki URL'ler, [/XX.do?id=YY&....] haline gelmiştir. Kullanıcının önceki bölüme geri dönmesini önlemek amacıyla, burada daha önce verilen açıklamaları tekrarlıyoruz. Denetleyici tarafından görünüme aktarılan bilgilerin her iki sürümde de tamamen aynı olduğunu anlamak önemlidir. Bu konuda hiçbir değişiklik yapılmamıştır.

3.6.4.1. entete.jsp

Farklı görünümler arasında bir tutarlılık sağlamak amacıyla, bu görünümler aynı başlığı paylaşacak; bu başlık, uygulamanın adını ve menüyü gösterir:

Menü dinamiktir ve denetleyici tarafından belirlenir. Denetleyici, JSP sayfasına iletilen isteğe, "actions" anahtar özniteliğini ekler; bu özniteliğin ilişkili değeri, Hastable[] türünde bir dizidir. Bu dizinin her bir elemanı, başlıktaki menü seçeneklerinden birini oluşturmaya yönelik bir sözlüktür. Her sözlük iki anahtara sahiptir:

  • href: menü seçeneğiyle ilişkili URL
  • lien: menü metni

Uygulamanın diğer görünümleri, [entete.jsp] tarafından tanımlanan başlığı, aşağıdaki JSP etiketini kullanarak kullanacaktır:

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

Çalıştırıldığında bu etiket, onu içeren JSP sayfasının koduna [entete.jsp] sayfasının kodunu ekleyecektir. Sayfanın URL'si göreceli bir URL olduğundan (/ işareti bulunmadığından), [entete.jsp] sayfası, <jsp:include> etiketini içeren sayfanın bulunduğu klasörde aranacaktır.

Kod:

<%@ 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>

Açıklamalar: önceki sürüme göre herhangi bir değişiklik yok

3.6.4.2. liste.jsp

Bu görünüm, satışa sunulan ürünlerin listesini gösterir:

Bu görünüm, /main.do veya /validerpanier.do sorgusunun ardından görüntülenir. Denetleyicinin sorgusundaki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
listarticles
ArrayList, [Article] türünde nesneler
message
String nesnesi - sayfanın altında görüntülenecek mesaj

HTML makale dizisindeki her bir [Infos] bağlantısı, [/infos.do?id=ID] biçiminde bir URL'ye sahiptir; burada ID, görüntülenen makalenin id alanıdır.

Kod:

<%@ 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>

Açıklamalar: bir değişiklik (yukarıda vurgulanmıştır)

3.6.4.3. infos.jsp

Bu sayfa, bir ürünle ilgili bilgileri gösterir ve ürünün satın alınmasına da olanak tanır:

Image

Bu sayfa, satın alınan miktar hatalı olduğunda /infos.do?id=ID veya /achat.do?id=ID sorgusunun ardından görüntülenir. Denetleyicinin isteğinin bileşenleri şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
article
[Article] türü nesne - görüntülenecek ürün
msg
String nesnesi - miktar hatası durumunda görüntülenecek mesaj
qte
String nesnesi - [Qte] giriş alanında görüntülenecek değer

[msg] ve [qte] alanları, miktar girişinde hata olması durumunda kullanılır:

Image

Bu sayfa, [Acheter] düğmesi ile gönderilen bir form içerir. POST'in hedef URL'si [/achat.do?id=ID]'tir; burada ID, satın alınan ürünün kimliğidir.

Kod:

<%@ 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>

Yorumlar: bir değişiklik (yukarıda vurgulanmıştır)

3.6.4.4. panier.jsp

Bu görünüm, sepetin içeriğini gösterir:

Image

Bu sayfa, /panier.do veya /retirerachat.do?id=ID isteğinin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
panier
[Panier] türünde nesne - görüntülenecek alışveriş sepeti

Sepetteki satın alma işlemlerinin bulunduğu HTML dizisindeki her bir [Retirer] bağlantısı, [retirerachat.do?id=ID] biçiminde bir URL'ye sahiptir; burada ID, sepetten çıkarılmak istenen ürünün [id] alanını ifade edersepetten çıkarmak istediğimiz ürünün [id] alanıdır.

Kod:

<%@ 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>

Açıklamalar: bir değişiklik (yukarıda vurgulanmıştır)

3.6.4.5. paniervide.jsp

Bu ekran, sepetin boş olduğunu belirten bilgileri gösterir:

Image

Bu, /panier.do veya /retirerachat.do?id=ID isteğinin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi

Kod:

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>

Açıklamalar: değişiklik yok.

3.6.4.6. erreurs.jsp

Bu görünüm, hata durumunda görüntülenir:

Image

Bu görünüm, [INFOS] görünümü tarafından işlenen hatalı miktarlı satın alma işlemi hariç, hataya yol açan tüm isteklerin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:

actions
Hashtable[] nesnesi - menü seçenekleri dizisi
erreurs
ArrayList - görüntülenecek hata mesajlarını temsil eden String nesneleri

Kod:

<%@ 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>

Açıklamalar: değişiklik yok.

3.6.4.7. index.jsp

Bu sayfa, uygulamanın [web.xml] dosyasında uygulamanın ana sayfası olarak tanımlanmıştır:

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

[index.jsp] görünümü, kullanıcıyı uygulamanın giriş noktasına yönlendirmekle yetinir:

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

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

Yorumlar: bir değişiklik (yukarıda vurgulanmıştır)

3.6.5. Struts denetleyicisi

Struts, [ActionServlet] adlı genel bir denetleyiciye sahiptir. Bir servletin, uygulama başlatıldığında uygulamayı başlatmaya yarayan [init] adlı bir yöntemi olduğu bilinmektedir. Struts'un genel denetleyicisi olan [ActionServlet]'i kullanırsak, [init] yöntemine erişemeyiz. Burada, uygulamanın başlatılması sırasında yapmamız gereken işler var; esas olarak modele erişim nesnesini oluşturmak. Bu nedenle [init] yöntemine ihtiyacımız var. Dolayısıyla, [Actionservlet] sınıfını aşağıdaki [MainServlet] sınıfında türetiriz:

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 {
   // özel alanlar
  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);

     // alıcılar - ayarlayıcılar
  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{

     // üst sınıfın başlatılması
    super.init();
     // servletin başlatma parametrelerini alınıyor
    config = getServletConfig();
    String param = null;
    for (int i = 0; i < parameters.length; i++) {
      param = config.getInitParameter(parameters[i]);
      if (param == null) {
         // hatayı kaydediyoruz
        erreurs.add("Paramètre [" + parameters[i]
            + "] absent dans le fichier [web.xml]");
      }
    }
     // Hata mı var?
    if (erreurs.size() != 0) {
      return;
    }
     // iş katmanına erişim için bir IArticlesDomain nesnesi oluşturulur
    try {
      articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
          new ClassPathResource((String) config
              .getInitParameter(SPRING_CONFIG_FILENAME))))
          .getBean("articlesDomain");
    } catch (Exception ex) {
       // hatayı kaydediyoruz
      erreurs.add("Erreur de configuration de l'accès aux données : "
          + ex.toString());
      return;
    }
     // uygulamanın belirli URL'leri kaydediliyor
    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);

     // işlem tamamlandı
    return;
  }
}

Açıklamalar:

  • Bu sınıfın önemi, [init] yönteminde ve özel alanlarında yatmaktadır
  • [init] yöntemi, önceki sürümdeki denetleyicideki [init] yöntemiyle aynı işlevi görür:
    • belirli yapılandırma parametrelerinin varlığını kontrol eder
    • Spring aracılığıyla uygulama alanına erişim hizmeti oluşturur
    • olası başlatma hatalarını belirtmek için bir hata listesi oluşturur
  • Çalışmaya başlamadan önce, [init] yöntemi, üst sınıf olan [ActionServlet] sınıfının [init] yöntemini çağırır. Struts yapılandırma dosyası [struts-config.xml]'i işleyen de bu yöntemdir.
  • Bir dizi özel alan ve bunlara ait erişim yöntemleri tanımlanmıştır:
    • hatalar: [init] tarafından tespit edilen hataların listesi
    • [hActionListe, hActionPanier, hActionValidationPanier]: sözlükler. Her biri, [entete.jsp] görünümü tarafından görüntülenen ana menüdeki bir seçeneği görüntülemek için gerekli bilgileri içerir
    • acticlesDomain: uygulama modeline erişim hizmeti
  • Bir Struts uygulamasının denetleyicisi, olası çeşitli eylemleri işlemekle görevli [Action] sınıfları tarafından erişilebilir durumdadır. Bu sınıflar, önceki özel alanlara erişebilir; çünkü bu alanlar genel erişim yöntemleriyle donatılmıştır.

Bu denetleyicinin örneklenmesi, [web.xml] dosyası tarafından sağlanır:

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

.do ile biten tüm URL öğeleri, [istia.st.articles.web.struts.MainServlet] sınıfının bir örneği tarafından işlenecektir

3.6.6. Struts uygulamasının eylemleri

3.6.6.1. Giriş

Her Struts eylemi bir sınıfın konusu olacaktır. Önceki sürümde, her eylem uygulama denetleyicisindeki bir yöntemin konusuydu. [Action] sınıfının yazılması çoğunlukla şunlardan oluşur:

  • önceki sürümde kullanılan yöntemi kopyalayıp yapıştırmak
  • kodu Struts kurallarına göre uyarlamak

3.6.6.2. main.do, liste.do

Bu iki eylem aynıdır ve [struts-config.xml]'te şu şekilde tanımlanmıştır:

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>

Bu [main.do, liste.do] eylemlerinden biri bir tarayıcıda çalıştırıldığında, şu sonuç elde edilir:

Image

[ListeArticlesAction] sınıfının kodu şöyledir:

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 {

     // kontrol servleti
    MainServlet mainServlet = (MainServlet) this.getServlet();

     // başlatma hataları mı?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }

     // etki alanına erişim nesnesi
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();

     // hata listesi
    ArrayList erreurs = new ArrayList();
     // ürün listesi isteniyor
    List articles = null;
    try {
      articles = articlesDomain.getAllArticles();
    } catch (UncheckedAccessArticlesException ex) {
       // hata kaydediliyor
      erreurs.add("Erreur lors de l'obtention de tous les articles : "
          + ex.toString());
    }
     // hatalar var mı?
    if (erreurs.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // ürün listesi görüntüleniyor
    request.setAttribute("listarticles", articles);
    request.setAttribute("message", "");
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionPanier() });
    return mapping.findForward("afficherListeArticles");
  }
}

Açıklamalar:

  • [Action] sınıfının kodunu yazmak, esasen [execute] yönteminin kodunu yazmaktan ibarettir
  • Denetleyici örneğinde bir dizi bilgi depolanmıştır. Bu örneğe ilişkin referans şu şekilde alınır:
     // kontrol servleti
    MainServlet mainServlet = (MainServlet) this.getServlet();
  • kontrolör tarafından depolanan başlatma hatalarının listesi alınır. Bu liste boş değilse, [ERREURS] görünümü müşteriye gönderilir:
1
2
3
4
5
6
7
8
     // başlatma hataları mı?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }

Müşteriye fiilen gönderilecek görünüm, [struts-config.xml] tarafından sağlanır:

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

Bu, [/vues/erreurs.jsp] görünümüdür. Okuyucunun, bu görünümden ne beklendiğini kontrol etmesi önerilir. Bu bilgiler, burada [request] nesnesindeki eylem tarafından öznitelikler olarak sağlanmaktadır.

  • Yine denetleyici sayesinde, eylem uygulama modeline (alan katmanı) erişim nesnesini alabilir:
     // etki alanına erişim nesnesi
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
  • Bu işlem tamamlandıktan sonra, makale listesi istenebilir:
     // hata listesi
    ArrayList erreurs = new ArrayList();
     // ürün listesi isteniyor
    List articles = null;
    try {
      articles = articlesDomain.getAllArticles();
    } catch (UncheckedAccessArticlesException ex) {
       // hata kaydediliyor
      erreurs.add("Erreur lors de l'obtention de tous les articles : "
          + ex.toString());
    }
  • Hata oluşursa, [ERREURS] görünümü gönderilir:
1
2
3
4
5
6
7
8
     // hatalar var mı?
    if (erreurs.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
  • Aksi takdirde [LISTE] görünümü gönderilir:
1
2
3
4
5
6
     // makale listesi görüntüleniyor
    request.setAttribute("listarticles", articles);
    request.setAttribute("message", "");
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionPanier() });
    return mapping.findForward("afficherListeArticles");

Müşteriye fiilen gönderilecek görünüm bize [struts-config.xml] tarafından sağlanır:

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

Bu, [/vues/liste.jsp] görünümüdür. Okuyucunun bu görünümden ne beklendiğini kontrol etmesi önerilir. Bu bilgiler, [request] nesnesindeki eylem tarafından öznitelikler olarak sağlanmaktadır.

3.6.6.3. infos.do

Bu eylem, [LISTE] görünümünde görüntülenen öğelerden biri hakkında bilgi sağlamak için kullanılır:

Bu eylem, [struts-config.xml]'te şu şekilde yapılandırılmıştır:

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

[InfosArticleAction] sınıfının kodu şöyledir:

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 {

     // kontrol servleti
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // başlatma hataları mı?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // etki alanına erişim nesnesi
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
     // hata listesi
    ArrayList erreurs = new ArrayList();
     // istenen kimlik alınır
    String strId = request.getParameter("id");
     // bir şey mi?
    if (strId == null) {
       // normal değil
      erreurs.add("action incorrecte([infos,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // strId'i tamamen dönüştürüyoruz
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // normal değil
      erreurs.add("action incorrecte([infos,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // anahtar kimliği öğesi isteniyor
    Article article = null;
    try {
      article = articlesDomain.getArticleById(id);
    } catch (UncheckedAccessArticlesException ex) {
       // normal değil
      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) {
       // normal değil
      erreurs.add("Article de clé [" + id + "] inexistant");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // ürün oturuma ekleniyor
    request.getSession().setAttribute("article", article);
     // bilgi sayfası görüntüleniyor
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionListe() });
    return mapping.findForward("afficherInfosArticle");
  }
}

Açıklamalar:

  • [execute] yönteminin başlangıcı, daha önce incelenenle aynıdır. Diğer eylemler için de durum aynı olacaktır.
  • Yöntem, normalde URL'de bulunması gereken [id] parametresini alır. Bu parametre, aslında [/infos.do?id=X] biçiminde olmalıdır. [id] parametresinin varlığını ve geçerliliğini test etmek için çeşitli testler yapılır. Bir sorun olması durumunda, [ERREURS] görünümü gönderilir.
  • [id] geçerliyse, ilgili ürün [domain] katmanından istenir. Bu katman bir istisna oluşturursa veya ürün bulunamazsa, yine [ERREURS] görünümü gönderilir.
  • Her şey yolunda giderse, elde edilen ürün oturuma eklenir. Bu tartışmaya açık bir noktadır. Burada, müşterinin bu ürünü satın alabileceği varsayımıyla hareket edilir. Eğer satın alırsa, ürünü [domain] katmanından yeniden istemek yerine oturumdan alınacaktır.
  • Son olarak, [INFOS] görünümü görüntülenir. Müşteriye gerçekten gönderilecek görünüm, [struts-config.xml] tarafından bize sağlanır:
        <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>

Bu, [/vues/infos.jsp] görünümüdür. Okuyucunun bu görünümden ne beklendiğini kontrol etmesi önerilir. Bu bilgiler, [request] nesnesindeki eylem tarafından öznitelikler olarak sağlanmaktadır.

3.6.6.4. achat.do

Bu eylem, önceki [INFOS] görünümü tarafından görüntülenen ürünü satın almak için kullanılır:

Ürün satın alındıktan sonra, [LISTE] görünümü yeniden görüntülenir (sağdaki görünüm). Yukarıdaki soldaki görünümün HTML koduna bakıldığında, <form> etiketinin şu şekilde tanımlandığı görülür:

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>

Böylece, formun [achat.do] eylemi ile denetleyiciye gönderildiğini görüyoruz.

Bu eylem, [struts-config.xml] dosyasında şu şekilde yapılandırılmıştır:

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

[AchatArticleAction] sınıfının kodu şöyledir:

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 {

     // kontrol servleti
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // başlatma hataları mı?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // bu eylemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // satın alınan miktar alınır
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // hatalı miktar
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherInfosArticle");
    }
     // müşterinin oturumu alınır
    HttpSession session = request.getSession();
     // oturuma eklenen ürün alınır
    Article article = (Article) session.getAttribute("article");
     // oturum süresi doldu mu?
    if(article==null){
       // hata sayfası görüntüleniyor
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
     // yeni satın alma işlemi oluşturulur
    Achat achat = new Achat(article, qté);
     // satın alma işlemi müşterinin sepetine eklenir
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
     // ürün listesine geri dönülüyor
    return mapping.findForward("afficherListeArticles");
  }
}

Açıklamalar:

  • [execute] yönteminin başlangıcı, daha önce incelenenlerle aynıdır.
  • Denetleyiciye gönderilen formun biçimini hatırlayalım:
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>
  • İstekte iki parametre bulunmaktadır: [id]: satın alınan ürünün numarası, [qte]: satın alınan miktar.
  • [qte] parametresinin varlığı ve geçerliliği test edilir. Bu parametre hatalı olarak algılanırsa, kullanıcıya bir hata mesajıyla birlikte [INFOS] görünümü gönderilir:
     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // satın alınan miktar alınır
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // miktar hatası
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherInfosArticle");
    }
  • Satın alınan ürün oturumdan alınır. Oturumun süresi dolmuş olabilir. Bu durumda, [ERREURS] görünümü gönderilir:
     // müşterinin oturum bilgilerini alır
    HttpSession session = request.getSession();
     // oturuma eklenen ürün alınır
    Article article = (Article) session.getAttribute("article");
     // oturum süresi doldu mu?
    if(article==null){
       // hata sayfası görüntüleniyor
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
  • Oturumun süresi dolmamışsa, ürün sepetine eklenir; bu ürün de oturumdan alınır:
     // müşterinin oturumu alınır
    HttpSession session = request.getSession();
     // oturumda bulunan ürünü alıyoruz
    Article article = (Article) session.getAttribute("article");
     // oturum süresi doldu mu?
    if(article==null){
       // hata sayfası görüntüleniyor
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
     // yeni satın alma işlemi oluşturuluyor
    Achat achat = new Achat(article, qté);
     // satın alma işlemi müşterinin sepetine ekleniyor
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
  • son olarak, [LISTE] görünümü gönderilir:
     // ürün listesine geri dönülüyor
    return mapping.findForward("afficherListeArticles");
  • Müşteriye gerçekten gönderilecek görünüm bize [struts-config.xml] tarafından sağlanır:
        <action 
            path="/achat" type="istia.st.articles.web.struts.AchatArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherListeArticles" path="/main.do"/>
        </action>

Bu, [/main.do] görünümüdür. Bu bir görünüm değil, bir eylemdir. Dolayısıyla, daha önce açıklanan [/main.do] eylemi yürütülecek ve ürün listesi görüntülenecektir.

3.6.6.5. panier.do

Bu eylem, müşterinin tüm satın alımlarını görüntülemek için kullanılır. Bu eylem, [Voir le panier] menü seçeneği aracılığıyla kullanılabilir:

[Voir le panier] bağlantısıyla ilişkili HTML kodu şöyledir:

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

[panier.do] eylemi, [struts-config.xml] içinde şu şekilde yapılandırılmıştır:

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

[VoirPanierAction] sınıfının kodu şöyledir:

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 {

     // kontrol servleti
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // başlatma hataları mı?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // hata sayfası görüntülenir
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // sepet görüntüleniyor
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null || panier.getAchats().size() == 0) {
       // sepet boş
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherPanierVide");
    } else {
       // sepet içinde bir şey var
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(), mainServlet.getHActionValidationPanier() });
      return mapping.findForward("afficherPanier");
    }
  }
}

Açıklamalar:

  • [execute] yönteminin başlangıcı, daha önce incelenenlerle aynıdır.
  • Alışveriş sepeti, normalde bulunduğu oturumdan alınır. Oturumun süresi dolmuş olabilir ve bu durumda alışveriş sepeti olmaz. Bu durum bir hata olarak değerlendirilmez, sadece alışveriş sepetinin boş olduğu kabul edilir.
  • Alışveriş sepeti boşsa, [PANIERVIDE] görünümü görüntülenir
  • Aksi takdirde, [PANIER] görünümü görüntülenir

Müşteriye gerçekten gönderilen görünümler şu eylemle tanımlanır:

        <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

Bu eylem, sepetten bir ürünü çıkarmak için kullanılır:

[retirerachat.do] eyleminin ardından, sepet yeniden görüntülenir (yukarıdaki sağdaki görünüm). Yukarıdaki soldaki görünümdeki [Valider le panier] bağlantısının HTML koduna bakıldığında, şu sonuç ortaya çıkar:

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

Dolayısıyla [retirerachat.do] eylemi, sepetten çıkarılacak ürünün kimliğini parametre olarak alır. Bu eylem, [struts-config.xml] içinde şu şekilde yapılandırılmıştır:

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

[RetirerAchatAction] sınıfının kodu şöyledir:

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 {

     // kontrol servleti
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // başlatma hataları mı?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // sepeti alınıyor
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // oturum süresi doldu
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherPanierVide");
    }
     // çıkarılacak ürünün kimliği alınır
    String strId = request.getParameter("id");
     // bir şey mi?
    if (strId == null) {
       // normal değil
      erreurs.add("action incorrecte([retirerachat,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // strId'i tam olarak dönüştürüyoruz
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // normal değil
      erreurs.add("action incorrecte([retirerachat,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // satın alma işlemi iptal ediliyor
    panier.enlever(id);
     // sepet yeniden görüntüleniyor
    return mapping.findForward("afficherPanier");
  }
}

Açıklamalar:

  • [execute] yönteminin başlangıcı, daha önce incelenenlerle aynıdır.
  • Aşağıdaki kod, [id] parametresinin varlığını ve geçerliliğini kontrol etmeyi amaçlamaktadır. Eğer parametre hatalıysa, [ERREURS] görünümü gönderilir.
  • Aksi takdirde, satın alma işlemi sepetten kaldırılır:
     // satın alma işlemi kaldırılıyor
    panier.enlever(id);
  • ardından sepet yeniden görüntülenir:
     // sepeti yeniden görüntüleniyor
    return mapping.findForward("afficherPanier");

Müşteriye gönderilen gerçek görünüm, şu eylemle belirlenir:

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

Görüldüğü üzere, görünüm açısından [/panier.do] eylemi tetiklenecektir. Bu eylem daha önce açıklanmıştır. Sepetin durumuna göre [PANIER] veya [PANIERVIDE] görünümünü görüntüleyecektir.

3.6.6.7. validerpanier.do

Bu eylem, müşterinin yaptığı alışverişleri onaylamak için kullanılır. Somut olarak bu, tek bir eylemle gerçekleşir: Satın alınan ürünlerin stokları, veritabanında satın alınan miktarlar kadar azaltılır. Bu eylem aşağıdaki menüden gelir:

 

[Valider le panier] bağlantısının HTML kodu şöyledir:

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

Bu bağlantı etkinleştirildiğinde, stoklar azaltılır ve ürün listesi yeniden görüntülenir.

Bu işlem, [struts-config.xml] dosyasında şu şekilde yapılandırılmıştır:

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

[ValiderPanierAction] sınıfının kodu şöyledir:

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 {

     // kontrol servleti
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // başlatma hataları mı?
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // hata sayfası görüntüleniyor
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // etki alanına erişim nesnesi
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
     // bu eylemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
         // Alıcı sepetini onayladı
        Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // oturum süresi doldu
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
     request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
         // sepet onaylanıyor
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // normal değil
            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");
        }
         // Olası hatalar alınıyor
         erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(),mainServlet.getHActionPanier() });
      return mapping.findForward("afficherErreurs");
        }
         // her şey normal görünüyor OK - ürün listesi görüntüleniyor
    return mapping.findForward("afficherListeArticles");
  }
}

Açıklamalar:

  • [execute] yönteminin başlangıcı, daha önce incelenenlerle aynıdır.
  • Alışveriş sepeti oturumdan alınır. Oturumun süresi dolmuşsa, [ERREURS] görünümü gönderilir:
     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
         // Alıcı sepetini onayladı
        Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // oturum süresi doldu
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
     request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
  • Sepetteki satın alımlar onaylanır. Stoklar, satın alımları karşılamaya yetmezse hatalar meydana gelebilir. Bu durumda, [ERREURS] görünümü gönderilir:
         // sepet onaylanıyor
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // anormal durum
            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");
        }
         // Olası hatalar alınıyor
         erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(),mainServlet.getHActionPanier() });
      return mapping.findForward("afficherErreurs");
        }
  • Her şey yolunda giderse, ürün listesi yeniden görüntülenir:
         // her şey normal görünüyor OK - ürün listesi görüntüleniyor
    return mapping.findForward("afficherListeArticles");

Müşteriye gerçekten gönderilen görünüm, şu eylemle tanımlanır:

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

Görüldüğü gibi, görünüm açısından [/main.do] eylemi tetiklenecektir. Bu eylem daha önce açıklanmıştır. Bu eylem, [LISTE] görünümünü görüntüleyecektir.

3.7. Spring ile MVC mimarisi

3.7.1. Uygulamanın genel mimarisi

Uygulamanın MVC mimarisine geri dönelim:

İlk sürümde:

  • denetim bir servlet tarafından sağlanıyordu
  • görünümler JSP sayfaları tarafından sağlanıyordu
  • model, üç .jar dosyasından oluşan bir set tarafından sağlanıyordu

Struts sürümünde:

  • denetleyici, Struts'un genel [ActionServlet] servletinden türetilmiş bir servlet tarafından sağlanıyordu
  • görünümler, [Struts] sürümündekilerle aynı JSP sayfaları tarafından sağlanıyordu
  • Model, aynı üç arşiv tarafından sağlanıyordu

Spring sürümünde:

  • denetleyici, Spring tarafından sağlanan [DispatcherServlet] servlet'i tarafından sağlanacaktır
  • görünümler, birkaç küçük ayrıntı dışında, daha öncekiyle aynı JSP sayfaları tarafından sağlanacaktır
  • model, yine aynı üç arşiv tarafından sağlanacaktır

Uygulamamızı Struts'tan Spring'e geçirmek, ortodoks bir Spring mimarisi için önerilen tüm unsurları kullanmamayı kabul edersek oldukça basit olduğunu göreceğiz. MVC Başlıca değişiklikler şunlardır:

  • Struts'ta servlet/denetleyicinin belirli yöntemlerinde veya [Action] sınıfından türetilen sınıfların örnekleri tarafından işlenen eylemler, artık Spring [Controller] arayüzünü uygulayan sınıf örnekleri tarafından işlenmektedir
  • Gerekli yapılandırma dosyaları şunlardır:
    • [web.xml], çünkü bir web uygulamasıyla uğraşıyoruz. Bu dosya, uygulamanın başlatılması sırasında [applicationContext.xml] dosyasını kullanacak bir Listener içerir
    • [applicationContext.xml] dosyasını işleyecek ve bu da uygulamanın ihtiyaç duyduğu bean'leri, özellikle de modele erişim hizmetine ait olanı oluşturmaya olanak sağlayacaktır
  • JSP görünümleri Struts'takilerle aynı olacaktır. Yeni bir tane oluşturmamız gerekecektir.

Önceki sürümde kullanılan MVC STRUTS mimarisini hatırlayalım:

M=modèle
iş sınıfları, veri erişim sınıfları ve veritabanı
V=vues
sayfalar JSP
C=contrôleur
istemci isteklerini işleyen servlet, nesneler [Action]

Spring ile de aynı mimariyi kullanıyoruz:

M=modèle
iş işlev sınıfları, veri erişim sınıfları ve veritabanı
V=vues
sayfalar JSP
C=contrôleur
istemci isteklerini işleyen servlet, [Controller] arayüzünü uygulayan nesneler
  • denetleyici, uygulamanın kalbidir. Tüm istemci istekleri buradan geçer. Bu, SPRING tarafından sağlanan genel bir servlettir. Türü [DispatcherServlet]'tir. Bundan sonra bu denetleyiciye [Spring] denetleyicisi diyeceğiz.
  • [Spring] denetleyicisi, istemcinin isteğini [Controller] türündeki örneklerden birine yönlendirecektir. İşlenecek her eylem için böyle bir örnek olacaktır. Bu örnek, Struts'ta olduğu gibi istenen URL'de tanımlanır. Böylece, istenen URL [liste.do] olduğu için istenen eylemin [liste] eylemi olduğu anlaşılır
  • C, uygulamanın bağlamı ise, [Spring] denetleyicisi, Struts sürümünde struts-config.xml yapılandırma dosyasının rolünü üstlenen [C-servlet.xml] dosyasını kullanır. Uygulama tarafından işlenmesi gereken her eyleme, isteği işlemekle görevli Controller türündeki sınıfın adı atanır.
  • Denetleyici, eyleme bağlı Controller türündeki nesneye kontrolü devreder. Bunu, söz konusu nesnenin handleRequest yönteminin yürütülmesini talep ederek ve bu nesneye müşterinin isteğini ileterek gerçekleştirir. Geliştirici, burada yapması gerekeni yapar: Gerekirse iş sınıflarına veya veri erişim sınıflarına başvurması gerekebilir. İşlem tamamlandığında, Controller nesnesi, müşteriye yanıt olarak göndermesi gereken görünümün adını denetleyiciye geri verir.
  • Kontrolör, yapılandırma dosyasında, görüntülemesi istenen görünüm adıyla ilişkili URL'i bulur. Ardından bu görünümü gönderir. Müşteri ile olan iletişim sona erer.

3.7.2. Model

Önceki iki sürümdekiyle aynıdır. [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception] adlı Java arşivlerinden oluşur.

3.7.3. Uygulamanın yapılandırması

3.7.3.1. Genel mimari

Eclipse projesinin genel mimarisi şöyledir:

Image

3.7.3.2. Veri erişim yapılandırması

Veri erişim arayüzünde herhangi bir değişiklik olmadığı için, ilgili yapılandırma dosyaları önceki sürümdekilerle aynıdır. Bu dosyalar [WEB-INF/src] dosyasında tanımlanmıştır:

Image

Yukarıdaki ekran görüntüsünde, [articles.xml, spring-config-sqlmap-firebird.xml, sqlmap-config-firebird.xml, log4j.properties] dosyaları önceki sürümlere aittir.

3.7.3.3. Arşiv dizini

[WEB-INF/lib]'te, artık gerekli olmayan Struts arşivleri hariç, önceki sürümdekilerle aynı arşivler bulunur:

Image

3.7.3.4. Uygulamanın yapılandırılması

Uygulama, [WEB-INF] klasöründeki [web.xml, applicationContext.xml, springwebarticles-servlet.xml] dosyası dahil olmak üzere üç dosya kullanılarak yapılandırılır:

Image

[web.xml] dosyası şu şekildedir:

<?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>
     <!-- uygulamanın Spring bağlam yükleyicisi -->
    <listener>
        <listener-class> 
            org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     <!-- servlet -->
    <servlet>
        <servlet-name>springwebarticles</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
     <!-- URL eşlemesi -->
    <servlet-mapping>
        <servlet-name>springwebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
     <!-- giriş belgesi -->
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Bu dosya ne içeriyor?

  • Uygulamanın ana sayfası [/vues/index.jsp]'tir (welcome-file)
  • *.do biçimindeki URL istekleri, [springwebarticles] servletine yönlendirilecektir (servlet-mapping)
  • [springwebarticles] servleti, Spring tarafından sağlanan [org.springframework.web.servlet.DispatcherServlet] sınıfının bir örneğidir (servlet-name, servlet-class).
  • [org.springframework.web.context.ContextLoaderListener] dinleyicisi, uygulamanın başlatılmasıyla birlikte çalıştırılacaktır. Ana görevi, [applicationContext.xml] dosyasında tanımlanan Spring bean'lerini oluşturmaktır

[applicationContext.xml] dosyası şu şekildedir:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- iş mantığı sınıfı -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!-- web uygulaması yapılandırması-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>

Burada hem tanıdık hem de daha az tanıdık unsurlar bulunmaktadır. Uygulamanın başlatılması sırasında üç bean örneklenecektir:

  • articlesDao: [dao] katmanına erişim hizmeti
  • articlesDomain: modele erişim hizmeti
  • config: tüm istemcilerin paylaşması gereken bilgileri bir araya getireceğimiz bir bean. Bu bean, geleneksel olarak uygulama bağlamının üstlendiği rolü üstlenecek, ancak tipli bilgilerle çalışacaktır.

Son dosya olan [springwebarticles.xml], uygulama tarafından kabul edilen eylemleri, Struts dosyası [struts-config.xml]'te kullanılan yönteme çok benzer bir şekilde tanımlar. İçeriği şöyledir:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- eylem yöneticileri = denetleyiciler -->
    <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>

     <!-- uygulama eşlemesi-->
    <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>

     <!-- görünüm yöneticisi -->
    <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>

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

Bu yapılandırma dosyası ne diyor?

  • Denetleyicimizin aşağıdaki URL'leri işleyeceğini belirtir:
main.do
makale listesini görüntülemek için
liste.do
makale listesini görüntülemek için
infos.do
belirli bir ürünle ilgili bilgileri görüntülemek için
achat.do
belirli bir ürünü satın almak için
panier.do
alışveriş sepetini görüntülemek için
retirerachat.do
sepetten bir ürünü çıkarmak için
validerpanier.do
alışveriş sepetini onaylamak için
  • Yukarıdaki eylemlerin her biri, önceki sürümlerde denetleyici tarafından işlenen eylemlerle bire bir eşleşmektedir. Her bir eylem için, onu işlemekten sorumlu sınıfın adı belirtilmiştir. [/panier.do] eylemini örnek olarak alalım:
  • bu eylem, [VoirPanierController] bean'i tarafından işlenmelidir. Bu isim serbesttir. Bu sadece bir anahtardır.
                <prop key="/panier.do">VoirPanierController</prop>
  • [VoirPanierController] anahtarı, aynı yapılandırma dosyasında tanımlanmış bir bean'in adıdır:
    <bean id="VoirPanierController" 
        class="istia.st.articles.web.spring.VoirPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
  • [VoirPanierController] bean'i şunları tanımlar:
  • eylemi işlemek için oluşturulacak [ istia.st.articles.web.spring.VoirPanierController] sınıfını
  • nasıl örnekleneceğini. Burada, [applicationContext.xml] tarafından tanımlanan ve uygulamanın başlatılması sırasında örneklenen [config] bean'i parametre olarak sağlanır. Bu işlem, tüm [Controller] eylemleri için yapılacaktır. Böylece her bir eylem, özel bir alanda [config] nesnesine sahip olacak ve bu nesnede tüm müşteriler arasında paylaşılan tüm bilgileri bulabilecektir.
  • Görünüm adlarının nasıl çözümlenmesi gerektiği:
     <!-- görünüm yöneticisi -->
    <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>

Struts'ta olduğu gibi, bir eylemi işleyen [Controller] örneği, işleme tamamlandıktan sonra Spring denetleyicisine hangi görünümü görüntülemesi gerektiğini belirtmek üzere bir anahtar döndürür. Bu anahtara dayanarak, anahtarla ilişkili görünümü oluşturmak için çeşitli stratejiler uygulanabilir. Seçilen strateji, [viewResolver] bean'i tarafından tanımlanan stratejidir. Burada bu bean, farklı başlatma parametreleriyle [org.springframework.web.servlet.view.InternalResourceViewResolver] sınıfına atanmıştır. Ayrıntılara girmeden özetlemek gerekirse, [viewResolver] bean'i burada, görünüm anahtarı "XX" ise, oluşturulan görünümün [/vues/XX.jsp] olacağını belirtir. İstemciye gönderilen görünümlerin türü çeşitli şekillerde değiştirilebilir:

  • [viewResolver] bean'i için uygulama sınıfını değiştirerek
  • uygulama sınıfının başlatma parametrelerini değiştirerek

Böylece, sadece [viewResolver] bean'inin değerini değiştirerek HTML görünümünden XML görünümüne geçilebilir

  • uygulama için bir mesaj dosyasının adı (messageSource). Burada dosya mevcut olacak ancak boş olacaktır. Bu dosya kullanılmayacaktır. Dosya, uygulamanın [ClassPath] konumuna yerleştirilmelidir. Burada ise [WEB-INF/classes] konumuna yerleştirilecektir. Eclipse ortamında, dosyayı [WEB-INF/src] konumuna yerleştirerek şu sonuca ulaşılır:

Image

3.7.4. JSP görünümleri

Kullanılacak JSP görünümleri, Struts tarafından kullanılanlarla aynı olacaktır. Hiçbiri değiştirilmez:

Image

Tek bir yeni görünüm oluşturulur: redirpanier.jsp. Bu görünüm, yalnızca istemciyi [/panier.do] eylemine yönlendirmek için kullanılır. Kod şöyledir:

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

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

Okuyucunun, Struts sürümündeki farklı görünümlerin tanımlarını tekrar gözden geçirmesi önerilir.

3.7.5. Eylemlerin işlenmesi

Çeşitli eylemlerin işlenmesi için gerekli sınıflar, [istia.st.articles.web.spring] paketinde bir araya getirilmiştir:

Image

Bir örnek üzerinden Spring uygulamasının işleyişini hatırlayalım:

  • kullanıcı [http://localhost:8080/springwebarticles/main.do] URL'sini ister

Image

Ne oldu?

  • [springwebarticles] uygulamasındaki [web.xml] dosyası görüntülendi:
<?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>
     <!-- uygulamanın Spring bağlam yükleyicisi -->
    <listener>
        <listener-class> 
            org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     <!-- servlet -->
    <servlet>
        <servlet-name>springwebarticles</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
     <!-- URL eşlemesi -->
    <servlet-mapping>
        <servlet-name>springwebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
     <!-- giriş belgesi -->
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
  • Bu, uygulamaya yapılan ilk istekse, bir dizi işlem tetiklendi:
    • [ org.springframework.web.context.ContextLoaderListener] dinleyicisi yüklendi
    • [applicationContext.xml] yapılandırma dosyasını işledi:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- iş sınıfı -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!-- web uygulaması yapılandırması-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>
  • Yukarıdaki bean'ler uygulama bağlamında oluşturuldu
  • ardından [springwebarticles-servlet.xml] dosyası işlendi:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- eylem yöneticileri = denetleyiciler -->
    <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>

     <!-- uygulama eşlemesi-->
    <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>

     <!-- görünüm yöneticisi -->
    <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>

     <!-- mesaj dosyası -->
    <bean id="messageSource" 
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename">
            <value>messages</value>
        </property>
    </bean>
</beans>
  • Bu dosya tarafından tanımlanan [Controller] bean'leri de oluşturuldu
  • Artık istemcinin isteğini işlemek için her şey hazırdır. İstek şuydu: [http://localhost:8080/springwebarticles]. Burada, bağlamdan bir URL değil, bağlamın kendisi istenmektedir. Bu durumda, [web.xml] dosyasının [welcome-file] bölümü kullanılır.
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
  • [index.jsp] görünümü şu şekildedir:
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/main.do"/>
  • Böylece istemci, [http://localhost:8080/springwebarticles/main.do] URL'sine yönlendirilmeye davet edilir. İstemci bunu yapar.
  • Böylece Spring denetleyicisi yeni bir istek alır. [springwebarticles-servlet.xml] dosyasını kullanır:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- eylem yöneticileri = denetleyiciler -->
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
....
    </bean>

     <!-- uygulama eşlemesi-->
    <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>
  • Bu dosya, [/main.do] eyleminin [ListController] bean'i tarafından işlenmesi gerektiğini belirtir.
  • İstemcinin isteği, [ListController] bean'inin [handleRequest] yöntemine iletilir. Bu yöntem işini yapar ve görüntülenecek görünümün anahtarını denetleyiciye döndürür. Burada, her şey yolunda giderse, bu anahtar [liste] olacaktır.
  • Spring denetleyicisi, bu anahtara ait görünümü belirlemek için [springwebarticles-servlet.xml] yapılandırma dosyasındaki [viewResolver] bean'ini kullanır. Burada bu, [/vues/liste.jsp] görünümü olacaktır
  • [/vues/liste.jsp] görünümü istemciye gönderilir

3.7.6. Spring uygulamasının başlatılması

Uygulamanın başlatılması sırasında [applicationContext.xml] dosyasındaki bean'lerin örneklendiğini belirtmiştik:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- iş sınıfı -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!-- web uygulaması yapılandırması-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>

[articlesDao, articlesDomain] bean'lerini tanıyoruz, ancak [config] bean'ini tanımıyoruz. Bu bean, aşağıdaki Java sınıfı tarafından tanımlanmıştır:

package istia.st.articles.web.spring;

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

/**
 * @author ST - ISTIA
 */

public class Config {

     // özel alanlar
    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);

     // alıcı-ayarlayıcılar
    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;
    }

     // web uygulamasının başlatılması
    public void init() {
         // uygulamanın belirli URL'lerini kaydediyoruz
        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);

         // bitti
        return;
    }
}

Bu sınıf, bir web uygulaması servlet'indeki [init] yönteminin yaptığını yapar. Uygulamayı başlatır. Burada bu işlem şu şekilde gerçekleşir:

  • çünkü [config] bean'i, [applicationContext.xml] içinde şu şekilde tanımlanmıştır:
     <!-- web uygulamasının yapılandırılması-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>

oluşturulduğunda, özel alanı [articlesDomain] başlatılmıştır

  • ardından, yukarıdaki bean'in [init-method="init"] özniteliği nedeniyle, bean ile ilişkili sınıfın [init] yöntemi yürütülür. Burada, kullanıcıya sunulan menüdeki üç olası bağlantıyı oluşturmak için kullanılan üç [hActionListe, hActionPanier, hActionValidationPanier] sözlüğü başlatılır.
  • Eylemleri işleyecek olan [Controller] türündeki örneklerin bu özel alanlara erişebilmesi için genel erişim yöntemleri oluşturulur.

3.7.7. Spring uygulamasının [Controller] eylemleri

3.7.7.1. Giriş

Her Spring eylemi, [Controller] türünde bir sınıfa konu olacaktır. Struts sürümünde ise her eylem, [Action] türünde bir sınıfa konu oluyordu. [Controller] sınıfının yazılması çoğunlukla şunlardan oluşur:

  • Struts sürümünde kullanılan [Action] sınıfını kopyalayıp yapıştırmak
  • kodu Spring kurallarına uyarlamak

3.7.7.2. main.do, liste.do

Bu iki işlem aynıdır ve [springwebarticles-servlet.xml]'te şu şekilde tanımlanmıştır:

     <!-- uygulamanın eşlemesi-->
    <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>

Bu eylemler, birazdan ayrıntılı olarak ele alacağımız [ istia.st.articles.web.spring.ListController] sınıfıyla ilişkilidir. Bu eylemlerden biri bir tarayıcıda yürütüldüğünde, şu sonuç elde edilir:

Image

[istia.st.articles.web.spring.ListController] sınıfının kodu şöyledir:

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 {

     // web uygulamasının yapılandırması
    Config config;

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

     // isteğin işlenmesi
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

         // ürün listesi isteniyor
        List articles = null;
        try {
            articles = config.getArticlesDomain().getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // hatanın kaydedilmesi
            ArrayList erreurs = new ArrayList();
            erreurs.add("Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
             // hata sayfası görüntüleniyor
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { config
                    .getHActionListe() });
             // hata görünümünü gönder
            return new ModelAndView("erreurs");
        }
         // ürün listesi görüntüleniyor
        request.setAttribute("listarticles", articles);
        request.setAttribute("message", "");
        request.setAttribute("actions", new Hashtable[] { config
                .getHActionPanier() });
         // görünümü gönder
        return new ModelAndView("liste");
    }

}

Açıklamalar:

  • Sınıfın [config] adlı özel bir alanı vardır. Bu alan, [ListController] bean'i örneklendirildiğinde Spring tarafından başlatılmıştır:
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

Yukarıda görüldüğü gibi, [ListController]'in [config] alanı, [config] bean'i ile başlatılmıştır. Bu ne anlama geliyor? Bu, [applicationContext.xml] ve c.a.d içinde tanımlanan [config] bean'i ile, yukarıda açıklanan [istia.st.articles.web.spring.Config]'in bir örneğidir.

  • [Controller] sınıfının kodunu yazmak, esasen [handleRequest] yönteminin kodunu yazmaktan ibarettir
  • Modele makale listesi istenir. Bu modele [config.getArticlesDomain()] aracılığıyla erişilebilir. Bir istisna meydana gelirse, [ERREURS] görünümü gönderilir. [handleRequest] tarafından döndürülen sonuç, [ModelAndView] türünde olmalıdır. Bu sınıfın örneği çeşitli şekillerde oluşturulabilir. Burada, ve her zaman olduğu gibi, görüntülenecek görünümün anahtarını geçirerek [ModelAndView] sınıfının bir örneği oluşturulur. Hatırlatmak gerekirse, [viewResolver] bean'inin yapılandırmasına göre, XX anahtarına ait görünümün görüntülenmesi istendiğinde, [/vues/XX.jsp] görünümü gönderilecektir.
         // ürün listesi isteniyor
        List articles = null;
        try {
            articles = config.getArticlesDomain().getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // hata kaydediliyor
            ArrayList erreurs = new ArrayList();
            erreurs.add("Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
             // hata sayfası görüntüleniyor
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { config
                    .getHActionListe() });
             // hata görünümünü gönder
            return new ModelAndView("erreurs");
        }
  • Hata yoksa, [LISTE] görünümü gönderilir:
1
2
3
4
5
6
7
         // ürün listesi görüntüleniyor
        request.setAttribute("listarticles", articles);
        request.setAttribute("message", "");
        request.setAttribute("actions", new Hashtable[] { config
                .getHActionPanier() });
         // görünümü gönder
        return new ModelAndView("liste");

3.7.7.3. infos.do

Bu eylem, [LISTE] görünümünde görüntülenen öğelerden biri hakkında bilgi sağlamak için kullanılır:

Bu eylem, [springwebarticles-servlet.xml]'te şu şekilde tanımlanmıştır:

     <!-- uygulama eşlemesi-->
    <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>

[InfosController] sınıfının kodu şöyledir:

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 {

     // web uygulamasının yapılandırması
    Config config;

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

     // isteğin işlenmesi
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

     // hata listesi
    ArrayList erreurs = new ArrayList();
     // istenen kimliği alıyoruz
    String strId = request.getParameter("id");
     // bir şey mi?
    if (strId == null) {
       // normal değil
      erreurs.add("action incorrecte([infos,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     // strId'i tamamen dönüştürüyoruz
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // normal değil
      erreurs.add("action incorrecte([infos,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     // anahtar kimliği öğesi isteniyor
    Article article = null;
    try {
      article = config.getArticlesDomain().getArticleById(id);
    } catch (UncheckedAccessArticlesException ex) {
       // normal değil
      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) {
       // normal değil
      erreurs.add("Article de clé [" + id + "] inexistant");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     // ürün oturuma ekleniyor
    request.getSession().setAttribute("article", article);
     // bilgi sayfası görüntüleniyor
    request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
        return new ModelAndView("infos");
    }
}

Açıklamalar:

  • [handleRequest] yöntemi, normalde URL'de bulunması gereken [id] parametresini alır. Bu parametre, aslında [/infos.do?id=X] biçiminde olmalıdır. [id] parametresinin varlığını ve geçerliliğini test etmek için çeşitli testler yapılır. Bir sorun olması durumunda, [ERREURS] görünümü gönderilir.
  • [id] geçerliyse, ilgili ürün [domain] katmanından istenir. Bu katman bir istisna oluşturursa veya ürün bulunamazsa, yine [ERREURS] görünümü gönderilir.
  • Her şey yolunda giderse, elde edilen ürün oturuma eklenir. Bu tartışmaya açık bir noktadır. Burada, müşterinin bu ürünü satın alabileceği varsayımıyla hareket edilir. Eğer satın alırsa, ürünü [domain] katmanından yeniden istemek yerine oturumdan alınır.
  • Son olarak, [INFOS] görünümü görüntülenir.

3.7.7.4. achat.do

Bu eylem, önceki [INFOS] görünümü tarafından görüntülenen ürünü satın almak için kullanılır:

Image

Bu görünümün HTML koduna bakıldığında, <form> etiketinin şu şekilde tanımlandığı görülür:

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

Görüldüğü gibi, form [achat.do] eylemi ile denetleyiciye gönderiliyor.

Bu eylem, [springwebarticles-servlet.xml] içinde şu şekilde yapılandırılmıştır:

     <!-- uygulamanın eşlemesi-->
    <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>

[AchatController] sınıfının kodu şöyledir:

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 {

   // web uygulamasının yapılandırması
  Config config;

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

   // isteğin işlenmesi
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // satın alınan miktar alınır
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // hatalı miktar
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("infos");
    }
     // müşterinin oturumu alınır
    HttpSession session = request.getSession();
     // oturuma eklenen ürün alınır
    Article article = (Article) session.getAttribute("article");
     // oturum süresi doldu mu?
    if (article == null) {
       // hata sayfası görüntüleniyor
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // yeni satın alma işlemi oluşturuluyor
    Achat achat = new Achat(article, qté);
     // satın alma işlemi müşterinin sepetine eklenir
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
     // ürün listesine geri dönülüyor
    return new ModelAndView("index");
  }
}

Açıklamalar:

  • Denetleyiciye gönderilen formun biçimini hatırlayalım:
        <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>
  • İstekte iki parametre bulunmaktadır: [id]: satın alınan ürün numarası, [qte]: satın alınan miktar.
  • [qte] parametresinin varlığı ve geçerliliği kontrol edilir. Bu parametre hatalı olarak algılanırsa, kullanıcıya bir hata mesajıyla birlikte [INFOS] görünümü döndürülür:
     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // satın alınan miktar alınır
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // miktar hatası
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("infos");
    }
  • Satın alınan ürün oturumdan alınır. Oturumun süresi dolmuş olabilir. Bu durumda, [ERREURS] görünümü gönderilir:
     // müşterinin oturum bilgilerini alır
    HttpSession session = request.getSession();
     // oturuma eklenen ürün alınır
    Article article = (Article) session.getAttribute("article");
     // oturum süresi doldu mu?
    if (article == null) {
       // hata sayfası görüntüleniyor
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
  • Oturumun süresi dolmamışsa, ürün sepetine eklenir; bu ürün de oturumdan alınır:
1
2
3
4
5
6
7
8
9
     // yeni satın alma işlemi oluşturuluyor
    Achat achat = new Achat(article, qté);
     // satın alma işlemini müşterinin sepetine eklenir
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
  • son olarak, [LISTE] görünümü gönderilir:
     // ürün listesine geri dönülüyor
    return new ModelAndView("index");
  • Yukarıda, [/vues/index.jsp] görünümü gönderilir. Bu görünümün, istemci tarayıcısından [/main.do] URL'sine yönlendirilmesini istediği bilinmektedir. Ürün listesini görüntüleyecek olan bu yönlendirmedir.

3.7.7.5. panier.do

Bu eylem, müşterinin tüm satın alımlarını görüntülemek için kullanılır. Aşağıdaki menüden erişilebilir:

Image

Yukarıdaki bağlantıyla ilişkili HTML kodu şöyledir:

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

Bu bağlantı tarafından yönlendirilen sayfa şöyledir:

Image

Bu eylem, [springwebarticles-servlet.xml]'te şu şekilde yapılandırılmıştır:

     <!-- uygulamanın eşlemesi-->
    <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>

[VoirPanierController] sınıfının kodu şöyledir:

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 {

   // web uygulamasının yapılandırılması
  Config config;

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

   // isteğin işlenmesi
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // sepet görüntüleniyor
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null || panier.getAchats().size() == 0) {
       // sepet boş
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("paniervide");
    } else {
       // sepet içinde bir şey var
      request.setAttribute("actions", new Hashtable[] {
          config.getHActionListe(), config.getHActionValidationPanier() });
      return new ModelAndView("panier");
    }
  }
}

Açıklamalar:

  • Alışveriş sepeti, normalde bulunduğu oturumdan alınır. Oturumun süresi dolmuş olabilir ve bu durumda alışveriş sepeti olmaz. Bu durum bir hata olarak değerlendirilmez, sadece alışveriş sepetinin boş olduğu kabul edilir.
  • Alışveriş sepeti boşsa, [PANIERVIDE] görünümü görüntülenir
  • aksi takdirde, [PANIER] görünümü görüntülenir

3.7.7.6. retirerachat.do

Bu eylem, sepetten bir ürünü çıkarmak için kullanılır:

Image

Yukarıdaki bağlantıdaki HTML koduna bakıldığında şunlar görülür:

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

Dolayısıyla [retirerachat.do] eylemi, sepetten çıkarılacak ürünün kimliğini parametre olarak alır. Bu eylem, [springwebarticles-servlet.xml] içinde şu şekilde yapılandırılmıştır:

     <!-- uygulamanın eşlemesi-->
    <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>

[RetirerAchatController] sınıfının kodu şöyledir:

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 {

   // web uygulamasının yapılandırması
  Config config;

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

   // isteğin işlenmesi
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // sepeti alıyoruz
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // oturum süresi doldu
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // çıkarılacak ürünün kimliği alınıyor
    String strId = request.getParameter("id");
     // bir şey mi?
    if (strId == null) {
       // normal değil
      erreurs.add("action incorrecte([retirerachat,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // strId'i tam olarak dönüştürüyoruz
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // normal değil
      erreurs.add("action incorrecte([retirerachat,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // satın alma işlemini iptal ediyoruz
    panier.enlever(id);
     // sepet yeniden görüntüleniyor
    request.setAttribute("actions",
        new Hashtable[] { config.getHActionListe() });
    return new ModelAndView("redirpanier");
  }
}

Açıklamalar:

  • Kod, [id] parametresinin varlığını ve geçerliliğini kontrol eder. Parametre yanlışsa, [ERREURS] görünümü gönderilir.
  • Aksi takdirde, satın alma işlemi sepetten kaldırılır:
     // satın alma işlemi kaldırılıyor
    panier.enlever(id);
  • ardından sepet yeniden görüntülenir:
     // sepet yeniden görüntüleniyor
    request.setAttribute("actions",
        new Hashtable[] { config.getHActionListe() });
    return new ModelAndView("redirpanier");

[/vues/redirpanier.jsp] görünümünün kodunu hatırlayalım:

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

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

Görüldüğü gibi, müşteri [/panier.do] eylemine yönlendirilecektir. Bu eylem daha önce açıklanmıştı. Sepetin durumuna göre [PANIER] veya [PANIERVIDE] görünümünü görüntüleyecektir.

3.7.7.7. validerpanier.do

Bu eylem, müşterinin yaptığı alışverişleri onaylamak için kullanılır. Somut olarak bu, tek bir eylemle gerçekleşir: Satın alınan ürünlerin stokları, veritabanında satın alınan miktarlar kadar azaltılır. Bu eylem aşağıdaki menüden gelir:

Image

[Valider le panier] bağlantısının HTML kodu şöyledir:

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

Bu bağlantı etkinleştirildiğinde, stoklar azaltılır ve ürün listesi yeniden görüntülenir.

Bu eylem, [springwebarticles-servlet.xml]'te şu şekilde yapılandırılmıştır:

     <!-- uygulamanın eşlemesi-->
    <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>

[ValiderPanierController] sınıfının kodu şöyledir:

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 {

   // web uygulamasının yapılandırması
  Config config;

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

   // isteğin işlenmesi
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // Alıcı sepetini onayladı
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // oturum süresi doldu
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // sepet onaylanıyor
    try {
      config.getArticlesDomain().acheter(panier);
    } catch (UncheckedAccessArticlesException ex) {
       // normal değil
      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");
    }
     // Olası hatalar alınıyor
    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");
    }
     // her şey normal görünüyor OK - ürün listesi görüntüleniyor
    return new ModelAndView("index");
  }
}

Açıklamalar:

  • Alışveriş sepeti oturumdan alınır. Oturum süresi dolmuşsa, [ERREURS] görünümü gönderilir:
     // bu işlemle ilgili hata listesi
    ArrayList erreurs = new ArrayList();
     // Alıcı sepetini onayladı
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // oturum süresi doldu
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
  • Sepetteki satın alımlar onaylanır. Stoklar, satın alımları karşılamaya yetmezse hatalar meydana gelebilir. Bu durumda, [ERREURS] görünümü gönderilir:
     // sepet onaylanıyor
    try {
      config.getArticlesDomain().acheter(panier);
    } catch (UncheckedAccessArticlesException ex) {
       // normal değil
      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");
    }
     // Olası hatalar alınıyor
    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");
    }
  • Her şey yolunda giderse, ürün listesi yeniden görüntülenir:
     // her şey normal görünüyor OK - ürün listesi görüntüleniyor
    return new ModelAndView("index");

[/vues/index.jsp] görünümünün müşteriyi [/main.do] eylemine yönlendirdiği bilinmektedir. Bu eylem, [LISTE] görünümünü görüntüleyecektir.