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ü

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

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

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

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

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:
- 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.
- 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.
- 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ı
- 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.
- 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:
- iş sınıfları
- veri erişim sınıfları
- 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);
|
| bir ürünü benzersiz bir şekilde tanımlayan birincil anahtar |
| ürün adı |
| ürünün fiyatı |
| mevcut stok durumu |
| 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:
- bir öğenin 5 bilgisini ayarlamaya yarayan bir oluşturucu
- 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.
- Makaleye girilen verilerin doğrulanması. Hatalı veriler olması durumunda bir istisna atılır.
- 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:
| ARTICLES tablosundaki tüm kalemleri bir [Article] nesne listesine dönüştürür |
| ARTICLES tablosunu boşaltır |
| birincil anahtarıyla tanımlanan [Article] nesnesini döndürür |
| ARTICLES tablosuna bir öğe ekler |
| [ARTICLES] tablosundaki bir öğeyi değiştirmeye olanak tanır |
| [ARTICLES] tablosundan bir ürünü silmeye yarar |
| [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#>=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();
}
|
| [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 |
| 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:
| veri erişim katmanı tarafından sağlanan veri erişim nesnesi |
| 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:
| satın alınan ürün |
| satın alınan miktar |
| satın alma tutarını gösterir |
| 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:
| 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 |
| alışverişlerin toplam tutarını hesaplar |
| sepetin kimlik dizesini göster |
| 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:

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:
| // 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:
| // 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:
| // 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:
| iş işlev sınıfları, veri erişim sınıfları ve veritabanı |
| sayfalar JSP |
| 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.jsp | Görünümler, uygulamanın [vues] klasöründe toplanmıştır  |
| infos.jsp |
| panier.jsp |
| paniervide.jsp |
| 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:
| anlamı | denetleyicinin eylemi | olası yanıtlar |
| 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] |
| 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] |
| 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 |
| müşteri sepetinden bir sepetinden bir ürünü silmek istiyor | - oturumdaki sepeti alır ve değiştirir | - [PANIER] - [PANIERVIDE] - [ERREURS] |
| müşteri, sepetini | - oturumdan alışveriş sepetini alır | - [PANIER] - [PANIERVIDE] - [ERREURS] |
| 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:
- farklı görünümlerin URL'lerinde yapılan değişiklikler
- [IArticlesDao] ve [IArticlesDomain] arayüzlerini uygulayan sınıfların değiştirilmesi
- 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:
- [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.
- [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#>=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
| |
 |  |
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:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| ArrayList adet [Article] türünde nesne |
| 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:

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:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| [Article] türü nesne - görüntülenecek ürün |
| String nesnesi - miktar hatası durumunda görüntülenecek mesaj |
| 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:

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:

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:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| [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:

Bu, /main?action=panier veya /main?action=retirerachat&id=ID isteğinin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
Kod:
| <%@ 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:

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:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| 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:
| <%@ 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:
| /**
* @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 |
| 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] |
| GET /main?action=infos&id=ID | - id=ID olan makaleyi iş sınıfına - görüntülemek | [INFOS] veya [ERREURS] |
| 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] |
| GET /main?action=retirerachat&id=ID | - id=ID olan ürünü müşterinin sepetindeki müşteri oturumundan | [PANIER] |
| GET /main?action=panier | - müşteri oturumunun müşteri oturumu | [PANIER] veya [PANIERVIDE] |
| 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
| 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ı:

İ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:
| // ü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:
| iş işlev sınıfları, veri erişim sınıfları ve veritabanı |
| sayfalar JSP |
| 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:
| iş sınıfları, veri erişim sınıfları ve veritabanı |
| sayfalar JSP |
| 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:

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:

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:

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:

[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:
| makale listesini görüntülemek için |
| makale listesini görüntülemek için |
| belirli bir ürünle ilgili bilgileri görüntülemek için |
| belirli bir ürünü satın almak için |
| alışveriş sepetini görüntülemek için |
| sepetten bir ürünü çıkarmak için |
| 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:

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:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| ArrayList, [Article] türünde nesneler |
| 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:

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:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| [Article] türü nesne - görüntülenecek ürün |
| String nesnesi - miktar hatası durumunda görüntülenecek mesaj |
| 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:

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:

Bu sayfa, /panier.do veya /retirerachat.do?id=ID isteğinin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| [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:

Bu, /panier.do veya /retirerachat.do?id=ID isteğinin ardından görüntülenir. Denetleyicinin isteğindeki öğeler şunlardır:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
Kod:
| <%@ 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:

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:
| Hashtable[] nesnesi - menü seçenekleri dizisi |
| 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:
| <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:

[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:
| // 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:
| // 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:
| // 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:
| <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:
| <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:
| iş sınıfları, veri erişim sınıfları ve veritabanı |
| sayfalar JSP |
| istemci isteklerini işleyen servlet, nesneler [Action] |
Spring ile de aynı mimariyi kullanıyoruz:
| iş işlev sınıfları, veri erişim sınıfları ve veritabanı |
| sayfalar JSP |
| 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:

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:

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:

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:

[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:
| makale listesini görüntülemek için |
| makale listesini görüntülemek için |
| belirli bir ürünle ilgili bilgileri görüntülemek için |
| belirli bir ürünü satın almak için |
| alışveriş sepetini görüntülemek için |
| sepetten bir ürünü çıkarmak için |
| 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:

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:

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:

Bir örnek üzerinden Spring uygulamasının işleyişini hatırlayalım:
- kullanıcı [http://localhost:8080/springwebarticles/main.do] URL'sini ister

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:

[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:
| // ü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:

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

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:

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:

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:

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