Skip to content

3. مقاله ۲ – نمونه‌هایی از معماری‌های وب سه‌لایه

اهداف این مقاله:

  • معماری‌های سه‌لایه
  • معماری وب پایه MVC
  • معماری Struts MVC
  • معماری Spring MVC

ابزارهای مورد استفاده:

  • 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، درایور JDBC. در واقع، هر سورس JDBC کفایت می‌کند.
  • IBExpert، نسخهٔ شخصی: http://www.hksoftware.net/download/ibep_2005.2.14.1_full.exe (مارس ۲۰۰۵). IBExpert به شما امکان می‌دهد پایگاه داده SGBD Firebird را به‌صورت گرافیکی مدیریت کنید.
  • Tomcat: http://jakarta.apache.org/tomcat/
  • پلاگین Tomcat برای Eclipse: http://www.sysdeo.com/eclipse/tomcatPlugin.html. همچنین به سند https://tahe.developpez.com/java/eclipse/ مراجعه کنید.

درک این سند نیازمند چندین پیش‌نیاز است. برخی از این موارد ممکن است در اسنادی که من نوشته‌ام پوشش داده شده باشند. در مواردی که چنین است، به آن‌ها ارجاع داده‌ام. بدیهی است که این صرفاً یک پیشنهاد است و خواننده آزاد است از اسناد مورد علاقه خود استفاده کند.

  • جاوا: [https://tahe.developpez.com/java/cours]
  • برنامه‌نویسی وب با جاوا: [https://tahe.developpez.com/java/web/]
  • برنامه‌نویسی وب با جاوا، اکلیپس و تام‌کت: [https://tahe.developpez.com/java/eclipse/]
  • برنامه‌نویسی وب با Struts: [https://tahe.developpez.com/java/struts/]
  • استفاده از جنبه Spring IoC: [https://tahe.developpez.com/java/springioc]
  • کتابخانه تگ JSTL: [https://tahe.developpez.com/java/eclipse/] (بخشی)
  • مستندات Ibatis SqlMap: [https://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
  • فایربرد: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf] (مارس ۲۰۰۵).

ایده های این سند از کتابی نشأت گرفته است که در تابستان ۲۰۰۴ خواندم، اثری باشکوه از راد جانسون: J2EE توسعه بدون EJB، منتشر شده توسط Wrox.


3.1. برنامهٔ وب‌مقالات

در اینجا هدف ما تشریح برخی از عناصر یک برنامه کاربردی وب تجارت الکترونیک است. این کار به کاربران وب امکان می‌دهد

  • برای مشاهده فهرستی از اقلام از یک پایگاه داده
  • تا برخی از آنها را به سبد خرید آنلاین اضافه کنند
  • و انتخاب خود را تأیید کنند. این تأیید به سادگی سطوح موجودی اقلام خریداری شده را در پایگاه داده به‌روز می‌کند.

نماهای مختلفی که به کاربر ارائه می‌شوند به شرح زیر خواهد بود:

  • نما [LISTE]، که فهرستی از اقلام قابل فروش را نمایش می‌دهد

Image

  • نما [INFOS] که اطلاعات بیشتری درباره یک محصول ارائه می‌دهد:

Image

  1. نما [PANIER]، که محتویات سبد خرید مشتری را نمایش می‌دهد

Image

  1. نما [PANIERVIDE] برای زمانی که سبد خرید مشتری خالی است

Image

  1. نما [ERREURS] که هرگونه خطای برنامه را گزارش می‌کند

Image

3.2. معماری کلی برنامه

ما می‌خواهیم یک برنامه با معماری سه‌لایه زیر بسازیم:

  • این سه لایه از طریق استفاده از رابط‌های جاوا مستقل شده‌اند
  • یکپارچه‌سازی لایه‌های مختلف توسط Spring انجام می‌شود
  • هر لایه در پکیج‌های جداگانه‌ای قرار دارد: web (لایه رابط کاربری)، domain (لایه منطق کسب‌وکار) و DAO (لایه دسترسی به داده).

در اینجا فرض می‌کنیم که لایه‌های [domain] و [dao] از قبل موجود هستند. ما صرفاً بر لایه [web] تمرکز خواهیم کرد که پیشنهاد می‌کنیم آن را به چند روش بسازیم:

  • با استفاده از فناوری کنترل‌کننده سرولت سنتی – صفحات JSP
  • با استفاده از فناوری Struts MVC
  • با استفاده از فناوری اسپرینگ (MVC)

در تمام موارد، برنامه از معماری MVC (مدل – نما – کنترل‌کننده) پیروی خواهد کرد. اگر به نمودار لایه‌ای بالا مراجعه کنیم، معماری MVC به شرح زیر در آن جای می‌گیرد:

پردازش یک درخواست مشتری مراحل زیر را دنبال می‌کند:

  1. کلاینت درخواستی را به کنترلر ارسال می‌کند. این کنترلر یک سرولت است که تمام درخواست‌های کلاینت را مدیریت می‌کند. این نقطه ورود به برنامه است. این «C» در ساختار MVC است.
  2. کنترل‌کننده این درخواست را پردازش می‌کند. برای این کار، ممکن است به کمک لایه کسب‌وکار، که در ساختار MVC به‌عنوان مدل «M» شناخته می‌شود، نیاز داشته باشد.
  3. کنترل‌کننده پاسخی از لایه کسب‌وکار دریافت می‌کند. درخواست مشتری پردازش شده است. این ممکن است به چندین پاسخ احتمالی منجر شود. یک مثال کلاسیک این است
    • یک صفحهٔ خطا اگر درخواست نتوانست به درستی پردازش شود
    • در غیر این صورت، یک صفحه تأیید
  4. کنترل‌کننده پاسخ (= نما) را برای ارسال به کلاینت انتخاب می‌کند. این معمولاً صفحه‌ای حاوی عناصر پویا است. کنترل‌کننده این عناصر را در اختیار نما قرار می‌دهد.
  5. ویو به کلاینت ارسال می‌شود. این «V» در ساختار MVC است.

3.3. مدل

در اینجا به M در MVC می‌پردازیم. مدل شامل اجزای زیر است:

  1. کلاس‌های کسب‌وکار
  2. کلاس‌های دسترسی به داده
  3. پایگاه داده

3.3.1. پایگاه داده

پایگاه داده تنها شامل یک جدول به نام ARTICLES است. این جدول با استفاده از دستورات زیر SQL ایجاد شده است:

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


/* محدودیت‌ها */
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<>'');

/*کلید اصلی */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
کلید اصلی که یک آیتم را به‌طور منحصربه‌فرد شناسایی می‌کند
nom
نام آیتم
prix
قیمت آن
stockactuel
موجودی فعلی
stockminimum
سطح موجودی که در زیر آن باید سفارش مجدد ثبت شود

در آزمایش‌های زیر از پایگاه‌داده با نام [Firebird] استفاده شد. [Firebird] نسخه‌ای «منبع باز» از SGBD است. درایور JDBC [firebirdsql-full.jar] در پوشه [WEB-INF/lib] برنامه وب قرار دارد.

3.3.2. پکیج‌های مدل

مدل M در اینجا به صورت سه آرشیو ارائه شده است:

  • istia.st.articles.dao: شامل کلاس‌های دسترسی به داده‌ها برای لایه [dao] است
  • istia.st.articles.exception: شامل یک کلاس استثنا برای مدیریت این آیتم است
  • istia.st.articles.domain: شامل کلاس‌های کسب‌وکار برای لایه [domain] است
archive
contenu
rôle
istia.st.articles.dao
- شامل پکیج [istia.st.articles.dao] است که به نوبه خود شامل عناصر زیر است:
- [IArticlesDao]: رابط دسترسی به لایه Dao. این تنها رابطی است که برای لایه [domain] قابل مشاهده است. این لایه رابط‌های دیگر را نمی‌بیند.
- [Article]: کلاسی که یک مقاله را تعریف می‌کند
- [ArticlesDaoSqlMap]: کلاسی که رابط [IArticlesDao] را با استفاده از ابزار SqlMap پیاده‌سازی می‌کند
لایه دسترسی به داده – به طور کامل در لایه [dao] از معماری سه‌لایه وب‌اپلیکیشن قرار دارد
istia.st.articles.domain
- شامل پکیج [istia.st.articles.domain] است که به نوبه خود شامل عناصر زیر می‌باشد:
- [IArticlesDomain]: رابط دسترسی به لایه [domain]. این تنها رابطی است که برای لایه وب قابل مشاهده است. این لایه هیچ رابط دیگری را نمی‌بیند.
- [AchatsArticles]: کلاسی که [IArticlesDomain] را پیاده‌سازی می‌کند
- [Achat]: کلاسی که نمایانگر خرید مشتری است
- [Panier]: کلاسی که تمام خریدهای یک مشتری را نشان می‌دهد
نماینده مدل خرید وب است – کاملاً در لایه [domain] از معماری سه‌لایهٔ برنامهٔ وب قرار دارد
istia.st.articles.exception
- شامل بسته [istia.st.articles.exception] است که به نوبه خود شامل عناصر زیر است:
- [UncheckedAccessArticlesException]: کلاسی که یک استثنا از نوع [RuntimeException] را تعریف می‌کند. این نوع استثنا توسط لایه [dao] هر زمان که مشکلی در دسترسی به داده‌ها رخ دهد، پرتاب می‌شود.
 

3.3.3. پکیج [istia.st.articles.dao]

کلاس تعریف‌کننده یک آیتم به شرح زیر است:

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) {
     // ابتدایی‌سازی ویژگی‌های نمونه
    setId(id);
    setNom(nom);
    setPrix(prix);
    setStockActuel(stockActuel);
    setStockMinimum(stockMinimum);
  }

     //گترها – ستترها
  public int getId() {
    return id;
  }

  public void setId(int id) {
     //آیا این شناسه معتبر است؟
    if (id < 0)
      throw new UncheckedAccessArticlesException("id[" + id + "] invalide");
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
     // نام معتبر؟
    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) {
     //آیا قیمت معتبر است؟
    if(prix<0) throw new UncheckedAccessArticlesException("Prix["+prix+"]invalide");
    this.prix = prix;
  }

  public int getStockActuel() {
    return stockActuel;
  }

  public void setStockActuel(int stockActuel) {
     //آیا این سهام معتبر است؟
    if (stockActuel < 0)
      throw new UncheckedAccessArticlesException("stockActuel[" + stockActuel + "] invalide");
    this.stockActuel = stockActuel;
  }

  public int getStockMinimum() {
    return stockMinimum;
  }

  public void setStockMinimum(int stockMinimum) {
     //آیا موجودی معتبر است؟
    if (stockMinimum < 0)
      throw new UncheckedAccessArticlesException("stockMinimum[" + stockMinimum + "] invalide");
    this.stockMinimum = stockMinimum;
  }

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

این کلاس فراهم می‌کند:

  1. یک سازنده برای تنظیم پنج مورد اطلاعات یک آیتم
  2. دستورگرها، که اغلب به آن‌ها گتر/ستتر گفته می‌شود، برای خواندن و نوشتن پنج بخش اطلاعات استفاده می‌شوند. نام این متدها از استاندارد JavaBean پیروی می‌کند. روش رایج این است که از اشیاء JavaBean در لایه DAO برای تعامل با داده‌های لایه SGBD استفاده شود.
  3. بررسی داده‌های واردشده برای آیتم. اگر داده‌ها نادرست باشند، یک استثنا پرتاب می‌شود.
  4. یک متد، toString، که مقدار یک آیتم را به صورت یک رشتهٔ کاراکتری بازیابی می‌کند. این اغلب برای اشکال‌زدایی یک برنامه کاربردی مفید است.

رابط [IArticlesDao] به شرح زیر تعریف شده است:

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

نقش روش‌های مختلف در این رابط به شرح زیر است:

getAllArticles
تمام آیتم‌های جدول ARTICLES را در یک لیست از اشیاء [Article] برمی‌گرداند
clearAllArticles
جدول ARTICLES را پاک می‌کند
getArticleById
شیء [Article] را که با کلید اصلی‌اش شناسایی شده است بازمی‌گرداند
ajouteArticle
امکان افزودن یک آیتم به جدول ARTICLES را فراهم می‌کند
modifieArticle
به شما امکان می‌دهد یک رکورد را در جدول [ARTICLES] ویرایش کنید
supprimerArticle
امکان حذف یک آیتم از جدول [ARTICLES] را برای شما فراهم می‌کند
changerStockArticle
امکان تغییر موجودی یک کالا را در جدول [ARTICLES] فراهم می‌کند

رابط، برنامه‌های مشتری را با تعدادی متد فراهم می‌کند که تنها بر اساس امضاهایشان تعریف شده‌اند. این رابط به نحوه پیاده‌سازی واقعی این متدها کاری ندارد. این امر انعطاف‌پذیری را در یک برنامه فراهم می‌کند. برنامه مشتری به جای فراخوانی یک پیاده‌سازی خاص از آن رابط، متدهای رابط را فراخوانی می‌کند.

انتخاب یک پیاده‌سازی خاص از طریق یک فایل پیکربندی Spring انجام خواهد شد. در اینجا، پیشنهاد می‌شود که رابط IArticlesDao با استفاده از یک محصول متن‌باز به نام SqlMap پیاده‌سازی شود. این کار به ما امکان می‌دهد تا تمام دستورات SQL را از کد جاوا حذف کنیم.

کلاس پیاده‌سازی [ArticlesDaoSqlMap] به شرح زیر تعریف شده است:

package istia.st.articles.dao;

// واردات
import com.ibatis.sqlmap.client.SqlMapClient;
import istia.st.articles.domain.Article;
import java.util.List;

public class ArticlesDaoSqlMap implements IArticlesDao {

   // میدان‌ها
  private SqlMapClient sqlMap;

   // سازنده‌ها
  public ArticlesDaoSqlMap(String sqlMapConfigFileName) { }

   // متدها
  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) {}
}

تمام روش‌های دسترسی به داده‌ها همگام‌سازی شده‌اند تا از بروز مشکلاتی که ناشی از دسترسی همزمان به منبع داده است، جلوگیری شود. در هر لحظه، تنها یک نخ به یک روش خاص دسترسی دارد.

کلاس [ArticlesDaoSqlMap] از ابزار [Ibatis SqlMap] استفاده می‌کند. هدف این ابزار استخراج کد دسترسی به داده‌های SQL از کد جاوا است. سپس این کد در یک فایل پیکربندی قرار می‌گیرد. بعداً به این موضوع باز خواهیم گشت. برای نمونه‌سازی، کلاس [ArticlesDaoSqlMap] به یک فایل پیکربندی نیاز دارد که نام آن به عنوان پارامتر به سازنده کلاس ارسال می‌شود. این فایل پیکربندی اطلاعات مورد نیاز برای را تعریف می‌کند:

  • دسترسی به پایگاه داده SGBD حاوی مقالات
  • مدیریت استخر اتصال
  • مدیریت تراکنش‌ها

در مثال ما، این فایل [sqlmap-config-firebird.xml] نامیده می‌شود و دسترسی به یک پایگاه داده Firebird را تعریف می‌کند:

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

فایل پیکربندی [articles.xml] که در بالا به آن اشاره شد، به شما امکان می‌دهد نحوه ساخت یک نمونه از کلاس [istia.st.articles.dao.Article] از یک سطر در جدول [ARTICLES] را در داخل SGBD تعریف کنید. همچنین پرس‌وجوهای SQL را تعریف می‌کند که لایه [dao] را قادر می‌سازد تا داده‌ها را از منبع داده Firebird بازیابی کند.

<?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 -->
  <typeAlias alias="article" type="istia.st.articles.dao.Article"/>

     <!--نگاشت ORM: سطر  جدول ARTICLES – نمونهٔ کلاس Article -->
  <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>

     <!--پرس‌وجوی SQL برای بازیابی تمام مقالات -->
  <statement id="getAllArticles" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum from ARTICLES
</statement>

     <!-- پرس‌وجوی SQL برای حذف همه مقالات -->
  <statement id="clearAllArticles">delete from ARTICLES</statement>

     <!--پرس‌وجوی SQL برای درج یک آیتم -->
  <statement id="insertArticle">
    insert into ARTICLES (id, nom, prix,
    stockactuel, stockminimum) values
    (#(id#,#name#,#price#,#current_stock#,#minimum_stock#)
</statement>

     <!-- پرس‌وجوی SQL برای حذف یک آیتم خاص -->
  <statement id="deleteArticle">delete FROM ARTICLES where id=#id#</statement>

     <!-- پرس‌وجوی SQL برای اصلاح یک آیتم خاص -->
  <statement id="modifyArticle">
    update ARTICLES set nom=#نام#,
    prix=#price#,current_stock=#current_stock#,minimum_stock=#minimum_stock# where
    id=#id#
</statement>

     <!-- پرس‌وجوی SQL برای بازیابی یک آیتم خاص -->
  <statement id="getArticleById" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum FROM ARTICLES where id=#id#
</statement>

     <!-- پرس‌وجوی SQL برای اصلاح موجودی یک آیتم مشخص -->
  <statement id="changerStockArticle">
    update ARTICLES set
    stockActuel=stockActuel+#movement#
    where id=#id# و stockActuel+#movement#>=0
</statement>
</sqlMap>

کد بسته [dao] را می‌توان در ضمیمه یافت.

3.3.4. بسته [istia.st.articles.domain]

رابط [IArticlesDomain] لایه [métier] را از لایه [web] جدا می‌کند. دومی از طریق این رابط به لایه [métier/domain] دسترسی پیدا می‌کند، بدون اینکه خود را درگیر کلاسی کند که در واقع آن را پیاده‌سازی می‌کند. این رابط عملیات زیر را برای دسترسی به لایه کسب‌وکار تعریف می‌کند:

 package istia.st.articles.domain;

//واردات
import java.util.ArrayList;
import java.util.List;

public abstract interface IArticlesDomain {

   // روش‌ها
  void acheter(Panier panier);
  List getAllArticles();
  Article getArticleById(int idArticle);
  ArrayList getErreurs();
}
List getAllArticles()
فهرست اشیاء [Article] را که باید به کلاینت ارائه شوند، بازمی‌گرداند
Article getArticleById(int idArticle)
شیء [Article] را که توسط [idArticle] شناسایی شده بازمی‌گرداند
void acheter(Panier panier)
سبد خرید مشتری را با کاهش موجودی اقلام خریداری‌شده به میزان تعداد خریداری‌شده اعتبارسنجی می‌کند – در صورت ناکافی بودن موجودی ممکن است با خطا مواجه شود
ArrayList getErreurs()
فهرستی از هرگونه خطاهای رخ‌داده را بازمی‌گرداند – در صورت عدم وجود خطا خالی است

در اینجا، رابط [IArticlesDomain] توسط کلاس زیر [AchatsArticles] پیاده‌سازی خواهد شد:

package istia.st.articles.domain;

// واردات
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 {

   // میدان‌ها
  private IArticlesDao articlesDao;
  private ArrayList erreurs;

   // سازنده‌ها
  public AchatsArticles(IArticlesDao articlesDao) { }

   // روش‌ها
  public ArrayList getErreurs() {}
  public List getAllArticles() {}
  public Article getArticleById(int id) {}
  public void acheter(Panier panier) { }
}

این کلاس چهار متد رابط [IArticlesDomain] را پیاده‌سازی می‌کند. این کلاس دارای دو فیلد خصوصی است:

IArticlesDao articlesDao
شیء دسترسی به داده که توسط لایه دسترسی به داده فراهم شده است
ArrayList erreurs
فهرست هرگونه خطا

برای ایجاد یک نمونه از کلاس، باید شیء‌ای را که دسترسی به داده‌ها از SGBD را ممکن می‌سازد، فراهم کنید:

public AchatsArticles(IArticlesDao articlesDao)
سازنده

کلاس [Achat] یک خرید مشتری را نشان می‌دهد:

package istia.st.articles.domain;

public class Achat {

   // میدان‌ها
  private Article article;
  private int qte;

  // سازنده‌ها
  public Achat(Article article, int qte) { }

  // روش‌ها
  public double getTotal() {}
  public Article getArticle() {}
  public void setArticle(Article article) { }
  public int getQte() {}
  public void setQte() { }
  public String toString() {}
}

کلاس [Achat] یک JavaBean است با فیلدها و متدهای زیر:

article
مورد خریداری‌شده
qte
مقدار خریداری‌شده
double getTotal()
مبلغ خرید را نشان می‌دهد
String toString()
رشته شناسه شیء

کلاس [Panier] تمام خریدهای مشتری را نشان می‌دهد:

package istia.st.articles.domain;

// واردات
import java.util.ArrayList;

public class Panier {

  // میدان‌ها
  private ArrayList achats;

   // سازندگان
  public Panier() { }

   // روش‌ها
  public ArrayList getAchats() {}
  public void ajouter(Achat unAchat) { }
  public void enlever(int idAchat) { }
  public double getTotal() {}
  public String toString() { }
}

کلاس [Panier] یک JavaBean است که دارای فیلدها و متدهای زیر است:

achats
فهرست خریدهای مشتری – فهرستی از اشیاء از نوع [Achat]
void ajouter(Achat unAchat)
افزودن یک خرید به فهرست خریدها
void enlever(int idArticle)
حذف خرید آیتم idArticle
double getTotal()
مجموع مبلغ خریدها را محاسبه می‌کند
String toString()
بازگرداندن رشته شناسه سبد خرید
ArrayList getAchats()
فهرست خریدها را بازمی‌گرداند

کد بسته [domain] را می‌توان در ضمیمه یافت.

3.3.5. پکیج [istia.st.articles.exception]

این بسته شامل کلاسی است که استثنایی را که لایه [dao] هنگام مواجهه با مشکل در دسترسی به منبع داده پرتاب می‌کند، تعریف می‌کند:

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. آزمون مدل

مدل M با پیکربندی زیر در Eclipse آزمایش شده است:

Image

توضیحات:

  • در [WEB-INF/lib] موارد زیر یافت می‌شود:
    • آرشیو مورد نیاز ابزار [ibatis SqlMap]، که مسئول دسترسی به پایگاه داده Firebird SGBD است: ibatis-*.jar
    • آن‌که توسط ابزار [spring] مورد نیاز است: spring.jar
    • درایور JDBC برای SGBD و [Firebird]: firebirdsql-full.jar
    • آرشیوهای مورد نیاز برای لاگ‌ها: log4-*.jar, commons-logging.jar
    • سه آرشیو برای مدل آزمایش‌شده: istia.st.articles.*.jar
    • آرشیو مورد نیاز برای ابزار تست: [junit]
  • [WEB-INF/src] حاوی فایل‌های پیکربندی است که به طور خودکار توسط Eclipse به [WEB-INF/classes] کپی می‌شوند:
    • فایل‌های پیکربندی برای ابزار [sqlmap]: sqlmap-config-firebird.xml, articles.xml
    • آن‌ها برای ابزار [spring]: spring-config-test-dao.xml, spring-config-test-domain.xml
    • فایل پیکربندی برای ابزار [log4j]: log4j.properties
  • پکیج [istia.st.articles.tests] شامل کلاس‌های تست مدل است

3.3.6.1. تست‌های لایه [dao]

کلاس تست JUnit برای لایه [dao] به شرح زیر است. مطالعه آن به درک نحوه استفاده از متدهای رابط [IArticlesDao] کمک می‌کند:

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
public class JunitModeleDaoArticles extends TestCase {

     // یک نمونه از کلاس تحت آزمایش
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // یک نمونه دسترسی به داده‌ها را بازیابی می‌کند
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-dao.xml"))).getBean("articlesDao");
    }

    public void testGetAllArticles() {
         // موارد را نمایش می‌دهد
        listArticles();
    }

    public void testClearAllArticles() {
         //جدول مقالات را پاک می‌کند
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
    }

    public void testAjouteArticle() {
         //محتویات ARTICLES را حذف می‌کند
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         //درج
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         //جدول ARTICLES را می‌خواند
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         //آن را نمایش می‌دهد
        listArticles();
    }

    public void testSupprimeArticle() {
         //محتویات ARTICLES را حذف می‌کند
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         //درج
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         //جدول ARTICLES را می‌خواند
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         // حذف
        articlesDao.supprimeArticle(4);
         //جدول ARTICLES را می‌خواند
        articles = articlesDao.getAllArticles();
        assertEquals(1, articles.size());
         //جدول را نمایش می‌دهد
        listArticles();
    }

    public void testModifieArticle() {
         // محتویات جدول ARTICLES را حذف می‌کند
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         //درج
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         //جدول ARTICLES را می‌خواند
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         //getById
        Article unArticle = articlesDao.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getNom(), "article4");
         // به‌روزرسانی
        articlesDao.modifieArticle(new Article(4, "article4", 44, 44, 44));
         //getById
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getPrix(), 44, 1e-6);
         // جدول را نمایش می‌دهد
        listArticles();
    }

    public void testGetArticleById() {
         // محتویات ARTICLES را حذف می‌کند
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         //درج
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         //جدول ARTICLES را می‌خواند
        articles = articlesDao.getAllArticles();
        assertEquals(2, articles.size());
         //getById
        Article unArticle = articlesDao.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getNom(), "article4");
    }

    private void listArticles() {
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
         //مقالاتی را که خوانده شده‌اند نمایش می‌دهد
        for (int i = 0; i < articles.size(); i++) {
            System.out.println(((Article) articles.get(i)).toString());
        }
    }

    public void testChangerStockArticle() throws InterruptedException {
         // محتویات ARTICLES را حذف می‌کند
        articlesDao.clearAllArticles();
         //درج
        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);
         // ایجاد ۱۰۰ نخ برای به‌روزرسانی موجودی آیتم ۳
        Thread[] taches = new Thread[100];
        for (int i = 0; i < taches.length; i++) {
            taches[i] = new ThreadMajStock("thread-" + i, articlesDao);
            taches[i].start();
        }
         // در انتظار پایان یافتن نخ‌ها
        for (int i = 0; i < taches.length; i++) {
            taches[i].join();
        }
         //بازیابی آیتم ۳ و بررسی موجودی آن
        Article unArticle = articlesDao.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        assertEquals(1, unArticle.getStockActuel());
         //به‌روزرسانی موجودی برای آیتم ۴
        boolean erreur = false;
        int nbLignes = articlesDao.changerStockArticle(4, -100);
        assertEquals(0, nbLignes);
         // جدول را نمایش می‌دهد
        listArticles();
    }
}

توضیحات:

  • کلاس تست از متد setUp خود برای ذخیره یک نمونه از کلاس تحت تست استفاده می‌کند:
1
2
3
4
5
6
7
8
     // یک نمونه از کلاس تحت تست
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // یک نمونه از کلاس دسترسی به داده را بازیابی می‌کند
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-dao.xml"))).getBean("articlesDao");
    }
  • شیء تحت تست توسط [Spring] فراهم می‌شود. در بالا، بین Spring با نام [articlesDao] درخواست شده است. این بین در فایل پیکربندی Spring با شناسه [spring-config-test-dao.xml] تعریف شده است:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!--کلاس دسترسی به داده‌ها -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
</beans>

همانطور که در بالا نشان داده شده است، بین [articlesDao] یک نمونه از کلاس [istia.st.articles.dao.ArticlesDaoSqlMap] است. این کلاس یک سازنده دارد که نام فایل پیکربندی ابزار [SqlMap] را به عنوان پارامتر می‌پذیرد. این نام در اینجا ارائه شده است. این [sqlmap-config-firebird.xml] است. این قبلاً توصیف شده است. این تمام اطلاعات مورد نیاز برای دسترسی به داده‌ها در SGBD را فراهم می‌کند.

متد [testChangerStockArticle]، ۱۰۰ نخ (thread) ایجاد می‌کند که مأمور کاهش موجودی یک کالای مشخص هستند. هدف در اینجا، آزمایش دسترسی همزمان به SGBD است. از آنجا که متد [changerStockArticle] از کلاس [istia.st.articles.dao.ArticlesDaoSqlMap] همگام‌سازی شده است، این تست با موفقیت اجرا می‌شود. اگر همگام‌سازی حذف شود، دیگر با موفقیت اجرا نخواهد شد. کلاس مسئول به‌روزرسانی موجودی به شرح زیر است:

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() {
         // ردیابی
        System.out.println(name + " lancé");
         //تغییر موجودی برای کالا ۳
        articlesDao.changerStockArticle(3, -1);
         //پیگیری
        System.out.println(name + " terminé");
    }
}
  • کلاس فوق موجودی کالا شماره ۳ را به میزان ۱ کاهش می‌دهد.

3.3.6.2. تست‌ها برای لایه [domain]

کلاس تست JUnit برای لایه [domain] به شرح زیر است:

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
public class JunitModeleDomainArticles extends TestCase {

     // یک نمونه از کلاس دسترسی به دامنه
    private IArticlesDomain articlesDomain;

     // یک نمونه از کلاس دسترسی به داده‌ها
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         // یک نمونه از کلاس دسترسی به دامنه را بازیابی می‌کند
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");
         //یک نمونه از کلاس دسترسی به داده‌ها را بازیابی می‌کند
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");
    }

     //بازیابی یک آیتم خاص
    public void testGetArticleById() {
         // محتویات ARTICLES را حذف می‌کند
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         //درج
        articlesDao.ajouteArticle(new Article(3, "article3", 30, 30, 3));
        articlesDao.ajouteArticle(new Article(4, "article4", 40, 40, 4));
         //جدول ARTICLES را می‌خواند
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         //getById
        Article unArticle = articlesDomain.getArticleById(3);
        assertEquals(unArticle.getNom(), "article3");
        unArticle = articlesDao.getArticleById(4);
        assertEquals(unArticle.getNom(), "article4");
    }

     // نمایش صفحه
    private void listArticles() {
         //جدول ARTICLES را می‌خواند
        List articles = articlesDomain.getAllArticles();
         // موارد خوانده شده را نمایش می‌دهد
        for (int i = 0; i < articles.size(); i++) {
            System.out.println(((Article) articles.get(i)).toString());
        }
    }

     //خرید اقلام
    public void testAchatPanier(){
         // محتویات ARTICLES را حذف می‌کند
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         //درج
        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 را می‌خواند
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         //یک سبد خرید با دو آیتم ایجاد می‌کند
        Panier panier=new Panier();
        panier.ajouter(new Achat(article3,10));
        panier.ajouter(new Achat(article4,10));
         //بررسی می‌کند
        assertEquals(700.0,panier.getTotal(),1e-6);
        assertEquals(2,panier.getAchats().size());
         //تأیید سبد خرید
        articlesDomain.acheter(panier);
         //بررسی می‌کند
        assertEquals(0,articlesDomain.getErreurs().size());
        assertEquals(0,panier.getAchats().size());
         //جستجوی آیتم شماره ۳
        article3=articlesDomain.getArticleById(3);
        assertEquals(20,article3.getStockActuel());
         //جستجوی آیتم شماره ۴
        article4=articlesDomain.getArticleById(4);
        assertEquals(30,article4.getStockActuel());
         //سبد جدید
        panier.ajouter(new Achat(article3,100));
         //تأیید سبد
        articlesDomain.acheter(panier);
         // تأییدها – شما بیش از حد خرید کرده‌اید
         // حتماً اشتباهی رخ داده است
        assertEquals(1,articlesDomain.getErreurs().size());
         // جستجوی کالا شمارهٔ ۳
        article3=articlesDomain.getArticleById(3);
         //سطح موجودی نباید تغییر کرده باشد
        assertEquals(20,article3.getStockActuel());    
    }

     // حذف خریدها
    public void testRetirerAchats(){
         // حذف محتویات ARTICLES
        articlesDao.clearAllArticles();
         //جدول ARTICLES را می‌خواند
        List articles = articlesDao.getAllArticles();
        assertEquals(0, articles.size());
         //درج
        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 را می‌خواند
        articles = articlesDomain.getAllArticles();
        assertEquals(2, articles.size());
         //یک سبد خرید با دو آیتم ایجاد می‌کند
        Panier panier=new Panier();
        panier.ajouter(new Achat(article3,10));
        panier.ajouter(new Achat(article4,10));
         //بررسی می‌کند
        assertEquals(700.0,panier.getTotal(),1e-6);
        assertEquals(2,panier.getAchats().size());
         // افزودن یک آیتم خریداری‌شده
        panier.ajouter(new Achat(article3,10));
         //بررسی می‌کند
         // مجموع باید روی ۱۰۰۰ تنظیم شود
        assertEquals(1000.0,panier.getTotal(),1e-6);
         // هنوز ۲ قلم در سبد خرید وجود دارد
        assertEquals(2,panier.getAchats().size());
         // مقدار آیتم ۳ باید به ۲۰ تغییر می‌یافت
        Achat achat=(Achat)panier.getAchats().get(0);
        assertEquals(20,achat.getQte());
         // مورد ۳ از سبد حذف شده است
        panier.enlever(3);
         // تأیید می‌کند
         // مجموع باید به ۴۰۰ تغییر کرده باشد
        assertEquals(400.0,panier.getTotal(),1e-6);
         // فقط یک قلم در سبد
        assertEquals(1,panier.getAchats().size());
         // این باید آیتم شماره ۴ باشد
        assertEquals(4,((Achat)panier.getAchats().get(0)).getArticle().getId());
    }
}

توضیحات:

  • کلاس تست از متد setUp خود برای ذخیره یک نمونه از کلاس تحت تست و همچنین یک نمونه از کلاس دسترسی به داده‌ها استفاده می‌کند. این نکته آخر بحث‌برانگیز است. از نظر تئوری، کلاس تست نباید به لایه [dao] دسترسی داشته باشد، لایه‌ای که حتی قرار نیست از آن آگاه باشد. در اینجا ما این «آیین‌نامه» را نادیده گرفته‌ایم، که اگر به آن عمل می‌کردیم، مجبور می‌شدیم متدهای جدیدی در رابط [IArticlesDomain] خود ایجاد کنیم.
     //یک نمونه از کلاس دسترسی به دامنه
    private IArticlesDomain articlesDomain;

     // یک نمونه از کلاس دسترسی به داده‌ها
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
         //یک نمونه از کلاس دسترسی به دامنه را بازیابی می‌کند
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");
         //یک نمونه از کلاس دسترسی به داده‌ها را بازیابی می‌کند
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");
    }
  • شیء مورد آزمایش توسط [Spring] فراهم می‌شود. در بالا، ما از Spring bean با نام [articlesDomain] درخواست می‌کنیم. این bean در فایل پیکربندی Spring با آدرس [spring-config-test-domain.xml] تعریف شده است:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!--کلاس دسترسی به داده‌ها -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- کلاس کسب‌وکار -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
</beans>

همان‌طور که در بالا مشاهده می‌شود، بیین [articlesDomain] نمونه‌ای از کلاس [istia.st.articles.domain.AchatsArticles] است. این کلاس دارای یک سازنده است که به‌عنوان پارامتر یک شی دسترسی به لایه [dao] از نوع [IArticlesDao] را می‌پذیرد. در اینجا، فایل پیکربندی مشخص می‌کند که این شیء، بین با نام [articlesDao] است. این امر Spring را مجبور می‌کند تا این بین را نمونه‌سازی کند. نمونه‌سازی بین [articlesDao] قبلاً توضیح داده شد. بنابراین، در نهایت، دو بین نمونه‌سازی شده‌اند:

  • [articlesDao] از نوع [istia.st.articles.dao.ArticlesDaoSqlMap]
  • [articlesDomain] از نوع [ istia.st.articles.domain.AchatsArticles]

این دو نمونه‌سازی با اولین فراخوانی Spring تحریک می‌شوند:

1
2
3
4
         //یک نمونه دسترسی دامنه را بازیابی می‌کند
        articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
                new ClassPathResource("spring-config-test-domain.xml")))
                .getBean("articlesDomain");

سپس بیون [articlesDomain] بازیابی می‌شود. در دومین فراخوانی به اسپرینگ:

1
2
3
         // یک نمونه دسترسی به داده را بازیابی می‌کند
        articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
                "spring-config-test-domain.xml"))).getBean("articlesDao");

[Spring] به سادگی مرجعی به دانه (bean) [articlesDao] را که در فراخوانی قبلی ایجاد شده بود، بازمی‌گرداند. این اصل الگوی تک‌نسخه (singleton) است. اگر یک دانه از اسپرینگ درخواست شود، در صورتی که از قبل وجود نداشته باشد، آن را نمونه‌سازی می‌کند؛ در غیر این صورت، مرجعی به دانه موجود را بازمی‌گرداند.

3.4. اپلیکیشن وب سه‌لایه MVC

در ادامه، می‌خواهیم وب‌اپلیکیشن سه‌لایه زیر را بسازیم:

این برنامه دارای معماری MVC خواهد بود. مدل M نوشته و آزمایش شده است. این همان مدلی است که قبلاً توضیح داده شد. این مدل در سه آرشیو به ما ارائه شده است: [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception]. ما باید کنترل‌کننده C و ویوهای V را بنویسیم.

ابتدا، بیایید یک رویکرد متعارف را در نظر بگیریم، جایی که:

  • کنترل‌کننده C توسط یک سرولت واحد پیاده‌سازی می‌شود
  • ویوها (V) توسط صفحات JSP ارائه می‌شوند

3.5. معماری MVC مبتنی بر یک سرولت کنترل‌کننده و صفحات JSP است.

معماری MVC برنامه به شرح زیر خواهد بود:

M=modèle
کلاس‌های کسب‌وکار، کلاس‌های دسترسی به داده و پایگاه داده
V=vues
صفحات JSP
C=contrôleur
سرولتی برای پردازش درخواست‌های مشتری

3.5.1. مدل

این قبلاً ارائه شده بود. شامل آرشیوهای جاوا [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception] است.

3.5.2. نماها

ویوها با آن‌هایی که در ابتدای این سند ارائه شده‌اند مطابقت دارند:

LISTE
liste.jsp
نمای‌ها در پوشه [vues] درون برنامه قرار دارند.
INFOS
infos.jsp
PANIER
panier.jsp
PANIERVIDE
paniervide.jsp
ERREURS
erreurs.jsp

3.5.3. کنترل‌کننده

کنترلر از یک سرولت واحد به نام [WebArticles] تشکیل خواهد شد. این سرولت درخواست‌های مختلف کلاینت‌ها را پردازش می‌کند. این درخواست‌ها با وجود پارامتر [action] در درخواست کلاینت HTTP شناسایی می‌شوند:

requête
به معنی
عملگر کنترل‌کننده
پاسخ‌های ممکن
action=liste
مشتری می‌خواهد فهرستی از
اقلام
- درخواست فهرست اقلام از ...
کسب‌وکار
- [LISTE]
- [ERREURS]
action=infos
مشتری درخواست می‌کند
اطلاعات مربوط به یکی از اقلام نمایش داده شده در نما
[LISTE]
- آیتم را از لایه کسب‌وکار درخواست می‌کند
- [INFOS]
- [ERREURS]
action=achat
مشتری یک کالا را خریداری می‌کند
- کالا را از لایه کسب‌وکار درخواست می‌کند و
آن را به سبد خرید مشتری اضافه می‌کند
- [INFOS] در صورت وجود خطای مقدار
- [LISTE] در صورت عدم وجود خطا
action=retirerachat
مشتری می‌خواهد یک را حذف کند
خرید از سبد خرید خود
- سبد خرید را از جلسه بازیابی کرده و آن را اصلاح می‌کند
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
مشتری می‌خواهد خود را مشاهده کند
سبد خرید
- سبد خرید را از جلسه بازیابی می‌کند
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
مشتری خرید خود را تکمیل کرده است
و به مرحله پرداخت می‌رود
- سطوح موجودی کالا را به‌روز می‌کند
اقلام خریداری‌شده
- هر کالایی را که
برای خرید تأیید شده‌اند
- [LISTE]
- [ERREURS]

3.5.4. پیکربندی برنامه

ما تلاش خواهیم کرد تا برنامه را طوری پیکربندی کنیم که در برابر تغییراتی مانند موارد زیر تا حد امکان انعطاف‌پذیر باشد:

  1. تغییرات در آدرس‌های URL نماهای مختلف
  2. تغییراتی در کلاس‌های پیاده‌ساز رابط‌های [IArticlesDao] و [IArticlesDomain]
  3. تغییرات در SGBD، پایگاه داده یا جدول محصول

3.5.5. تغییرات URL

URLها برای ویوها به همراه چند پارامتر دیگر در فایل پیکربندی برنامه [web.xml] ذخیره خواهند شد:

<?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] یافت

  • URLها برای نماهای مختلف برنامه
  • نام [springConfigFileName] فایل پیکربندی Spring، که امکان ایجاد اشیاء تک‌نمونه (singleton) برای دسترسی به لایه‌های کسب‌وکار و DAO را فراهم می‌کند
  • نما [/vues/index.jsp]، که زمانی نمایش داده می‌شود که URL درخواستی توسط کلاینت /<context> باشد، جایی که <context> زمینهٔ برنامه است

3.5.6. تغییرات در کلاس‌های پیاده‌ساز رابط‌ها

مطابق با اصول معماری سه‌لایه، لایه‌ها باید از یکدیگر پنهان‌شده باشند. این پنهان‌سازی به شرح زیر انجام می‌شود:

  • لایه‌ها به‌جای کلاس‌های ملموس، از طریق رابط‌ها با یکدیگر ارتباط برقرار می‌کنند
  • کد در یک لایه هرگز خودِ کلاس لایه دیگر را برای استفاده از آن، نمونه سازی نمی‌کند. این کد به سادگی یک نمونه از پیاده‌سازی رابط را برای لایه‌ای که می‌خواهد استفاده کند، از یک ابزار خارجی، در این مورد [Spring]، درخواست می‌کند. برای انجام این کار، می‌دانیم که نیازی نیست نام کلاس پیاده‌سازی را بشناسیم، بلکه تنها نام «بین» (bean) اسپرینگ را که می‌خواهیم به آن ارجاع دهیم، کافی است.

در برنامه کاربردی ما، فایل پیکربندی Spring ممکن است به این شکل باشد:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!--کلاس دسترسی به داده‌ها -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!--کلاس کسب‌وکار -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
</beans>

برای دسترسی به لایه [métier]، یک کلاس در لایه [Interface utilisateur, UI] می‌تواند درخواست دانه [articlesDomain] را کند. اسپرینگ سپس یک شی از نوع [istia.st.articles.domain.AchatsArticles] را نمونه‌سازی می‌کند. برای این کار، به یک بین از نوع [articlesDao]، یعنی یک شی از نوع [istia.st.articles.dao.ArticlesDaoSqlMap] نیاز دارد. سپس Spring یک شی از این نوع را ایجاد می‌کند. این ایجاد بر اساس اطلاعات موجود در فایل [sqlmap-config-firebird.xml] است که فایل پیکربندی برای دسترسی به داده‌ها از طریق SqlMap می‌باشد. در پایان عملیات، کلاس [UI] که دانه [articlesDomain] را درخواست کرده است، کل زنجیره را که آن را به داده‌ها در SGBD متصل می‌کند، در اختیار دارد:

3.5.7. تغییرات مربوط به SGBD یا پایگاه داده

استقلال برنامه وب از تغییرات مربوط به SGBD یا پایگاه داده در اینجا توسط فایل‌های پیکربندی برای SqlMap تضمین می‌شود. دو فایل از آنها وجود دارد:

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

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

این فایل به یک پایگاه داده Firebird اشاره دارد. به سادگی نام درایور JDBC را برای کار با درایور دیگری مانند SGBD تغییر دهید.

  1. فایل [articles.xml]، که حاوی دستورالعمل‌های مختلف SQL مورد نیاز برنامه است:
<?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 -->
  <typeAlias alias="article" type="istia.st.articles.dao.Article"/>

     <!--نگاشت ORM: سطر  جدول ARTICLES – نمونهٔ کلاس مقاله -->
  <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>

     <!-- پرس‌وجوی SQL برای بازیابی تمام مقالات -->
  <statement id="getAllArticles" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum from ARTICLES
</statement>

     <!-- پرس‌وجوی SQL برای حذف همه مقالات -->
  <statement id="clearAllArticles">delete from ARTICLES</statement>

     <!--پرس‌وجوی SQL برای درج یک آیتم -->
  <statement id="insertArticle">
    insert into ARTICLES (id, nom, prix,
    stockactuel, stockminimum) values
    (#(id#,#name#,#price#,#current_stock#,#minimum_stock#)
</statement>

     <!--پرس‌وجوی SQL برای حذف یک آیتم خاص -->
  <statement id="deleteArticle">delete FROM ARTICLES where id=#id#</statement>

     <!-- پرس‌وجوی SQL برای اصلاح یک آیتم خاص -->
  <statement id="modifyArticle">
    update ARTICLES set nom=#نام#,
    prix=#price#,current_stock=#current_stock#,minimum_stock=#minimum_stock# where
    id=#id#
</statement>

     <!-- پرس‌وجوی SQL برای بازیابی یک آیتم خاص -->
  <statement id="getArticleById" resultMap="article">
    select id, nom, prix,
    stockactuel, stockminimum FROM ARTICLES where id=#id#
</statement>

     <!-- پرس‌وجوی SQL برای اصلاح موجودی یک آیتم مشخص -->
  <statement id="changerStockArticle">
    update ARTICLES set
    stockActuel=stockActuel+#movement#
    where id=#id# و stockActuel+#movement#>=0
</statement>
</sqlMap>

اگر نام جدول آیتم یا ستون‌ها تغییر می‌کرد، ما باید پرس‌وجوها را در این فایل پیکربندی بدون نیاز به تغییر کد جاوا دوباره بنویسیم. این وضعیت همچنین در صورتی صادق است که یک پرس‌وجو به دلایل عملکردی با یک رویه ذخیره‌شده جایگزین شود.

3.5.8. معماری کلی برنامه [webarticles]

یک برنامه وب جاوا مانند یک پازل با قطعات بسیار است. پیاده‌سازی معماری MVC عموماً تعداد این قطعات را افزایش می‌دهد. ساختار برنامه [webarticles] تحت [eclipse] به شرح زیر است:

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

3.5.9. نماهای JSP

نماهای JSP از کتابخانه تگ JSTL استفاده می‌کنند.

3.5.9.1. entete.jsp

برای تضمین یکپارچگی در نماهای مختلف، آن‌ها از یک هدر یکسان استفاده می‌کنند که نام برنامه را در کنار منو نمایش می‌دهد:

منو پویا است و توسط کنترلر تعریف می‌شود. کنترلر در درخواست ارسال‌شده به صفحه JSP، یک ویژگی کلید «actions» با مقدار مرتبط آرایه‌ای از نوع Hastable[] را شامل می‌شود. هر عنصر این آرایه یک دیکشنری است که برای ایجاد یک گزینه منو در هدر در نظر گرفته شده است. هر دیکشنری دو کلید دارد:

  • href: URL مرتبط با گزینه منو
  • link: متن منو

ویوهای دیگر در برنامه از هدر تعریف‌شده توسط [entete.jsp] با استفاده از تگ JSP زیر استفاده خواهند کرد:

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

هنگامی که این تگ اجرا شود، کد صفحه JSP که حاوی آن است، کد صفحه [entete.jsp] را شامل خواهد کرد. از آنجا که URL صفحه یک URL نسبی است (بدون '/')، صفحه [entete.jsp] در همان پوشه‌ای که صفحه حاوی تگ <jsp:include> در آن قرار دارد، جستجو خواهد شد.

کد:

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

این نما لیست اقلام موجود برای فروش را نمایش می‌دهد:

این نما پس از درخواست /main?action=list یا /main?action=cartvalidation نمایش داده می‌شود. پارامترهای درخواست کنترلر به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
listarticles
ابجکت‌های از نوع [Article]
message
شیء رشته – پیامی که در پایین صفحه نمایش داده می‌شود

هر لینک [Infos] در آرایه HTML مقالات دارای URL به شکل [?action=infos&id=ID] است، که در آن ID فیلد ID مقاله نمایش‌داده‌شده است.

کد:

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

این نما اطلاعاتی در مورد یک آیتم نمایش می‌دهد و همچنین امکان خرید آن را فراهم می‌کند:

Image

این صفحه پس از درخواست به /main?action=infos&id=ID یا درخواست به /main?action=achat&id=ID زمانی که مقدار خریداری‌شده نادرست باشد، نمایش داده می‌شود. اجزای درخواست کنترلر به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
article
شیء از نوع [Article] – موردی که باید نمایش داده شود
msg
ابژه رشته‌ای – پیامی برای نمایش در صورت بروز خطای مقدار
qte
ابژه رشته‌ای – مقداری که باید در فیلد ورودی [Qte] نمایش داده شود

فیلدهای [msg] و [qte] در صورت بروز خطای ورودی در مورد مقدار استفاده می‌شوند:

Image

این صفحه شامل فرمى است که از طریق دکمه [Acheter] ارسال مى‌شود. URL هدف براى POST، [?action=achat&id=ID] است، که در آن ID شناسه کالاى خريدارى‌شده مى‌باشد.

کد:

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

این نما محتویات سبد خرید را نمایش می‌دهد:

Image

این صفحه پس از درخواست /main?action=shopping-basket یا /main?action=remove-purchase&id=ID نمایش داده می‌شود. پارامترهای درخواست کنترلر به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
panier
شیء از نوع [Panier] – سبد خریدی که باید نمایش داده شود

هر لینک [Retirer] در آرایه HTML اقلام سبد خرید، دارای یک URL به شکل [?action=retirerachat&id=ID] است، که در آن ID فیلد [id] ازکالایی که می‌خواهید از سبد حذف کنید.

کد:

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

این نما اطلاعاتی را نمایش می‌دهد که نشان می‌دهد سبد خالی است:

Image

این نما پس از درخواست به /main?action=basket یا /main?action=remove&id=ID نمایش داده می‌شود. پارامترهای درخواست کنترلر به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو

کد:

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

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

3.5.9.6. erreurs.jsp

این نما در صورت بروز خطاها نمایش داده می‌شود:

Image

این صفحه پس از هر درخواستی که منجر به خطا شود نمایش داده می‌شود، به جز اقدام خرید با مقدار نادرست که توسط نما [INFOS] مدیریت می‌شود. اجزای درخواست کنترل‌کننده به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
erreurs
ArrayList آرایهٔ اشیاء String که نمایانگر پیام‌های خطایی است که باید نمایش داده شوند

کد:

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

این صفحه به‌عنوان صفحهٔ اصلی برنامه در فایل [web.xml] برنامه تعریف شده است:

<?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] به سادگی کلاینت را به نقطهٔ ورود برنامه هدایت می‌کند:

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

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

3.5.10. کنترل‌کننده

تنها کاری که باقی مانده نوشتن هستهٔ برنامهٔ وب ما است: کنترلر. نقش آن این است که:

  • درخواست مشتری را بازیابی کند،
  • پردازش اقدام درخواستی مشتری با استفاده از کلاس‌های کسب‌وکار،
  • بازگرداندن نمای مناسب در پاسخ.

3.5.10.1. ابتدایی‌سازی کنترلر

هنگامی که کلاس کنترلر توسط سرور سرولت بارگذاری می‌شود، متد [init] آن اجرا می‌گردد. این اتفاق فقط یک بار رخ می‌دهد. پس از بارگذاری در حافظه، کنترلر در آنجا باقی می‌ماند و درخواست‌های مشتریان مختلف را پردازش می‌کند. هر مشتری توسط یک نخ اجرایی مجزا پردازش می‌شود و بنابراین متدهای کنترلر به طور همزمان توسط نخ‌های مختلف اجرا می‌گردند. توجه باید داشت که به همین دلیل، کنترلر نباید هیچ فیلدی داشته باشد که متدهای آن بتوانند آن را تغییر دهند. فیلدهای آن باید فقط-خواندنی (read-only) باشند. این فیلدها توسط متد [init] که هدف اصلی آن است، مقداردهی اولیه می‌شوند. این متد از این جهت منحصر به فرد است که تنها یک بار توسط یک تار اجرایی واحد اجرا می‌شود. بنابراین در این متد هیچ مشکلی در دسترسی همزمان به فیلدهای کنترلر وجود ندارد. هدف متد [init]، مقداردهی اولیه اشیاء مورد نیاز وب‌اپلیکیشن است که به صورت فقط-خواندنی توسط تمام نخ‌های کلاینت به اشتراک گذاشته می‌شوند. این اشیاء مشترک را می‌توان در دو مکان قرار داد:

  • فیلدهای خصوصی کنترلر
  • زمینه اجرای برنامه (ServletContext)

متد [init] در اپلیکیشن [webarticles] اقدامات زیر را انجام خواهد داد:

  • فایل [web.xml] را برای پارامترهای لازم برای عملکرد صحیح برنامه بررسی می‌کند. این موارد در بخش 3.5.5 توضیح داده شده‌اند.
  • یک فیلد خصوصی [ArrayList erreurs] را با فهرستی از هرگونه خطا پر می‌کند. این فهرست در صورت عدم وجود خطا خالی خواهد بود، اما در هر صورت وجود خواهد داشت.
  • اگر خطایی رخ داده باشد، متد [init] در این نقطه متوقف می‌شود. در غیر این صورت، یک شیء از نوع [IArticlesDomain] ایجاد می‌کند که شیء تجاری مورد استفاده کنترل‌کننده برای مقاصد خود خواهد بود. همانطور که در بخش 3.5.6 توضیح داده شد، کنترلر بیانی را که نیاز دارد از فریم‌ورک Spring درخواست می‌کند. این فرآیند نمونه‌سازی ممکن است به خطاهای مختلفی منجر شود. در این صورت، این خطاها مجدداً در فیلد [erreurs] کنترلر ذخیره خواهند شد.

3.5.10.2. متدهای doGet، doPost

این دو متد درخواست‌های مشتری HTTP، GET و POST را مدیریت می‌کنند. این‌ها به‌طور متناوب پردازش خواهند شد. بنابراین روش [doPost] ممکن است درخواست را به روش [doGet] ارسال کند یا بالعکس. درخواست مشتری به شرح زیر پردازش خواهد شد:

  • میدان [erreurs] بررسی خواهد شد. اگر خالی نباشد، این بدان معناست که خطاهایی در حین راه‌اندازی برنامه رخ داده و برنامه قادر به اجرا نیست. سپس نما [ERREURS] به عنوان پاسخ ارسال خواهد شد.
  • پارامتر [action] درخواست بازیابی و بررسی می‌شود. اگر با هیچ اقدام شناخته‌شده‌ای مطابقت نداشته باشد، نما [ERREURS] همراه با یک پیام خطای مناسب ارسال می‌شود.
  • اگر پارامتر [action] معتبر باشد، درخواست مشتری برای پردازش به یک رویهٔ مخصوص اقدام ارسال می‌شود. رویهٔ مسئول اقدام [uneAction] امضای زیر را خواهد داشت:
1
2
3
4
5
6
7
8
/**
   * @param request la requête du client
   * @param response la réponse au client
   * @throws IOException
   * @throws ServletException
   */
  private void doUneAction(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException; 

3.5.10.3. پردازش اقدامات مختلف

روش‌هایی که اقدامات مختلف ممکن در برنامه را مدیریت می‌کنند به شرح زیر است:

متد
درخواست
پردازش
پاسخ‌های ممکن
doListe
GET /main?action=list
- درخواست فهرست اقلام
از کلاس تجاری
- نمایش آن
[LISTE] یا [ERREURS]
doInfos
GET /main?action=infos&id=ID
- مقاله با شناسه ID را از
کلاس بیزینس
- نمایش آن
[INFOS] یا [ERREURS]
doAchat
POST /main?action=purchase&id=ID
- مقدار خریداری‌شده در پارامترهای ارسال‌شده گنجانده شده است
- آیتم با شناسه=ID را از
کلاس تجاری
- آن را در سبد خرید در
جلسه مشتری
[LISTE] یا [INFOS]
یا [ERREURS]
doRetirerAchat
GET /main?action=remove&id=ID
- حذف آیتم با شناسه=ID از
فهرست خرید در
سشن مشتری
[PANIER]
doPanier
GET /main?action=basket
- نمایش
جلسه کلاینت
[PANIER] یا [PANIERVIDE]
doValidationPanier
GET /main?action=validate-basket
- کاهش
سطح موجودی تمام اقلام
در مشتری
سشن مشتری
[LISTE] یا [ERREURS]

3.5.10.4. کد

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

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

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

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

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

     // fields خصوصی
    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() {
         // بازیابی پارامترهای راه‌اندازی سرولت
        config = getServletConfig();
        String param = null;
        for (int i = 0; i < parameters.length; i++) {
            param = config.getInitParameter(parameters[i]);
            if (param == null) {
                 //خطا ثبت می‌شود
                erreurs.add(
                    "Paramètre ["
                        + parameters[i]
                        + "] absent dans le fichier [web.xml]");
            }
        }
         // آیا خطاهایی وجود دارد؟
        if (erreurs.size() != 0) {
            return;
        }
         //یک شیء IArticlesDomain برای دسترسی به لایه کسب‌وکار ایجاد می‌شود
        try {
            articlesDomain =
                (IArticlesDomain)
                    (
                        new XmlBeanFactory(
                            new ClassPathResource(
                                (String) config.getInitParameter(
                                    SPRING_CONFIG_FILENAME)))).getBean(
                    "articlesDomain");
        } catch (Exception ex) {
             //خطا ثبت می‌شود
            erreurs.add(
                "Erreur de configuration de l'accès aux données : "
                    + ex.toString());
            return;
        }
         // ما برخی از آدرس‌های URL برنامه را ذخیره می‌کنیم
        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);

         //کار تمام شد
        return;
    }


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

         //بررسی می‌کند که inicialization servlet چگونه انجام شد
        if (erreurs.size() != 0) {
             //آیا URL صفحهٔ خطا را داریم؟
            if (config.getInitParameter(URL_ERREURS) == null) {
                throw new ServletException(erreurs.toString());
            }
             // صفحهٔ خطا را نمایش دهید
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] {
            });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             //پایان
            return;
        }
         // پردازش اقدام
        String action = request.getParameter("action");
        if (action == null) {
             // فهرست آیتم‌ها
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_LISTE)) {
             // فهرست مقالات
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_INFOS)) {
             //جزئیات آیتم
            doInfos(request, response);
            return;
        }
        if (action.equals(ACTION_ACHAT)) {
             // خرید یک کالا
            doAchat(request, response);
            return;
        }
        if (action.equals(ACTION_PANIER)) {
             //مشاهده سبد خرید
            doPanier(request, response);
            return;
        }
        if (action.equals(ACTION_RETIRER_ACHAT)) {
             // حذف یک کالا از سبد خرید
            doRetirerAchat(request, response);
            return;
        }
        if (action.equals(ACTION_VALIDATION_PANIER)) {
             //تأیید سبد خرید
            doValidationPanier(request, response);
            return;
        }
         // اقدام نامشخص
        ArrayList erreurs = new ArrayList();
        erreurs.add("action [" + action + "] inconnue");
         //صفحهٔ خطا نمایش داده می‌شود
        request.setAttribute("actions", new Hashtable[] { hActionListe });
        afficheErreurs(request, response, erreurs);
         //پایان
        return;
    }


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

         // مشتری سبد خرید خود را تأیید کرده است
        Panier panier = (Panier) request.getSession().getAttribute("panier");
         // اعتبارسنجی این سبد
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // غیرعادی
            erreurs.add("Erreur d'accès aux données [" + ex.toString() + "]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // بازیابی خطاها
        ArrayList erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
            request.setAttribute(
                "actions",
                new Hashtable[] { hActionListe, hActionPanier });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // نمایش لیست اقلام
        request.setAttribute("message", "Votre panier a été validé");
        doListe(request, response);
         //پایان
        return;
    }


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

         // حذف یک آیتم از سبد خرید
        try {
            Panier panier =
                (Panier) request.getSession().getAttribute("panier");
            String strIdAchat = request.getParameter("id");
            panier.enlever(Integer.parseInt(strIdAchat));
        } catch (NumberFormatException ignored) {
        } catch (NullPointerException ignored) {
        }
         // نمایش سبد خرید
        doPanier(request, response);
    }


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

         // نمایش سبد خرید
        Panier panier = (Panier) request.getSession().getAttribute("panier");
         // آیا سبد خالی است؟
        if (panier == null || panier.getAchats().size() == 0) {
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_PANIER_VIDE))
                .forward(request, response);
             //پایان
            return;
        }
         // در سبد خرید چیزی وجود دارد
        request.setAttribute("panier", panier);
        request.setAttribute(
            "actions",
            new Hashtable[] { hActionListe, hActionValidationPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_PANIER))
            .forward(request, response);
         // پایان
        return;
    }


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

         // خرید یک کالا
         // بازیابی مقدار
        int qté = 0;
        try {
            qté = Integer.parseInt(request.getParameter("qte"));
            if (qté <= 0)
                throw new NumberFormatException();
        } catch (NumberFormatException ex) {
             // مقدار نادرست
            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);
             //پایان
            return;
        }
         //بازیابی جلسه مشتری
        HttpSession session = request.getSession();
         // ایجاد خرید
        Article article = (Article) session.getAttribute("article");
        Achat achat = new Achat(article, qté);
         // افزودن خرید به سبد خرید مشتری
        Panier panier = (Panier) session.getAttribute("panier");
        if (panier == null) {
            panier = new Panier();
            session.setAttribute("panier", panier);
        }
        panier.ajouter(achat);
         //بازگشت به فهرست اقلام
        String url = config.getInitParameter(URL_MAIN) + "?action=liste";
        getServletContext().getRequestDispatcher(url).forward(
            request,
            response);
         //پایان
        return;
    }


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

         // نمایش لیست اقلام
        request.setAttribute("infos", infos);
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_DEBUG))
            .forward(request, response);
         // پایان
        return;
    }


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

         // همانند get
        doGet(request, response);
    }


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

         // فهرست خطاها
        ArrayList erreurs = new ArrayList();
         //شناسهٔ درخواستی را بازیابی می‌کند
        String strId = request.getParameter("id");
         // چیزی؟
        if (strId == null) {
             // غیرعادی
            erreurs.add("action incorrecte([infos,id=null]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         //تبدیل strId به یک عدد صحیح
        int id = 0;
        try {
            id = Integer.parseInt(strId);
        } catch (Exception ex) {
             //غیرعادی
            erreurs.add("action incorrecte([infos,id=" + strId + "]");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // درخواست ورودی شناسه کلید
        Article article = null;
        try {
            article=articlesDomain.getArticleById(id);
        } catch (UncheckedAccessArticlesException ex) {
             //غیرعادی
            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) {
             // غیرعادی
            erreurs.add("Article de clé [" + id + "] inexistant");
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            afficheErreurs(request, response, erreurs);
            return;
        }
         // مورد به جلسه اضافه می‌شود
        request.getSession().setAttribute("article", article);
         //صفحه اطلاعات نمایش داده می‌شود
        request.setAttribute("actions", new Hashtable[] { hActionListe });
         /   / request.setAttribute("urlMain",config.getInitParameter(URL_MAIN));
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_INFOS))
            .forward(request, response);
         //پایان
        return;
    }

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

         // صفحهٔ خطا نمایش داده می‌شود
        request.setAttribute("erreurs", erreurs);
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
            .forward(request, response);
         // پایان
        return;
    }


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

         // فهرست خطاها
        ArrayList erreurs = new ArrayList();
         // درخواست فهرست آیتم‌ها
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             //خطا ثبت می‌شود
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
         // آیا خطایی وجود دارد؟
        if (erreurs.size() != 0) {
             // صفحهٔ خطاها را نمایش دهید
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // پایان
            return;
        }
         // نمایش لیست موارد
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);
         // پایان
        return;
    }

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

خواندن و درک این کد را به خود خواننده واگذار می‌کنیم. امیدواریم توضیحات کمک‌کننده باشند.

3.5.10.5. آزمون برنامه

بیایید به چند اسکرین‌شات از تست‌ها نگاهی بیندازیم. اول از همه، صفحهٔ اصلی برنامه:

Image

URL درخواستی در واقع [http://localhost:8080/webarticles] بود. خواننده خواهد دید که در فایل [web.xml]، ما یک صفحه اصلی برای برنامه تعریف می‌کنیم:

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

نما [index.jsp] به شرح زیر تعریف شده است:

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

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

بنابراین هدایت به URL [http://localhost:8080/webarticles/main?action=liste] انجام شد، همان‌طور که در آدرس مرورگر در تصویر اسکرین‌شات نشان داده شده است. بنابراین URL [/main?action=liste] درخواست شد. همچنان در داخل [web.xmlURL /main با servlet [webarticles] مرتبط است:

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

همچنین در [web.xmlservlet [webarticles] با servlet [ istia.st.articles.web.WebArticles] مرتبط است:

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

بنابراین، اگر سرولِت [ istia.st.articles.web.WebArticles] قبلاً بارگذاری نشده باشد، توسط کانتینر سرولِت Tomcat بارگذاری می‌شود و متد آن [init] اجرا می‌گردد:

    public void init() {
         //بازیابی پارامترهای راه‌اندازی سرولت
        config = getServletConfig();
        String param = null;
        for (int i = 0; i < parameters.length; i++) {
            param = config.getInitParameter(parameters[i]);
            if (param == null) {
                 //خطا ثبت می‌شود
                erreurs.add(
                    "Paramètre ["
                        + parameters[i]
                        + "] absent dans le fichier [web.xml]");
            }
        }
         // آیا خطایی وجود دارد؟
        if (erreurs.size() != 0) {
            return;
        }
         // یک شیء IArticlesDomain ایجاد کنید تا به لایه کسب‌وکار دسترسی پیدا کنید
        try {
            articlesDomain =
                (IArticlesDomain)
                    (
                        new XmlBeanFactory(
                            new ClassPathResource(
                                (String) config.getInitParameter(
                                    SPRING_CONFIG_FILENAME)))).getBean(
                    "articlesDomain");
        } catch (Exception ex) {
             //خطا ثبت می‌شود
            erreurs.add(
                "Erreur de configuration de l'accès aux données : "
                    + ex.toString());
            return;
        }
         // برخی از آدرس‌های URL برنامه ذخیره می‌شوند
        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);

         // کار تمام شد
        return;
    }

توضیحات: متد [init]

  • وجود برخی پارامترهای پیکربندی را بررسی می‌کند
  • یک سرویس را برای دسترسی به دامنهٔ برنامه با استفاده از Spring ایجاد می‌کند
  • یک لیست خطا را برای نشان دادن هرگونه خطای inicialization تنظیم می‌کند
  • تعدادی فیلد خصوصی:
    • errors: فهرست خطاهای شناسایی‌شده توسط [init]
    • [hActionListe, hActionPanier, hActionValidationPanier]: فرهنگ‌ها. هر یک حاوی اطلاعات لازم برای نمایش یک گزینه در منوی اصلی نمایش داده شده توسط نما [entete.jsp] است
    • acticlesDomain: سرویسی که دسترسی به مدل برنامه را فراهم می‌کند

متد [init] تنها یک بار، هنگام بارگذاری اولیه سرولت، اجرا می‌شود. پس از آن، بسته به نوع درخواست مشتری که توسط [GET, POST] مشخص می‌شود، یکی از متدهای [doGet, doPost] اجرا می‌گردد. در اینجا، هر دو متد کار یکسانی انجام می‌دهند و کد در [doGet] قرار داده شده است:

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {
         //بررسی نحوهٔ راه‌اندازی سرولت
        if (erreurs.size() != 0) {
             //آیا URL صفحه خطا را داریم؟
            if (config.getInitParameter(URL_ERREURS) == null) {
                throw new ServletException(erreurs.toString());
            }
             // صفحهٔ خطا را نمایش دهید
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] {
            });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             //پایان
            return;
        }
         // پردازش اقدام
        String action = request.getParameter("action");
        if (action == null) {
             // فهرست آیتم‌ها
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_LISTE)) {
             // فهرست مقالات
            doListe(request, response);
            return;
        }
        if (action.equals(ACTION_INFOS)) {
             //جزئیات آیتم
            doInfos(request, response);
            return;
        }
        if (action.equals(ACTION_ACHAT)) {
             // خرید یک کالا
            doAchat(request, response);
            return;
        }
        if (action.equals(ACTION_PANIER)) {
             //مشاهده سبد خرید
            doPanier(request, response);
            return;
        }
        if (action.equals(ACTION_RETIRER_ACHAT)) {
             // حذف یک کالا از سبد خرید
            doRetirerAchat(request, response);
            return;
        }
        if (action.equals(ACTION_VALIDATION_PANIER)) {
             //تأیید سبد خرید
            doValidationPanier(request, response);
            return;
        }
         // اقدام نامشخص
        ArrayList erreurs = new ArrayList();
        erreurs.add("action [" + action + "] inconnue");
         // صفحهٔ خطا نمایش داده می‌شود
        request.setAttribute("actions", new Hashtable[] { hActionListe });
        afficheErreurs(request, response, erreurs);
         // پایان
        return;
    }
  • متد [doGet] با بررسی اینکه آیا خطاهای инициализатсиون (راه‌اندازی) پس از اجرای متد [init] رخ داده است، شروع می‌شود. در صورت وجود خطا، ویوی [ERREURS] را نمایش می‌دهد و فرآیند به پایان می‌رسد.
  • در غیر این صورت، پارامتر [action] را از درخواست کلاینت بازیابی می‌کند. شایان ذکر است که این برنامه برای پاسخ به درخواست‌هایی طراحی شده است که باید حاوی پارامتر [action] باشند.
  • متد مرتبط با اقدام را اجرا می‌کند. در این مورد، این متد [doListe] خواهد بود.

متد [doListe] به شرح زیر است:

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

         // فهرست خطاها
        ArrayList erreurs = new ArrayList();
         // درخواست فهرست آیتم‌ها
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // ذخیره خطا
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
         // آیا خطایی وجود دارد؟
        if (erreurs.size() != 0) {
             // صفحهٔ خطاها را نمایش دهید
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // پایان
            return;
        }
         // نمایش لیست آیتم‌ها
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);
         // پایان
        return;
    }
  • به یاد داشته باشید که متد [init] سرویس دسترسی به مدل برنامه (لایه دامنه) را در یک فیلد خصوصی از سرولت ذخیره کرده است:
     // fields خصوصی
    private IArticlesDomain articlesDomain = null;
  • با استفاده از این سرویس دسترسی، می‌توانیم فهرست مقالات را درخواست کنیم:
         // فهرست خطاها
        ArrayList erreurs = new ArrayList();
         // درخواست فهرست آیتم‌ها
        List articles = null;
        try {
            articles = articlesDomain.getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             // ذخیره خطا
            erreurs.add(
                "Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
        }
  • در صورت بروز هرگونه خطا، نمای [ERREURS] بازگردانده می‌شود:
         // آیا خطایی وجود دارد؟
        if (erreurs.size() != 0) {
             // نمایش صفحهٔ خطاها
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { hActionListe });
            getServletContext()
                .getRequestDispatcher(config.getInitParameter(URL_ERREURS))
                .forward(request, response);
             // پایان
            return;
        }
  • در غیر این صورت، نما [LISTE] بازگردانده می‌شود:
1
2
3
4
5
6
7
         // نمایش لیست آیتم‌ها
        request.setAttribute("listarticles", articles);
        request.setAttribute("message","");
        request.setAttribute("actions", new Hashtable[] { hActionPanier });
        getServletContext()
            .getRequestDispatcher(config.getInitParameter(URL_LISTE))
            .forward(request, response);

در مثال ما، همه چیز به خوبی پیش رفت و ما با موفقیت نما [LISTE] را دریافت کردیم. از خوانندگان دعوت می‌شود تا کد مربوط به نما [LISTE] را بررسی کنند تا ببینند که پارامترهای پویا مورد نیاز این نما در واقع توسط کنترلر در بالا فراهم شده‌اند. همین نوع بررسی باید برای هر نما انجام شود:

  • شناسایی پارامترهای پویاِ نما
  • اطمینان حاصل کنید که کنترلر واقعاً این‌ها را در ویژگی‌های درخواست که به ویو ارسال می‌شود، گنجانده است

اکنون به سادگی توالی صفحاتی را که کاربر اپلیکیشن با آن مواجه می‌شود، تشریح می‌کنیم. از خواننده دعوت می‌شود که هر بار همان خط استدلال قبلی را به کار ببرد:

کاربر می‌تواند از فهرست مقالات، یک مقاله را انتخاب کند:

خریدار می‌تواند کالای شمارهٔ ۳ را از اینجا خریداری کند. بیایید یک اشتباه تایپی در مقدار وارد کنیم:

خطا علامت‌گذاری شده است. اکنون، بیایید چند قلم بخرم:

خرید ثبت شد و فهرست اقلام به‌روزرسانی شد. بیایید سبد خرید را بررسی کنیم:

این کالا واقعاً در سبد خرید قرار دارد. بیایید آن را حذف کنیم:

این کالا از سبد خرید حذف شد و سبد خرید تازه شد. اکنون خالی است.

بیایید ۱۰۰ عدد از کالا شماره ۳ و ۲ عدد از کالا شماره ۴ بخریم:

خرید کالا شماره ۳ امکان‌پذیر نبود زیرا ما قصد خرید ۱۰۰ عدد از آن را داشتیم و تنها ۳۰ عدد در انبار موجود بود. این کالا در سبد خرید باقی ماند:

با این حال، کالای شماره ۴ خریداری شد، همان‌طور که از سطح موجودی جدید آن که ۳۹ عدد است (۴۰–۱) مشخص است:

3.6. معماری MVC با Struts

3.6.1. معماری کلی برنامه

بیایید نگاهی دیگر به معماری برنامه (MVC) بیندازیم:

در نسخه قبلی:

  • کنترل‌کننده توسط یک سرولت مدیریت می‌شد
  • ویوها توسط صفحات JSP مدیریت می‌شدند
  • مدل توسط مجموعه‌ای از سه فایل .jar مدیریت می‌شد

در نسخه Struts:

  • کنترل‌کننده توسط یک سرویس‌لت مشتق‌شده از سرویس‌لت عمومی Struts به آدرس [ActionServlet] مدیریت خواهد شد
  • ویوها مانند قبل توسط همان صفحات JSP مدیریت خواهند شد، با چند تفاوت جزئی
  • مدل توسط همان سه آرشیو پیاده‌سازی خواهد شد

ما خواهیم دید که مهاجرت برنامه قبلی به Struts شامل کارهای زیر است:

  • اقداماتی که قبلاً در متدهای مشخصی از servlet/controller انجام می‌شدند، اکنون توسط نمونه‌هایی از کلاس‌هایی که از کلاس Struts به شماره [Action] مشتق شده‌اند، انجام می‌شوند.
  • نوشتن فایل‌های پیکربندی [web.xml] و [struts-config.xml]
  • برخی تغییرات در صفحات JSP اعمال شود

بیایید معماری عمومی MVC را که توسط STRUTS استفاده می‌شود، به یاد آوریم:

M=modèle
کلاس‌های کسب‌وکار، کلاس‌های دسترسی به داده و پایگاه داده
V=vues
صفحات JSP
C=contrôleur
سرولتی برای پردازش درخواست‌های مشتری، اشیاء [Action] و بین‌های [ActionForm] مرتبط با فرم‌ها.
  • کنترلر قلب برنامه است. تمام درخواست‌های کلاینت از آن عبور می‌کنند. این یک سرولت عمومی است که توسط STRUTS ارائه می‌شود. در برخی موارد، ممکن است لازم باشد که از آن ارث‌بری شود. برای موارد ساده، این کار ضروری نیست. این سرولت عمومی اطلاعات مورد نیاز خود را از فایلی به نام struts-config.xml بازیابی می‌کند.
  • اگر درخواست کلاینت شامل پارامترهای فرم باشد، این پارامترها توسط کنترلر در یک شی Bean قرار داده می‌شوند. شی‌های Bean که به این روش ایجاد می‌شوند، به مرور زمان در جلسه (session) یا درخواست (request) کلاینت ذخیره می‌گردند. این رفتار قابل پیکربندی است. اگر قبلاً ایجاد شده باشند، نیازی به ایجاد مجدد آن‌ها نیست.
  • در فایل پیکربندی struts-config.xml، اطلاعات خاصی با هر URL که باید توسط برنامه پردازش شود مرتبط می‌گردد (و بنابراین با یک JSP ویو که می‌تواند مستقیماً درخواست شود مطابقت ندارد):
    • نام کلاس Action مسئول رسیدگی به درخواست. در اینجا نیز، شیء Action ایجادشده ممکن است در جلسه (session) یا در درخواست (request) نگهداری شود.
    • اگر URL درخواستی پارامتریک باشد (مانند زمانی که یک فرم به کنترلر ارسال می‌شود)، نام بین (bean) مسئول ذخیره داده‌های فرم مشخص می‌شود.
  • کنترل‌کننده، با در دست داشتن این اطلاعات که توسط فایل پیکربندی آن فراهم شده است، هنگام دریافت درخواستی برای URL از یک کلاینت، می‌تواند تشخیص دهد که آیا نیاز به ایجاد یک بین (bean) وجود دارد و در صورت وجود، کدام یک. پس از ایجاد نمونه، آن بین می‌تواند بررسی کند که آیا داده‌هایی که ذخیره کرده است—که از فرم منشأ می‌گیرند—معتبر هستند یا خیر. یک متد در بین با نام `validate` به طور خودکار توسط کنترلر فراخوانی می‌شود. بین توسط توسعه‌دهنده ایجاد می‌شود، بنابراین توسعه‌دهنده کد بررسی اعتبار داده‌های فرم را در داخل متد `validate` قرار می‌دهد. اگر داده‌ها نامعتبر تشخیص داده شوند، کنترلر به کار خود ادامه نخواهد داد. کنترل را به یک ویو (view) واگذار می‌کند که نام آن را در فایل پیکربندی خود پیدا می‌کند. در این صورت تعامل کامل می‌شود. شایان ذکر است که توسعه‌دهنده می‌تواند انتخاب کند که فرم اعتبارسنجی نشود. این کار نیز در فایل struts-config.xml انجام می‌شود. در این صورت، کنترلر متد `validate` بیان را فراخوانی نمی‌کند.
  • اگر داده‌های بین صحیح باشد، یا اگر نیازی به اعتبارسنجی نباشد، یا اگر بین وجود نداشته باشد، کنترلر کنترل را به شیء از نوع Action که با URL مرتبط است، واگذار می‌کند. این کار را با فراخوانی متد `execute` این شیء و ارسال مرجع بهان (bean) که ممکن است ساخته باشد، انجام می‌دهد. در اینجا توسعه‌دهنده وظایف لازم را انجام می‌دهد: ممکن است نیاز باشد که کلاس‌های منطق کسب‌وکار یا کلاس‌های دسترسی به داده‌ها را فراخوانی کند. در پایان پردازش، شیء Action نام ویوی (view) را که باید در پاسخ به کلاینت ارسال کند، به کنترلر بازمی‌گرداند.
  • کنترلر در فایل پیکربندی خود، URL مرتبط با نام ویوی که از او خواسته شده نمایش دهد را پیدا می‌کند. سپس این ویو را ارسال می‌کند. تعامل با کلاینت کامل می‌شود.

در برنامه کاربردی ما، از اشیاء [Bean] به عنوان اشیاء بافر بین کلاینت و کلاس‌های [Action] استفاده نخواهیم کرد. ابژه [Action] پارامترهای درخواست کلاینت را مستقیماً از ابژه [HttpServletRequest] که دریافت می‌کند، بازیابی خواهد کرد. این کار انتقال (پورت) برنامه اصلی ما را آسان‌تر می‌کند. بنابراین، معماری نهایی برنامه ما به شرح زیر خواهد بود:

M=modèle
کلاس‌های کسب‌وکار، کلاس‌های دسترسی به داده و پایگاه داده
V=vues
صفحات JSP
C=contrôleur
سرولتی برای پردازش درخواست‌های مشتری، اشیاء [Action]

3.6.2. مدل

این قبلاً ارائه شده بود. شامل آرشیوهای جاوا [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception] است.

3.6.3. پیکربندی برنامه

3.6.3.1. معماری کلی

معماری کلی پروژه Eclipse به شرح زیر است:

Image

3.6.3.2. پیکربندی دسترسی به داده‌ها

از آنجایی که رابط دسترسی به داده‌ها بدون تغییر باقی می‌ماند، فایل‌های پیکربندی مربوطه نیز مانند نسخه قبلی هستند. این فایل‌ها در [WEB-INF/src] تعریف شده‌اند:

Image

در تصویر بالا، فایل‌های [articles.xml, spring-config-sqlmap-firebird.xml, sqlmap-config-firebird.xml, log4j.properties] مربوط به نسخه قبلی هستند.

3.6.3.3. زیرپوشه آرشیو

در [WEB-INF/lib]، همان آرشیو‌های نسخه قبلی را به علاوه آرشیوی که توسط Struts مورد نیاز است، خواهید یافت:

Image

3.6.3.4. پیکربندی برنامه

برنامه با استفاده از دو فایل پیکربندی می‌شود: [web.xml, struts-config.xml] در پوشه [WEB-INF]:

Image

فایل [web.xml] به شرح زیر است:

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

این فایل چه می‌گوید؟

  • صفحهٔ اصلی برنامه [vues/index.jsp] (فایل خوش‌آمدگویی) است
  • درخواست‌های URL با فرمت *.do به سرولت [strutswebarticles] (servlet-mapping) هدایت خواهند شد
  • سرولِت [strutswebarticles] نمونه‌ای از کلاس [ istia.st.articles.web.struts.MainServlet] است (نام-سرولِت، کلاس-سرولِت)
  • این سرولت دو پارامتر راه‌اندازی را می‌پذیرد
    • نام فایل پیکربندی Struts (config)
    • نام فایل پیکربندی Spring (springConfigFileName)

فایل [struts-config.xml] به شرح زیر است:

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

این فایل پیکربندی چه چیزی را مشخص می‌کند؟

  • که کنترلر ما آدرس‌های URL زیر را مدیریت خواهد کرد:
main.do
برای نمایش فهرست مقالات
liste.do
برای نمایش فهرست مقالات
infos.do
برای مشاهده اطلاعات یک آیتم خاص
achat.do
برای خرید یک آیتم مشخص
panier.do
برای مشاهده سبد خرید
retirerachat.do
حذف یک کالا از سبد خرید
validerpanier.do
برای تأیید سبد خرید
  • اقدامات فوق به صورت یک‌به‌یک با اقداماتی که در نسخه قبلی توسط سرولت انجام می‌شد، مطابقت دارند. برای هر یک از آن‌ها، اطلاعات زیر ارائه شده است:
  • نام کلاسی که مسئول رسیدگی به این اقدام است
  • پاسخ‌های ممکن (= نماها) پس از پردازش اقدام. تنها یکی از این موارد توسط کنترل‌کننده انتخاب خواهد شد.
  • نام یک فایل پیام برای برنامه (message-resources). در اینجا، این فایل وجود خواهد داشت اما خالی خواهد بود. از آن استفاده نخواهد شد. باید در دایرکتوری [ClassPath] برنامه قرار داده شود. در اینجا، در [WEB-INF/classes] قرار داده خواهد شد. در اکلیپس، این نتیجه با قرار دادن آن در [WEB-INF/src] حاصل می‌شود:

Image

3.6.4. ویوهای JSP

ویوهای JSP که در اینجا استفاده شده‌اند نیز از نسخه قبلی هستند. تغییرات بسیار کمی اعمال شده است: این تغییرات مربوط به آدرس‌های URL از نوع [?action=XX?id=YY& ...] است که به [/XX.do?id=YY&....] تبدیل می‌شوند. ما توضیحات ارائه‌شده قبلی را برای جلوگیری از نیاز کاربر به بازگشت، مجدداً تکرار می‌کنیم. مهم است بدانید که اطلاعاتی که توسط کنترلر به ویو ارسال می‌شود، در هر دو نسخه دقیقاً یکسان است. در این زمینه هیچ تغییری ایجاد نشده است.

3.6.4.1. entete.jsp

برای حفظ یکنواختی در نماهای مختلف، همه آنها از یک هدر یکسان استفاده خواهند کرد که نام برنامه را در کنار منو نمایش می‌دهد:

منو پویا است و توسط کنترلر تعریف می‌شود. کنترلر در درخواست ارسال‌شده به صفحه JSP، یک ویژگی کلید «actions» با مقدار مرتبط از نوع آرایه Hastable[] را شامل می‌شود. هر عنصر این آرایه یک دیکشنری است که برای ایجاد یک گزینه منو در هدر در نظر گرفته شده است. هر دیکشنری دو کلید دارد:

  • href: URL مرتبط با گزینه منو
  • link: متن منو

ویوهای دیگر در برنامه از هدر تعریف‌شده توسط [entete.jsp] با استفاده از تگ JSP زیر استفاده خواهند کرد:

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

هنگام اجرا، این تگ کد صفحه [entete.jsp] را در کد صفحه JSP که حاوی آن است، درج خواهد کرد. از آنجا که URL صفحه یک URL نسبی است (بدون «/»)، صفحه [entete.jsp] در همان پوشه‌ای که صفحه حاوی تگ <jsp:include> در آن قرار دارد، جستجو خواهد شد.

کد:

<%@ 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.6.4.2. liste.jsp

این نما فهرست اقلام موجود برای فروش را نمایش می‌دهد:

این نما پس از یک پرس‌وجوی مانند /main.do یا /validerpanier.do نمایش داده می‌شود. عناصر پرس‌وجوی کنترل‌کننده به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
listarticles
ابجکت‌های ArrayList از نوع [Article]
message
شیء رشته‌ای – پیامی که در پایین صفحه نمایش داده می‌شود

هر لینک [Infos] در آرایه HTML مقالات دارای URL به شکل [/infos.do?id=ID] است، که در آن ID فیلد ID مقاله نمایش‌داده‌شده است.

کد:

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

توضیحات: یک تغییر (در بالا برجسته شده)

3.6.4.3. infos.jsp

این نما اطلاعاتی در مورد یک آیتم نمایش می‌دهد و همچنین امکان خرید آن را فراهم می‌کند:

Image

این صفحه پس از درخواست /infos.do?id=ID یا درخواست /achat.do?id=ID زمانی که مقدار خریداری‌شده نادرست باشد، نمایش داده می‌شود. عناصر درخواست کنترل‌کننده به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
article
شیء از نوع [Article] – موردی که باید نمایش داده شود
msg
ابژه رشته‌ای – پیامی که در صورت خطای مقدار نمایش داده می‌شود
qte
شیء رشته‌ای – مقداری که باید در فیلد ورودی [Qte] نمایش داده شود

میدان‌های [msg] و [qte] در صورت بروز خطای ورودی در مورد مقدار استفاده می‌شوند:

Image

این صفحه شامل فرمى است که از طریق دکمه [Acheter] ارسال مى‌شود. URL هدف براى POST، [/achat.do?id=ID] است، که در آن ID شناسه کالاى خريدارى‌شده مى‌باشد.

کد:

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

توضیحات: یک تغییر (در بالا برجسته شده)

3.6.4.4. panier.jsp

این نما محتویات سبد خرید را نمایش می‌دهد:

Image

این صفحه پس از درخواست‌هایی مانند /panier.do یا /retirerachat.do?id=ID نمایش داده می‌شود. اجزای درخواست کنترل‌کننده به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
panier
شیء از نوع [Panier] – سبد خریدی که باید نمایش داده شود

هر لینک [Retirer] در آرایه HTML اقلام سبد خرید، دارای یک URL به شکل [retirerachat.do?id=ID] است، که در آن ID فیلد [id] ازموردی که می‌خواهیم از سبد خرید حذف کنیم.

کد:

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

توضیحات: یک تغییر (در بالا برجسته شده)

3.6.4.5. paniervide.jsp

این نما اطلاعاتی را نمایش می‌دهد که نشان می‌دهد سبد خالی است:

Image

این نما پس از درخواست /panier.do یا /retirerachat.do?id=ID نمایش داده می‌شود. اجزای درخواست کنترل‌کننده به شرح زیر است:

actions
شیء Hashtable[] – آرایهٔ گزینه‌های منو

کد:

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

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

توضیحات: بدون تغییر.

3.6.4.6. erreurs.jsp

این نما در صورت بروز خطاها نمایش داده می‌شود:

Image

این صفحه پس از هر درخواستی که منجر به خطا شود نمایش داده می‌شود، به جز اقدام خرید با مقدار نادرست که توسط نما [INFOS] مدیریت می‌شود. عناصر درخواست کنترل‌کننده به شرح زیر است:

actions
شیء Hashtable[] – آرایه گزینه‌های منو
erreurs
ArrayList – آرایه‌ای از اشیاء String که نمایانگر پیام‌های خطایی هستند که باید نمایش داده شوند

کد:

<%@ 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.6.4.7. index.jsp

این صفحه به‌عنوان صفحهٔ اصلی برنامه در فایل [web.xml] برنامه تعریف شده است:

<?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] به سادگی کلاینت را به نقطه ورود برنامه هدایت می‌کند:

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

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

توضیحات: یک تغییر (در بالا برجسته شده)

3.6.5. کنترل‌کننده Struts

Struts دارای یک کنترل‌کنندهٔ عمومی به نام [ActionServlet] است. ما می‌دانیم که یک سرولت دارای متدی به نام [init] است که به برنامه اجازه می‌دهد هنگام راه‌اندازی اولیه شود. اگر از کنترلر عمومی Struts به نام [ActionServlet] استفاده کنیم، به متد آن به نام [init] دسترسی نخواهیم داشت. در اینجا، وظایفی داریم که هنگام راه‌اندازی برنامه باید انجام دهیم، که اساساً شامل ایجاد یک شیء دسترسی به مدل است. بنابراین به متدی به نام [init] نیاز داریم. در نتیجه، کلاس [Actionservlet] را از کلاس زیر [MainServlet] مشتق می‌کنیم:

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 {
   // fields خصوصی
  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);

     // گیرنده و تنظیم‌کننده
  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{

     // ابتدایی‌سازی کلاس والد
    super.init();
     // بازیابی پارامترهای راه‌اندازی سرولت
    config = getServletConfig();
    String param = null;
    for (int i = 0; i < parameters.length; i++) {
      param = config.getInitParameter(parameters[i]);
      if (param == null) {
         //ثبت خطا
        erreurs.add("Paramètre [" + parameters[i]
            + "] absent dans le fichier [web.xml]");
      }
    }
     // آیا خطایی وجود دارد؟
    if (erreurs.size() != 0) {
      return;
    }
     // یک شیء IArticlesDomain ایجاد کنید تا به لایه کسب‌وکار دسترسی پیدا کنید
    try {
      articlesDomain = (IArticlesDomain) (new XmlBeanFactory(
          new ClassPathResource((String) config
              .getInitParameter(SPRING_CONFIG_FILENAME))))
          .getBean("articlesDomain");
    } catch (Exception ex) {
       //خطا ثبت می‌شود
      erreurs.add("Erreur de configuration de l'accès aux données : "
          + ex.toString());
      return;
    }
     // برخی از آدرس‌های URL برنامه ذخیره می‌شوند
    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);

     //کار تمام شد
    return;
  }
}

توضیحات:

  • ارزش این کلاس در متد [init] و فیلدهای خصوصی آن نهفته است
  • متد [init] همان کاری را انجام می‌دهد که متد [init] در کنترلر نسخه قبلی انجام می‌داد:
    • وجود پارامترهای پیکربندی خاصی را بررسی می‌کند
    • یک سرویس را برای دسترسی به دامنهٔ برنامه با استفاده از Spring ایجاد می‌کند
    • یک لیست خطا را برای نشان دادن هرگونه خطای инициализация تنظیم می‌کند
  • قبل از شروع کار، متد [init] متد [init] از کلاس والد [ActionServlet] را فراخوانی می‌کند. این متد است که فایل پیکربندی Struts [struts-config.xml] را پردازش خواهد کرد.
  • تعدادی فیلد خصوصی به همراه دسترسی‌دهنده‌هایشان تعریف شده‌اند:
    • errors: لیست خطاهای شناسایی‌شده توسط [init]
    • [hActionListe, hActionPanier, hActionValidationPanier]: فرهنگ‌ها. هر یک حاوی اطلاعات لازم برای نمایش یک گزینه در منوی اصلی نمایش داده شده توسط نما [entete.jsp] است
    • acticlesDomain: سرویسی که دسترسی به مدل برنامه را فراهم می‌کند
  • کنترل‌کننده یک برنامه Struts برای کلاس‌های [Action] که مسئول رسیدگی به اقدامات مختلف ممکن هستند، قابل دسترسی است. این کلاس‌ها به فیلدهای خصوصی مذکور دسترسی خواهند داشت، زیرا این فیلدها دارای دسترسی‌دهنده‌های عمومی هستند.

این کنترلر توسط فایل [web.xml] ایجاد می‌شود:

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

هر فایل URL که با .do پایان یابد، توسط یک نمونه از کلاس [istia.st.articles.web.struts.MainServlet] پردازش خواهد شد

3.6.6. اقدامات برنامه Struts

3.6.6.1. مقدمه

هر اکشن Struts به صورت یک کلاس پیاده‌سازی می‌شود. در نسخه قبلی، هر اکشن به صورت یک متد درون کنترلر برنامه پیاده‌سازی می‌شد. نوشتن کلاس [Action] معمولاً شامل موارد زیر است:

  • کپی و پیست متدی که در نسخه قبلی استفاده شده بود
  • تطبیق کد با قراردادهای Struts

3.6.6.2. main.do, liste.do

این دو اکشن یکسان هستند و در کلاس [struts-config.xml] به شرح زیر تعریف شده‌اند:

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

وقتی یکی از این اکشن‌ها، [main.do, liste.do]، در یک مرورگر اجرا می‌شود، نتیجه زیر به دست می‌آید:

Image

کد کلاس [ListeArticlesAction] به شرح زیر است:

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 {

     // سرولِت کنترل
    MainServlet mainServlet = (MainServlet) this.getServlet();

     // خطاهای инициализация؟
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       //صفحهٔ خطاها نمایش داده می‌شود
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }

     // شیء دسترسی دامنه
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();

     // فهرست خطاها
    ArrayList erreurs = new ArrayList();
     // درخواست فهرست آیتم‌ها
    List articles = null;
    try {
      articles = articlesDomain.getAllArticles();
    } catch (UncheckedAccessArticlesException ex) {
       //خطا ثبت می‌شود
      erreurs.add("Erreur lors de l'obtention de tous les articles : "
          + ex.toString());
    }
     // آیا خطایی وجود دارد؟
    if (erreurs.size() != 0) {
       // نمایش صفحهٔ خطاها
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // نمایش فهرست مقالات
    request.setAttribute("listarticles", articles);
    request.setAttribute("message", "");
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionPanier() });
    return mapping.findForward("afficherListeArticles");
  }
}

توضیحات:

  • نوشتن کد برای کلاس [Action] اساساً شامل نوشتن کد برای متد [execute] آن است
  • مقدار مشخصی از اطلاعات در نمونهٔ کنترل‌کننده ذخیره شده است. یک مرجع به آن با استفاده از: بازیابی می‌شود:
     // سرولِت کنترل
    MainServlet mainServlet = (MainServlet) this.getServlet();
  • ما لیست خطاهای инициализация ذخیره‌شده توسط کنترلر را بازیابی می‌کنیم. اگر این لیست خالی نباشد، نما [ERREURS] به کلاینت ارسال می‌شود:
1
2
3
4
5
6
7
8
     // خطاهای инициализация؟
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       //صفحهٔ خطا نمایش داده می‌شود
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }

نمائی که در واقع به کلاینت ارسال می‌شود توسط [struts-config.xml] ارائه می‌شود:

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

این نما [/vues/erreurs.jsp] است. از خواننده دعوت می‌شود تا بررسی کند که از این نما چه انتظاری می‌رود. این اطلاعات در اینجا توسط اکشن در شیء [request] به‌عنوان ویژگی‌ها ارائه شده است.

  • بار دیگر، از طریق کنترلر، اکشن می‌تواند شیئی را بازیابی کند که دسترسی به مدل برنامه (لایه دامنه) را فراهم می‌کند:
     // ابژه دسترسی دامنه
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
  • پس از انجام این کار، می‌توانیم لیست موارد را بازیابی کنیم:
     // فهرست خطاها
    ArrayList erreurs = new ArrayList();
     // درخواست فهرست آیتم‌ها
    List articles = null;
    try {
      articles = articlesDomain.getAllArticles();
    } catch (UncheckedAccessArticlesException ex) {
       //خطا ثبت می‌شود
      erreurs.add("Erreur lors de l'obtention de tous les articles : "
          + ex.toString());
    }
  • اگر خطایی رخ دهد، نمای [ERREURS] نمایش داده می‌شود:
1
2
3
4
5
6
7
8
     // آیا خطایی وجود دارد؟
    if (erreurs.size() != 0) {
       // صفحهٔ خطاها را نمایش می‌دهد
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
  • در غیر این صورت، نما [LISTE] بازگردانده می‌شود:
1
2
3
4
5
6
     // نمایش فهرست مقالات
    request.setAttribute("listarticles", articles);
    request.setAttribute("message", "");
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionPanier() });
    return mapping.findForward("afficherListeArticles");

نمایانی که در واقع به کلاینت ارسال می‌شود توسط [struts-config.xml] ارائه می‌شود:

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

این نما [/vues/liste.jsp] است. از خواننده دعوت می‌شود تا بررسی کند که از این نما چه انتظاری می‌رود. این اطلاعات در اینجا توسط عمل در شیء [request] به‌عنوان ویژگی‌ها ارائه شده است.

3.6.6.3. infos.do

این اقدام برای ارائه اطلاعات در مورد یکی از موارد نمایش داده شده در نما [LISTE] استفاده می‌شود:

این اقدام در [struts-config.xml] به شرح زیر پیکربندی شده است:

        <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] به شرح زیر است:

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 {

     // سرولت کنترل
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // خطاهای инициализация؟
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       //صفحهٔ خطا نمایش داده می‌شود
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // شیء دسترسی دامنه
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
     // فهرست خطاها
    ArrayList erreurs = new ArrayList();
     // بازیابی شناسهٔ درخواست‌شده
    String strId = request.getParameter("id");
     // چیزی؟
    if (strId == null) {
       // غیرعادی
      erreurs.add("action incorrecte([infos,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     //تبدیل strId به یک عدد صحیح
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       //غیرعادی
      erreurs.add("action incorrecte([infos,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // درخواست ورودی شناسه کلید
    Article article = null;
    try {
      article = articlesDomain.getArticleById(id);
    } catch (UncheckedAccessArticlesException ex) {
       //غیرعادی
      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) {
       // غیرعادی
      erreurs.add("Article de clé [" + id + "] inexistant");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // مورد به جلسه اضافه می‌شود
    request.getSession().setAttribute("article", article);
     //نمایش صفحه اطلاعات
    request.setAttribute("actions", new Hashtable[] { mainServlet
        .getHActionListe() });
    return mapping.findForward("afficherInfosArticle");
  }
}

توضیحات:

  • شروع متد [execute] با مورد بررسی‌شده قبلی یکسان است. این وضعیت برای سایر عملیات نیز صادق خواهد بود.
  • این متد پارامتر [id] را که معمولاً باید در URL وجود داشته باشد، بازیابی می‌کند. در واقع، URL باید به شکل [/infos.do?id=X] باشد. بررسی‌های مختلفی برای تأیید وجود و اعتبار پارامتر [id] انجام می‌شود. اگر مشکلی وجود داشته باشد، نمای [ERREURS] بازگردانده می‌شود.
  • اگر [id] معتبر باشد، آیتم متناظر از لایه [domain] درخواست می‌شود. اگر این عملیات خطایی (exception) ایجاد کند یا آیتم پیدا نشود، نمای [ERREURS] یک بار دیگر ارسال می‌شود.
  • اگر همه چیز به درستی پیش برود، آیتم بازیابی‌شده در جلسه (session) قرار داده می‌شود. این یک نکته قابل بحث است. در اینجا، ما فرض می‌کنیم که مشتری ممکن است بخواهد این آیتم را خریداری کند. اگر چنین باشد، ما آن را از جلسه بازیابی خواهیم کرد، به جای اینکه دوباره از لایه [domain] درخواست کنیم.
  • در نهایت، نمای [INFOS] نمایش داده می‌شود. نمایی که در واقع به کلاینت ارسال خواهد شد توسط [struts-config.xml] ارائه می‌شود:
        <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>

این نما [/vues/infos.jsp] است. از خواننده دعوت می‌شود تا بررسی کند که از این نما چه انتظاری می‌رود. این اطلاعات در اینجا توسط عمل در شیء [request] به‌عنوان ویژگی‌ها ارائه شده است.

3.6.6.4. achat.do

این اقدام برای خرید آیتمی که توسط نمای قبلی [INFOS] نمایش داده شده است، استفاده می‌شود:

پس از خرید آیتم، نمای [LISTE] دوباره نمایش داده می‌شود (نمای سمت راست). اگر به کد HTML در نمای سمت چپ بالا نگاه کنیم، می‌بینیم که تگ <form> به صورت زیر تعریف شده است:

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

بنابراین می‌توانیم ببینیم که فرم با استفاده از اکشن [achat.do] به کنترلر ارسال می‌شود.

این اکشن در [struts-config.xml] به صورت زیر پیکربندی شده است:

        <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] به شرح زیر است:

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 {

     // سرولِت کنترل
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // خطاهای инициализация؟
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // نمایش صفحه خطاها
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     // بازیابی مقدار خریداری‌شده
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       //مقدار نادرست
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherInfosArticle");
    }
     // جلسه مشتری را بازیابی می‌کند
    HttpSession session = request.getSession();
     //بازیابی آیتم اضافه شده به جلسه
    Article article = (Article) session.getAttribute("article");
     // جلسه منقضی شده است؟
    if(article==null){
       // نمایش صفحهٔ خطا
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
     // ایجاد خرید جدید
    Achat achat = new Achat(article, qté);
     // افزودن خرید به سبد خرید مشتری
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
     //بازگشت به فهرست اقلام
    return mapping.findForward("afficherListeArticles");
  }
}

توضیحات:

  • شروع متد [execute] با نمونه‌های بررسی‌شده قبلی یکسان است.
  • بیایید فرمت فرم ارسال‌شده به کنترلر را به یاد بیاوریم:
1
2
3
4
5
6
7
8
9
        <form method="post" action="achat.do?id=3"/>
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qte <input type="text" name="qte" size="3" value=""></td>
                    <td></td>
                </tr>
            </table>
        </form>
  • در این درخواست دو پارامتر وجود دارد: [id]: شماره آیتم خریداری‌شده؛ [qte]: مقدار خریداری‌شده.
  • وجود و اعتبار پارامتر [qte] بررسی می‌شود. اگر این پارامتر نادرست تشخیص داده شود، نما [INFOS] همراه با یک پیام خطا به کاربر بازگردانده می‌شود:
     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     // بازیابی مقدار خریداری‌شده
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // مقدار نادرست
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherInfosArticle");
    }
  • مورد خریداری‌شده از جلسه بازیابی می‌شود. ممکن است جلسه منقضی شده باشد. در این صورت، نما [ERREURS] بازگردانده می‌شود:
     // بازیابی جلسه مشتری
    HttpSession session = request.getSession();
     // بازیابی آیتم اضافه شده به جلسه
    Article article = (Article) session.getAttribute("article");
     // آیا جلسه منقضی شده است؟
    if(article==null){
       // صفحه خطا را نمایش می‌دهد
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
  • اگر جلسه منقضی نشده باشد، آیتم در سبد خرید قرار می‌گیرد که آن نیز از جلسه بازیابی می‌شود:
     //بازیابی جلسهٔ مشتری
    HttpSession session = request.getSession();
     //بازیابی آیتم اضافه شده به جلسه
    Article article = (Article) session.getAttribute("article");
     // جلسه منقضی شده است؟
    if(article==null){
       //صفحهٔ خطا را نمایش می‌دهد
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");      
    }
     // خرید جدید را ایجاد می‌کند
    Achat achat = new Achat(article, qté);
     // افزودن خرید به سبد خرید مشتری
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
  • در نهایت، نمای [LISTE] ارسال می‌شود:
     //بازگشت به فهرست اقلام
    return mapping.findForward("afficherListeArticles");
  • نمائی که در واقع به کلاینت ارسال می‌شود توسط [struts-config.xml] ارائه می‌شود:
        <action 
            path="/achat" type="istia.st.articles.web.struts.AchatArticleAction">
            <forward name="afficherInfosArticle" path="/vues/infos.jsp"/>
            <forward name="afficherListeArticles" path="/main.do"/>
        </action>

این نما [/main.do] است. این یک نما نیست بلکه یک اقدام است. بنابراین اقدام [/main.do] که در بالا توصیف شد اجرا شده و فهرست آیتم‌ها را نمایش می‌دهد.

3.6.6.5. panier.do

این اقدام برای نمایش تمام خریدهای مشتری استفاده می‌شود. این اقدام از طریق گزینه منوی [Voir le panier] در دسترس است:

کد HTML که با لینک [Voir le panier] مرتبط است به شرح زیر است:

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

اقدام [panier.do] در [struts-config.xml] به شرح زیر پیکربندی شده است:

        <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] به شرح زیر است:

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 {

     // سرولِت کنترل
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // خطاهای инициализация؟
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // نمایش صفحهٔ خطاها
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // نمایش سبد خرید
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null || panier.getAchats().size() == 0) {
       // سبد خرید خالی
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherPanierVide");
    } else {
       // در سبد خرید چیزی وجود دارد
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(), mainServlet.getHActionValidationPanier() });
      return mapping.findForward("afficherPanier");
    }
  }
}

توضیحات:

  • شروع متد [execute] با نمونه‌های بررسی‌شده قبلی یکسان است.
  • سبد خرید از سشن مربوطه که معمولاً در آن قرار دارد، بازیابی می‌شود. ممکن است سشن منقضی شده باشد که در این صورت سبد خریدی وجود ندارد. این وضعیت به‌عنوان خطا در نظر گرفته نمی‌شود؛ ما صرفاً فرض می‌کنیم که سبد خرید خالی است.
  • اگر سبد خرید خالی باشد، نمای [PANIERVIDE] نمایش داده می‌شود
  • در غیر این صورت، نما [PANIER] نمایش داده می‌شود

نماهای ارسال‌شده به کلاینت در واقع توسط اقدام تعریف می‌شوند:

        <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

این اقدام برای حذف یک خرید از سبد خرید استفاده می‌شود:

پس از اقدام [retirerachat.do]، سبد خرید دوباره نمایش داده می‌شود (نمای بالا سمت راست). اگر به کد HTML برای لینک [Valider le panier] در نمای بالا سمت چپ نگاه کنیم، موارد زیر را مشاهده می‌کنیم:

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

بنابراین، اکشن [retirerachat.do] به عنوان پارامتر، شناسه آیتمی را که باید از سبد حذف شود دریافت می‌کند. این اکشن در [struts-config.xml] به صورت زیر پیکربندی شده است:

        <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] به شرح زیر است:

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 {

     // سرولت کنترل
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // خطاهای инициализация؟
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // نمایش صفحهٔ خطاها
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     // بازیابی سبد خرید
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // جلسه منقضی شده است
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherPanierVide");
    }
     //شناسهٔ آیتمی را که باید حذف شود بازیابی می‌کند
    String strId = request.getParameter("id");
     // چیزی؟
    if (strId == null) {
       // غیرعادی
      erreurs.add("action incorrecte([retirerachat,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     //تبدیل strId به یک عدد صحیح
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // غیرعادی
      erreurs.add("action incorrecte([retirerachat,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
     // حذف خرید
    panier.enlever(id);
     // نمایش مجدد سبد خرید
    return mapping.findForward("afficherPanier");
  }
}

توضیحات:

  • شروع متد [execute] با نمونه‌های بررسی‌شده قبلی یکسان است.
  • کد زیر وجود و اعتبار پارامتر [id] را بررسی می‌کند. اگر نادرست تشخیص داده شود، نما [ERREURS] بازگردانده می‌شود.
  • در غیر این صورت، آیتم از سبد خرید حذف می‌شود:
     // حذف خرید
    panier.enlever(id);
  • سپس سبد دوباره نمایش داده می‌شود:
     // نمایش مجدد سبد خرید
    return mapping.findForward("afficherPanier");

نمایانی که در واقع به کلاینت ارسال می‌شود توسط اکشن تعریف می‌شود:

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

می‌توانیم ببینیم که از نظر نما، اکشن [/panier.do] است که فعال خواهد شد. این موضوع قبلاً توضیح داده شده است. این اکشن نمای [PANIER] یا [PANIERVIDE] را بسته به وضعیت سبد نمایش خواهد داد.

3.6.6.7. validerpanier.do

این اقدام برای تأیید خریدهای مشتری استفاده می‌شود. به طور عملی، این شامل یک اقدام واحد است: موجودی اقلام خریداری‌شده در پایگاه داده به میزان تعداد خریداری‌شده کاهش می‌یابد. به این اقدام از طریق منوی زیر دسترسی پیدا می‌شود:

 

کد HTML برای لینک [Valider le panier] به شرح زیر است:

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

وقتی این لینک فعال می‌شود، موجودی‌ها کاهش می‌یابند و فهرست اقلام دوباره نمایش داده می‌شود.

این اقدام در [struts-config.xml] به شرح زیر پیکربندی شده است:

        <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] به شرح زیر است:

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 {

     // سرولت کنترل
    MainServlet mainServlet = (MainServlet) this.getServlet();
     // خطاهای инициализация؟
    ArrayList erreursInit = mainServlet.getErreurs();
    if (erreursInit.size() != 0) {
       // صفحه خطاها را نمایش دهید
      request.setAttribute("erreurs", erreursInit);
      request.setAttribute("actions", new Hashtable[] {});
      return mapping.findForward("afficherErreurs");
    }
     // ابژه دسترسی دامنه
    IArticlesDomain articlesDomain = mainServlet.getArticlesDomain();
     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
         // خریدار سبد خرید خود را تأیید کرده است
        Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // جلسه منقضی شده است
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
     request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
         //سبد خرید در حال تأیید است
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // غیرعادی
            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");
        }
         // بازیابی هرگونه خطا
         erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(),mainServlet.getHActionPanier() });
      return mapping.findForward("afficherErreurs");
        }
         // به نظر می‌رسد همه چیز OK باشد – نمایش لیست اقلام
    return mapping.findForward("afficherListeArticles");
  }
}

توضیحات:

  • شروع متد [execute] با نمونه‌های بررسی‌شده قبلی یکسان است.
  • سبد خرید از جلسه بازیابی می‌شود. اگر جلسه منقضی شده باشد، نمای [ERREURS] ارسال می‌شود:
     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
         //خریدار سبد خرید خود را تأیید کرده است
        Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // جلسه منقضی شده است
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
     request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe() });
      return mapping.findForward("afficherErreurs");
    }
  • موارد داخل سبد تأیید می‌شوند. اگر موجودی برای تکمیل سفارش کافی نباشد، ممکن است خطاهایی رخ دهد. در این صورت، نمای [ERREURS] ارسال می‌شود:
         //سبد خرید تأیید می‌شود
        try {
            articlesDomain.acheter(panier);
        } catch (UncheckedAccessArticlesException ex) {
             // غیرعادی
            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");
        }
         // بازیابی هرگونه خطا
         erreurs = articlesDomain.getErreurs();
        if (erreurs.size() != 0) {
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { mainServlet
          .getHActionListe(),mainServlet.getHActionPanier() });
      return mapping.findForward("afficherErreurs");
        }
  • اگر همه چیز به درستی پیش برود، فهرست اقلام دوباره نمایش داده می‌شود:
         // به نظر می‌رسد همه چیز OK باشد – نمایش لیست اقلام
    return mapping.findForward("afficherListeArticles");

ویوی ارسال‌شده به کلاینت در عمل توسط اکشن تعریف می‌شود:

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

می‌توانیم ببینیم که از نظر نما، اکشن [/main.do] فعال خواهد شد. این موضوع قبلاً توضیح داده شده است. این نما را نمایش خواهد داد [LISTE].

3.7. معماری MVC با Spring

3.7.1. معماری کلی برنامه

بیایید به معماری MVC برنامه بازگردیم:

در نسخه اول:

  • کنترلر توسط یک سرولت مدیریت می‌شد
  • ویوها توسط صفحات JSP مدیریت می‌شدند
  • مدل توسط مجموعه‌ای از سه فایل .jar مدیریت می‌شد

در نسخه Struts:

  • کنترلر با یک سروِلت مشتق‌شده از سروِلت عمومی [ActionServlet] در Struts پیاده‌سازی شد
  • نماها توسط همان صفحات JSP مانند نسخه [Struts] مدیریت می‌شدند
  • مدل با استفاده از همان سه فایل .jar پیاده‌سازی شد

در نسخه اسپرینگ:

  • کنترل‌کننده توسط یک سرولتی که توسط Spring ([DispatcherServlet]) فراهم شده است، مدیریت خواهد شد
  • ویوها مانند قبل توسط همان صفحات JSP مدیریت خواهند شد، با چند تفاوت جزئی
  • مدل توسط همان سه فایل مدیریت خواهد شد

خواهیم دید که مهاجرت برنامهٔ ما از Struts به Spring ساده است اگر آماده باشیم از استفاده از همهٔ عناصری که برای معماری ارتدکس Spring MVC توصیه شده‌اند، صرف‌نظر کنیم. تغییرات اصلی به شرح زیر است:

  • اقداماتی که قبلاً در متدهای خاص servlet/controller یا توسط نمونه‌هایی از کلاس‌های مشتق‌شده از کلاس [Action] در Struts انجام می‌شدند، اکنون توسط نمونه‌های کلاسی که رابط [Controller] اسپرینگ را پیاده‌سازی می‌کنند، مدیریت می‌شوند
  • فایل‌های پیکربندی مورد نیاز اکنون به شرح زیر هستند:
    • [web.xml]، زیرا این یک برنامه وب است. این فایل شامل یک Listener است که هنگام راه‌اندازی برنامه، از فایل [applicationContext.xml] استفاده خواهد کرد
    • [applicationContext.xml] که امکان ایجاد بیون‌های مورد نیاز برنامه، به‌ویژه بیون سرویس دسترسی به مدل را فراهم می‌کند
  • ویوهای JSP با آن‌هایی که در Struts وجود دارند یکسان خواهند بود. ما باید یک ویو جدید ایجاد کنیم.

بیایید معماری MVC STRUTS مورد استفاده در نسخه قبلی را به یاد بیاوریم:

M=modèle
کلاس‌های کسب‌وکار، کلاس‌های دسترسی به داده و پایگاه داده
V=vues
صفحات JSP
C=contrôleur
سرولتی برای رسیدگی به درخواست‌های مشتری، اشیاء [Action]

با Spring، ما معماری یکسانی را اتخاذ می‌کنیم:

M=modèle
کلاس‌های کسب‌وکار، کلاس‌های دسترسی به داده و پایگاه داده
V=vues
صفحات JSP
C=contrôleur
سرولتی برای پردازش درخواست‌های مشتری، اشیایی که رابط [Controller] را پیاده‌سازی می‌کنند
  • کنترلر قلب برنامه است. تمام درخواست‌های مشتری از طریق آن عبور می‌کنند. این یک سرولت عمومی است که توسط SPRING ارائه می‌شود. نوع آن [DispatcherServlet] است. از این پس، ما به این کنترلر، کنترلر [Spring] خواهیم گفت.
  • کنترل‌کننده [Spring] درخواست مشتری را به یکی از نمونه‌های نوع [Controller] مسیریابی خواهد کرد. برای هر اکشن قابل پردازش، یک نمونه از این نوع وجود خواهد داشت. این موضوع، همانند Struts، در URL درخواست‌شده تعریف می‌شود. بنابراین، ما خواهیم دانست که اکشن درخواست‌شده، اکشن [liste] است، زیرا URL درخواست‌شده [liste.do] است.
  • اگر C زمینهٔ برنامه باشد، کنترل‌کنندهٔ [Spring] از فایلی به نام [C-servlet.xml] استفاده می‌کند که همان هدف فایل پیکربندی struts-config.xml در نسخهٔ Struts را ایفا می‌کند. برای هر اکشنی که باید توسط برنامه پردازش شود، نام کلاس کنترلر مسئول رسیدگی به درخواست با آن مرتبط می‌شود.
  • کنترلر کنترل را به شیء از نوع Controller مرتبط با آن اکشن واگذار می‌کند. این کار را با فراخوانی متد handleRequest آن شیء انجام می‌دهد و درخواست کلاینت را به آن ارسال می‌کند. در اینجا توسعه‌دهنده وظایف لازم را انجام می‌دهد: ممکن است نیاز باشد به کلاس‌های کسب‌وکار یا کلاس‌های دسترسی به داده‌ها فراخوانی کند. پس از اتمام پردازش، شیء کنترلر نام نمایی را که باید در پاسخ به کلاینت ارسال کند، به کنترلر بازمی‌گرداند.
  • کنترل‌گر در فایل پیکربندی خود، URL مرتبط با نام ویوی که از او خواسته شده نمایش دهد را پیدا می‌کند. سپس این ویو را ارسال می‌کند. تعامل با کلاینت به پایان می‌رسد.

3.7.2. مدل

این همانند دو نسخه قبلی است. این شامل آرشیوهای جاوا [istia.st.articles.dao, istia.st.articles.domain, istia.st.articles.exception] است.

3.7.3. پیکربندی برنامه

3.7.3.1. معماری کلی

معماری کلی پروژه اکلیپس به شرح زیر است:

Image

3.7.3.2. پیکربندی دسترسی به داده‌ها

از آنجا که رابط دسترسی به داده‌ها بدون تغییر باقی می‌ماند، فایل‌های پیکربندی مربوطه نیز مانند نسخه قبلی هستند. این فایل‌ها در [WEB-INF/src] تعریف شده‌اند:

Image

در تصویر بالا، فایل‌های [articles.xml, spring-config-sqlmap-firebird.xml, sqlmap-config-firebird.xml, log4j.properties] مربوط به نسخه‌های قبلی هستند.

3.7.3.3. پوشهٔ آرشیو

در [WEB-INF/lib]، همان آرشیوهایی را که در نسخه قبلی وجود داشت پیدا خواهید کرد، به جز آن‌هایی که مربوط به Struts هستند که دیگر مورد نیاز نیست:

Image

3.7.3.4. پیکربندی برنامه

برنامه با استفاده از سه فایل پیکربندی شده است: [web.xml, applicationContext.xml, springwebarticles-servlet.xml] در پوشه [WEB-INF]:

Image

فایل [web.xml] به شرح زیر است:

<?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>
     <!-- بارگذار زمینه Spring برای برنامه -->
    <listener>
        <listener-class> 
            org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     <!-- سرولت -->
    <servlet>
        <servlet-name>springwebarticles</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
     <!--نگاشت URL -->
    <servlet-mapping>
        <servlet-name>springwebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
     <!--سند ورودی -->
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

این فایل حاوی چه چیزی است؟

  • صفحهٔ اصلی برنامه [/vues/index.jsp] (فایل خوش‌آمدگویی) است
  • درخواست‌های URL با فرمت *.do به سرولت [springwebarticles] (servlet-mapping) هدایت خواهند شد
  • سرولتی [springwebarticles] نمونه‌ای از کلاس [org.springframework.web.servlet.DispatcherServlet] (servlet-name, servlet-class) است که توسط Spring ارائه شده است.
  • لیستنر [org.springframework.web.context.ContextLoaderListener] هنگام راه‌اندازی برنامه اجرا خواهد شد. نقش اصلی آن ایجاد نمونه‌هایی از بی́n‌های Spring تعریف‌شده در فایل [applicationContext.xml] است.

فایل [applicationContext.xml] به شرح زیر است:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- کلاس دسترسی به داده‌ها -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!--کلاس کسب‌وکار -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!--پیکربندی برنامه وب-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>

برخی از عناصر آشنا هستند، در حالی که برخی دیگر کمتر آشنا هستند. سه بیون هنگام راه‌اندازی برنامه ایجاد خواهند شد:

  • articlesDao: سرویسی که دسترسی به لایه [dao] را فراهم می‌کند
  • articlesDomain: سرویسی که دسترسی به مدل را فراهم می‌کند
  • config: یک بین که در آن اطلاعاتی را که همه کلاینت‌ها باید به اشتراک بگذارند، گردآوری می‌کنیم. این بین نقشی را که به طور سنتی توسط زمینه برنامه (application context) ایفا می‌شود، بر عهده خواهد داشت، اما با اطلاعات تایپ‌شده به جای اطلاعات بدون نوع.

فایل نهایی، [springwebarticles.xml]، اکشن‌های پذیرفته‌شده توسط برنامه را به شیوه‌ای بسیار مشابه با فایل Struts با شناسه [struts-config.xml] تعریف می‌کند. محتوای آن به شرح زیر است:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- پردازشگرهای اقدام = کنترل‌کننده‌ها -->
    <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>

     <!--نگاشت برنامه-->
    <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>

     <!-- پردازشگر نما -->
    <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>

     <!-- فایل پیام‌ها -->
    <bean id="messageSource" 
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename">
            <value>messages</value>
        </property>
    </bean>
</beans>

این فایل پیکربندی چه چیزی را مشخص می‌کند؟

  • که کنترلر ما URLهای زیر را مدیریت خواهد کرد:
main.do
برای نمایش لیست مقالات
liste.do
برای نمایش فهرست مقالات
infos.do
برای مشاهده اطلاعات یک آیتم خاص
achat.do
برای خرید یک آیتم مشخص
panier.do
برای مشاهده سبد خرید
retirerachat.do
حذف یک کالا از سبد خرید
validerpanier.do
برای تأیید سبد خرید
  • اقدامات فهرست‌شده در بالا به‌طور یک‌به‌یک با اقداماتی که در نسخه‌های قبلی توسط کنترلر پردازش می‌شدند، مطابقت دارند. برای هر یک، نام کلاسی که مسئول پردازش آن است مشخص شده است. بیایید مثال اقدام [/panier.do] را در نظر بگیریم:
  • این اقدام باید توسط بین [VoirPanierController] پردازش شود. این نام دلخواه است؛ صرفاً یک کلید است.
                <prop key="/panier.do">VoirPanierController</prop>
  • کلید [VoirPanierController] نام یک بین (bean) است که در همان فایل پیکربندی تعریف شده است:
    <bean id="VoirPanierController" 
        class="istia.st.articles.web.spring.VoirPanierController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
  • بیان [VoirPanierController] تعریف می‌کند:
  • کلاسی که باید نمونه آن ساخته شود، [ istia.st.articles.web.spring.VoirPanierController]، برای رسیدگی به اقدام
  • نحوهٔ نمونه‌سازی آن. در اینجا، بین [config] – که توسط [applicationContext.xml] تعریف شده و هنگام راه‌اندازی برنامه نمونه‌سازی می‌شود – به‌عنوان یک پارامتر ارسال می‌شود. این کار برای تمام اقدامات [Controller] انجام خواهد شد. بدین ترتیب، هر یک از آن‌ها در یک فیلد خصوصی، شیء [config] را خواهد داشت که در آن تمام اطلاعات مشترک میان همه کلاینت‌ها را پیدا خواهد کرد.
  • نحوه حل نام‌های نما:
     <!-- پردازشگر نما -->
    <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، نمونه [Controller] که یک اکشن را پردازش می‌کند، پس از پردازش، یک کلید را به کنترلر Spring بازمی‌گرداند تا نشان دهد کدام نما را باید نمایش دهد. بر اساس این کلید، ممکن است استراتژی‌های مختلفی برای تولید نمای مرتبط با آن کلید وجود داشته باشد. استراتژی مورد استفاده، استراتژی تعریف‌شده توسط بیون [viewResolver] است. در اینجا، این بیون با کلاس [org.springframework.web.servlet.view.InternalResourceViewResolver] با پارامترهای اولیه مختلف مرتبط است. بدون ورود به جزئیات، بین [viewResolver] در اینجا مشخص می‌کند که اگر کلید نما «XX» باشد، آنگاه نمای تولید شده [/vues/XX.jsp] خواهد بود. نوع نماهای ارسال شده به کلاینت را می‌توان به روش‌های مختلفی تغییر داد:

  • با تغییر کلاس پیاده‌سازی برای bean [viewResolver]
  • با تغییر پارامترهای راه‌اندازی کلاس پیاده‌سازی

بنابراین، می‌توان به سادگی با تغییر مقدار بیون [viewResolver]، از یک نما با نام HTML به یک نما با نام XML سوئیچ کرد.

  • نام یک فایل پیام برای برنامه (messageSource). در اینجا، فایل وجود خواهد داشت اما خالی خواهد بود. از آن استفاده نخواهد شد. باید در [ClassPath] برنامه قرار داده شود. در اینجا در [WEB-INF/classes] قرار داده می‌شود. در اکلیپس، این نتیجه با قرار دادن آن در [WEB-INF/src] حاصل می‌شود:

Image

3.7.4. ویوهای JSP

ویوهای JSP مورد استفاده، همان‌هایی هستند که توسط Struts استفاده می‌شوند. هیچ‌کدام از آن‌ها تغییر داده نشده‌اند:

Image

یک ویوی جدید واحد ایجاد می‌شود: redirpanier.jsp. این ویو صرفاً برای هدایت مشتری به اکشن [/panier.do] استفاده می‌شود. کد آن به شرح زیر است:

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

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

از خوانندگان دعوت می‌شود تا تعاریف دیدگاه‌های مختلف در نسخه Struts را مرور کنند.

3.7.5. پردازش اکشن

کلاس‌های مورد نیاز برای پردازش اقدامات مختلف در بسته [istia.st.articles.web.spring] گروهبندی شده‌اند:

Image

بیایید با استفاده از یک مثال بررسی کنیم که برنامه Spring چگونه کار می‌کند:

  • کاربر URL [http://localhost:8080/springwebarticles/main.do] را درخواست می‌کند

Image

چه اتفاقی افتاد؟

  • فایل [web.xml] از برنامه [springwebarticles] دسترسی پیدا کرد:
<?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>
     <!-- بارگذار زمینه Spring برای برنامه -->
    <listener>
        <listener-class> 
            org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     <!-- سرولت -->
    <servlet>
        <servlet-name>springwebarticles</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
     <!--نگاشت URL -->
    <servlet-mapping>
        <servlet-name>springwebarticles</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
     <!-- سند ورودی -->
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
  • اگر این اولین درخواست به برنامه بود، تعدادی رویداد فعال شدند:
    • شنونده [ org.springframework.web.context.ContextLoaderListener] بارگذاری شد
    • فایل پیکربندی [applicationContext.xml] را پردازش کرد:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!--کلاس دسترسی به داده‌ها -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!-- کلاس کسب‌وکار -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!--پیکربندی برنامه وب-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>
  • بیان‌های فهرست‌شده در بالا در زمینهٔ برنامه ایجاد شدند
  • سپس فایل [springwebarticles-servlet.xml] پردازش شد:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- پردازشگرهای اقدام = کنترل‌کننده‌ها -->
    <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>

     <!--نگاشت برنامه-->
    <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>

     <!-- پردازشگر نما -->
    <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>

     <!-- فایل پیام‌ها -->
    <bean id="messageSource" 
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename">
            <value>messages</value>
        </property>
    </bean>
</beans>
  • بیان‌های [Controller] تعریف‌شده توسط این فایل نیز ایجاد شدند
  • اکنون همه چیز برای پردازش درخواست مشتری آماده است. این بود: [http://localhost:8080/springwebarticles]. در اینجا، ما یک URL را از زمینه درخواست نمی‌کنیم، بلکه خود زمینه را درخواست می‌کنیم. بنابراین از بخش [welcome-file] فایل [web.xml] استفاده می‌شود.
    <welcome-file-list>
        <welcome-file>/vues/index.jsp</welcome-file>
    </welcome-file-list>
  • نما [index.jsp] به شرح زیر است:
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

<c:redirect url="/main.do"/>
  • بنابراین از کلاینت خواسته می‌شود تا به URL [http://localhost:8080/springwebarticles/main.do] هدایت شود. این کار انجام می‌شود.
  • بنابراین کنترلر Spring یک درخواست جدید دریافت می‌کند. آن فایل [springwebarticles-servlet.xml] را پردازش می‌کند:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- پردازشگرهای اقدام = کنترل‌کننده‌ها -->
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>
....
    </bean>

     <!--نگاشت برنامه-->
    <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>
  • این فایل به آن می‌گوید که اکشن [/main.do] باید توسط بین [ListController] پردازش شود.
  • درخواست مشتری به متد [handleRequest] از بیون [ListController] ارسال می‌شود. این متد وظیفه خود را انجام داده و کلید نمای نمایش داده شده را به کنترلر بازمی‌گرداند. در اینجا، اگر همه چیز به درستی پیش برود، این کلید [liste] خواهد بود.
  • کنترلر Spring از bean [viewResolver] در فایل پیکربندی [springwebarticles-servlet.xml] برای تعیین نمای مرتبط با این کلید استفاده می‌کند. در این مورد، این نما [/vues/liste.jsp] خواهد بود.
  • ویو [/vues/liste.jsp] به کلاینت ارسال می‌شود

3.7.6. ابتدایی‌سازی برنامه اسپرینگ

ما اشاره کردیم که وقتی برنامه اجرا می‌شود، بین‌های موجود در فایل [applicationContext.xml] نمونه سازی می‌شوند:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!--کلاس دسترسی به داده‌ها -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoSqlMap">
        <constructor-arg index="0">
            <value>sqlmap-config-firebird.xml</value>
        </constructor-arg>
    </bean>
     <!--کلاس کسب‌وکار -->
    <bean id="articlesDomain" class="istia.st.articles.domain.AchatsArticles">
        <constructor-arg index="0">
            <ref bean="articlesDao"/>
        </constructor-arg>
    </bean>
     <!--پیکربندی برنامه وب-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>
</beans>

ما با بیون‌های [articlesDao, articlesDomain] آشنا هستیم اما با بیون [config] نه. این توسط کلاس جاوای زیر تعریف می‌شود:

package istia.st.articles.web.spring;

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

/**
 * @author ST - ISTIA
 */

public class Config {

     // fields خصوصی
    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);

     // گیرنده و تنظیم‌کننده
    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;
    }

     // ابتدایی‌سازی برنامه وب
    public void init() {
         // ما برخی از آدرس‌های URL برنامه را ذخیره می‌کنیم
        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);

         // همین است
        return;
    }
}

این کلاس همان عملکرد متد [init] یک سرولتی از یک برنامه کاربردی وب را انجام می‌دهد. این کلاس برنامه کاربردی را راه‌اندازی می‌کند. در اینجا، این کار به شرح زیر انجام می‌شود:

  • زیرا بین [config] به شرح زیر در [applicationContext.xml] تعریف شده است:
     <!--پیکربندی برنامه وب-->
    <bean id="config" class="istia.st.articles.web.spring.Config" init-method="init">
        <property name="articlesDomain">
            <ref bean="articlesDomain"/>
        </property>
    </bean>

هنگامی که ایجاد شد، فیلد خصوصی [articlesDomain] آن مقداردهی اولیه شد

  • سپس، به دلیل ویژگی [init-method="init"] مربوط به بین فوق، متد [init] کلاس مرتبط با بین اجرا می‌شود. در اینجا، سه دیکشنری [hActionListe, hActionPanier, hActionValidationPanier] که برای تولید سه لینک منوی ممکن ارائه‌شده به کاربر استفاده می‌شوند، مقداردهی اولیه می‌شوند.
  • دستورگرهای عمومی ایجاد می‌شوند تا این فیلدهای خصوصی را برای نمونه‌های نوع [Controller] قابل دسترسی کنند، که این نمونه‌ها عملیات را مدیریت خواهند کرد.

3.7.7. اقدامات [Controller] در برنامه Spring

3.7.7.1. مقدمه

هر اکشن Spring توسط کلاسی از نوع [Controller] پیاده‌سازی می‌شود. در نسخه Struts، هر اکشن توسط کلاسی از نوع [Action] پیاده‌سازی می‌شد. نوشتن کلاس [Controller] معمولاً شامل موارد زیر است:

  • کپی و پیست کلاس [Action] که در نسخه Struts استفاده می‌شد
  • سازگار کردن کد با کنوانسیون‌های Spring

3.7.7.2. main.do، liste.do

این دو اقدام یکسان هستند و در [springwebarticles-servlet.xml] به شرح زیر تعریف شده‌اند:

     <!--نگاشت برنامه-->
    <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>

آنها با کلاس [ istia.st.articles.web.spring.ListController] مرتبط هستند که به زودی به تفصیل در مورد آن بحث خواهیم کرد. هنگامی که یکی از این اکشن‌ها در یک مرورگر اجرا می‌شود، نتیجه زیر به دست می‌آید:

Image

کد کلاس [istia.st.articles.web.spring.ListController] به شرح زیر است:

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 {

     //پیکربندی برنامه وب
    Config config;

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

     //پردازش درخواست
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

         //درخواست فهرست آیتم‌ها
        List articles = null;
        try {
            articles = config.getArticlesDomain().getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             //خطا ثبت می‌شود
            ArrayList erreurs = new ArrayList();
            erreurs.add("Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
             //صفحهٔ خطاها را نمایش می‌دهد
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { config
                    .getHActionListe() });
             //نمای خطاها را ارسال می‌کند
            return new ModelAndView("erreurs");
        }
         // نمایش لیست آیتم‌ها
        request.setAttribute("listarticles", articles);
        request.setAttribute("message", "");
        request.setAttribute("actions", new Hashtable[] { config
                .getHActionPanier() });
         //ارسال نما
        return new ModelAndView("liste");
    }

}

توضیحات:

  • این کلاس دارای یک فیلد خصوصی [config] است. این فیلد توسط Spring هنگام instantiate شدن bean [ListController] مقداردهی اولیه شد:
    <bean id="ListController" class="istia.st.articles.web.spring.ListController">
        <property name="config">
            <ref bean="config"/>
        </property>
    </bean>

همانطور که در بالا مشاهده می‌شود، فیلد [config] در [ListController] با بین [config] مقداردهی اولیه شده است. این به چه معناست؟ این به بیون [config] اشاره دارد که در [applicationContext.xml] و c.a.d تعریف شده است. این یک نمونه از [istia.st.articles.web.spring.Config] است، همانطور که در بالا توضیح داده شد.

  • نوشتن کد برای کلاس [Controller] اساساً شامل نوشتن کد برای متد آن [handleRequest] است.
  • ما لیست اقلام را از مدل درخواست می‌کنیم. این از طریق [config.getArticlesDomain()] قابل دسترسی است. اگر یک استثنا رخ دهد، نما [ERREURS] بازگردانده می‌شود. نتیجه‌ای که توسط [handleRequest] بازگردانده می‌شود باید از نوع [ModelAndView] باشد. این کلاس را می‌توان به روش‌های مختلفی نمونه سازی کرد. در اینجا—و این همیشه صادق خواهد بود—ما با ارسال کلید نمای مورد نظر برای نمایش، یک نمونه از [ModelAndView] ایجاد می‌کنیم. شایان ذکر است که به دلیل پیکربندی bean [viewResolver]، درخواست نمایش ویوی با کلید XX منجر به ارسال ویوی [/vues/XX.jsp] خواهد شد.
         // درخواست فهرست آیتم‌ها
        List articles = null;
        try {
            articles = config.getArticlesDomain().getAllArticles();
        } catch (UncheckedAccessArticlesException ex) {
             //خطا ثبت می‌شود
            ArrayList erreurs = new ArrayList();
            erreurs.add("Erreur lors de l'obtention de tous les articles : "
                    + ex.toString());
             // نمایش صفحه خطاها
            request.setAttribute("erreurs", erreurs);
            request.setAttribute("actions", new Hashtable[] { config
                    .getHActionListe() });
             // ارسال نمای خطاها
            return new ModelAndView("erreurs");
        }
  • اگر خطایی وجود نداشته باشد، نما [LISTE] ارسال می‌شود:
1
2
3
4
5
6
7
         // نمایش لیست آیتم‌ها
        request.setAttribute("listarticles", articles);
        request.setAttribute("message", "");
        request.setAttribute("actions", new Hashtable[] { config
                .getHActionPanier() });
         // ارسال نما
        return new ModelAndView("liste");

3.7.7.3. infos.do

این اقدام برای ارائه اطلاعات در مورد یکی از آیتم‌های نمایش داده شده در نمای [LISTE] استفاده می‌شود:

این اقدام در [springwebarticles-servlet.xml] به شرح زیر تعریف شده است:

     <!-- نقشه‌برداری برنامه-->
    <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] به شرح زیر است:

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 {

     //پیکربندی برنامه وب
    Config config;

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

     //پردازش درخواست
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

     //فهرست خطاها
    ArrayList erreurs = new ArrayList();
     //بازیابی شناسهٔ درخواست‌شده
    String strId = request.getParameter("id");
     // چیزی؟
    if (strId == null) {
       // غیرعادی
      erreurs.add("action incorrecte([infos,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     //تبدیل strId به یک عدد صحیح
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       // غیرعادی
      erreurs.add("action incorrecte([infos,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     //درخواست ورودی شناسه کلید
    Article article = null;
    try {
      article = config.getArticlesDomain().getArticleById(id);
    } catch (UncheckedAccessArticlesException ex) {
       // غیرعادی
      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) {
       // غیرعادی
      erreurs.add("Article de clé [" + id + "] inexistant");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
          return new ModelAndView("erreurs");
    }
     // مورد به جلسه اضافه می‌شود
    request.getSession().setAttribute("article", article);
     //صفحه اطلاعات نمایش داده می‌شود
    request.setAttribute("actions", new Hashtable[] { config.getHActionListe() });
        return new ModelAndView("infos");
    }
}

توضیحات:

  • متد [handleRequest] پارامتر [id] را که معمولاً باید در URL وجود داشته باشد، بازیابی می‌کند. در واقع، URL باید به شکل [/infos.do?id=X] باشد. بررسی‌های مختلفی برای تأیید وجود و اعتبار پارامتر [id] انجام می‌شود. اگر مشکلی وجود داشته باشد، نما [ERREURS] بازگردانده می‌شود.
  • اگر [id] معتبر باشد، آیتم متناظر از لایه [domain] درخواست می‌شود. اگر این عملیات خطایی (exception) ایجاد کند یا آیتم پیدا نشود، نمای [ERREURS] یک بار دیگر ارسال می‌شود.
  • اگر همه چیز به درستی پیش برود، آیتم بازیابی‌شده در جلسه (session) قرار داده می‌شود. این یک نکته قابل بحث است. در اینجا، ما فرض می‌کنیم که مشتری ممکن است بخواهد این آیتم را خریداری کند. اگر چنین باشد، ما آن را از جلسه بازیابی خواهیم کرد، به جای اینکه دوباره از لایه [domain] درخواست کنیم.
  • در نهایت، نمای [INFOS] نمایش داده می‌شود.

3.7.7.4. achat.do

این اقدام برای خرید کالایی که توسط نمای قبلی [INFOS] نمایش داده شده است، استفاده می‌شود:

Image

اگر به کد HTML برای این نما نگاه کنیم، می‌بینیم که تگ <form> به صورت زیر تعریف شده است:

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

بنابراین می‌توانیم ببینیم که فرم با استفاده از اکشن [achat.do] به کنترلر ارسال می‌شود.

این اکشن در [springwebarticles-servlet.xml] به صورت زیر پیکربندی شده است:

     <!-- نقشه‌برداری برنامه-->
    <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] به شرح زیر است:

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 {

   //پیکربندی برنامه وب
  Config config;

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

   //پردازش درخواست
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     //بازیابی مقدار خریداری‌شده
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // مقدار نادرست
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("infos");
    }
     //بازیابی جلسه مشتری
    HttpSession session = request.getSession();
     // بازیابی آیتم اضافه شده به جلسه
    Article article = (Article) session.getAttribute("article");
     // آیا جلسه منقضی شده است؟
    if (article == null) {
       // نمایش صفحهٔ خطا
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // ایجاد خرید جدید
    Achat achat = new Achat(article, qté);
     // افزودن خرید به سبد خرید مشتری
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
     //بازگشت به فهرست اقلام
    return new ModelAndView("index");
  }
}

توضیحات:

  • بیایید فرمت فرم ارسال‌شده به کنترلر را به یاد بیاوریم:
        <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>
  • در پرس‌وجو دو پارامتر وجود دارد: [id]: شماره قلم خریداری‌شده؛ [qte]: مقدار خریداری‌شده.
  • وجود و اعتبار پارامتر [qte] بررسی می‌شود. اگر این پارامتر نادرست تشخیص داده شود، نما [INFOS] همراه با یک پیام خطا به کاربر بازگردانده می‌شود:
     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     // بازیابی مقدار خریداری‌شده
    int qté = 0;
    try {
      qté = Integer.parseInt(request.getParameter("qte"));
      if (qté <= 0)
        throw new NumberFormatException();
    } catch (NumberFormatException ex) {
       // مقدار نادرست
      request.setAttribute("msg", "Quantité incorrecte");
      request.setAttribute("qte", request.getParameter("qte"));
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("infos");
    }
  • مورد خریداری‌شده از جلسه بازیابی می‌شود. ممکن است جلسه منقضی شده باشد. در این صورت، نما [ERREURS] بازگردانده می‌شود:
     //بازیابی جلسه مشتری
    HttpSession session = request.getSession();
     // بازیابی آیتم اضافه شده به جلسه
    Article article = (Article) session.getAttribute("article");
     // آیا جلسه منقضی شده است؟
    if (article == null) {
       // نمایش صفحه خطا
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
  • اگر جلسه منقضی نشده باشد، کالا در سبد خرید قرار می‌گیرد که آن نیز از جلسه بازیابی می‌شود:
1
2
3
4
5
6
7
8
9
     // ایجاد خرید جدید
    Achat achat = new Achat(article, qté);
     // افزودن خرید به سبد خرید مشتری
    Panier panier = (Panier) session.getAttribute("panier");
    if (panier == null) {
      panier = new Panier();
      session.setAttribute("panier", panier);
    }
    panier.ajouter(achat);
  • در نهایت، نمای [LISTE] ارسال می‌شود:
     // بازگشت به فهرست اقلام
    return new ModelAndView("index");
  • در بالا، نمای [/vues/index.jsp] ارسال می‌شود. می‌دانیم که این دستور به مرورگر کلاینت می‌دهد تا به URL [/main.do] هدایت (redirect) شود. این هدایت است که لیست اقلام را نمایش خواهد داد.

3.7.7.5. panier.do

این اقدام برای نمایش تمام خریدهای مشتری استفاده می‌شود. این مورد از طریق منو در دسترس است:

Image

کد HTML مرتبط با لینک بالا به شرح زیر است:

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

صفحه بازگردانده شده توسط این لینک به شرح زیر است:

Image

این اقدام در [springwebarticles-servlet.xml] به شرح زیر پیکربندی شده است:

     <!-- نقشه‌برداری برنامه-->
    <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] به شرح زیر است:

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 {

   //پیکربندی برنامه وب
  Config config;

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

   // پردازش درخواست
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // نمایش سبد خرید
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null || panier.getAchats().size() == 0) {
       // سبد خالی
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("paniervide");
    } else {
       // در سبد خرید چیزی وجود دارد
      request.setAttribute("actions", new Hashtable[] {
          config.getHActionListe(), config.getHActionValidationPanier() });
      return new ModelAndView("panier");
    }
  }
}

توضیحات:

  • سبد خرید از سشن مربوطه که معمولاً در آن قرار دارد، بازیابی می‌شود. ممکن است سشن منقضی شده باشد، که در این صورت سبد خریدی وجود نخواهد داشت. این مورد به‌عنوان خطا در نظر گرفته نمی‌شود؛ ما صرفاً فرض می‌کنیم که سبد خرید خالی است.
  • اگر سبد خالی باشد، نما [PANIERVIDE] نمایش داده می‌شود
  • در غیر این صورت، نما [PANIER] نمایش داده می‌شود

3.7.7.6. retirerachat.do

این اقدام برای حذف یک آیتم از سبد خرید استفاده می‌شود:

Image

اگر به کد HTML در لینک بالا نگاه کنیم، موارد زیر را مشاهده می‌کنیم:

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

عمل [retirerachat.do] بنابراین به‌عنوان پارامتر، شناسهٔ آیتمی را که باید از سبد حذف شود، می‌گیرد. این عمل در [springwebarticles-servlet.xml] به‌صورت زیر پیکربندی شده است:

     <!-- نقشه‌برداری برنامه-->
    <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] به شرح زیر است:

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 {

   // پیکربندی برنامه وب
  Config config;

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

   // پردازش درخواست
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     //بازیابی سبد خرید
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // جلسه منقضی شده است
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // بازیابی شناسه آیتم برای حذف
    String strId = request.getParameter("id");
     // چیزی؟
    if (strId == null) {
       // غیرعادی
      erreurs.add("action incorrecte([retirerachat,id=null]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     //تبدیل strId به یک عدد صحیح
    int id = 0;
    try {
      id = Integer.parseInt(strId);
    } catch (Exception ex) {
       //غیرعادی
      erreurs.add("action incorrecte([retirerachat,id=" + strId + "]");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     //خرید حذف شد
    panier.enlever(id);
     // نمایش مجدد سبد خرید
    request.setAttribute("actions",
        new Hashtable[] { config.getHActionListe() });
    return new ModelAndView("redirpanier");
  }
}

توضیحات:

  • این کد وجود و اعتبار پارامتر [id] را بررسی می‌کند. اگر نادرست تشخیص داده شود، نما [ERREURS] ارسال می‌شود.
  • در غیر این صورت، آیتم از سبد خرید حذف می‌شود:
     // حذف خرید
    panier.enlever(id);
  • سپس سبد دوباره نمایش داده می‌شود:
     // نمایش مجدد سبد خرید
    request.setAttribute("actions",
        new Hashtable[] { config.getHActionListe() });
    return new ModelAndView("redirpanier");

بیایید کد مربوط به نما [/vues/redirpanier.jsp] را به یاد بیاوریم:

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

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

می‌توانیم ببینیم که مشتری به اقدام [/panier.do] هدایت خواهد شد. این موضوع قبلاً توضیح داده شده است. این اقدام بسته به وضعیت سبد خرید، نما [PANIER] یا [PANIERVIDE] را نمایش خواهد داد.

3.7.7.7. validerpanier.do

این اقدام برای تأیید خریدهای مشتری استفاده می‌شود. به طور عملی، این شامل یک اقدام واحد است: موجودی اقلام خریداری‌شده در پایگاه داده به میزان تعداد خریداری‌شده کاهش می‌یابد. به این اقدام از طریق منوی زیر دسترسی پیدا می‌شود:

Image

کد HTML برای لینک [Valider le panier] به شرح زیر است:

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

هنگامی که این لینک فعال می‌شود، موجودی‌ها کاهش می‌یابند و لیست اقلام دوباره نمایش داده می‌شود.

این اقدام در [springwebarticles-servlet.xml] به شرح زیر پیکربندی شده است:

     <!-- نقشه‌برداری برنامه-->
    <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] به شرح زیر است:

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 {

   //پیکربندی برنامه وب
  Config config;

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

   //پردازش درخواست
  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     //خریدار سبد خرید خود را تأیید کرده است
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // جلسه منقضی شده است
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
     // تأیید سبد خرید
    try {
      config.getArticlesDomain().acheter(panier);
    } catch (UncheckedAccessArticlesException ex) {
       // غیرعادی
      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");
    }
     // بازیابی هرگونه خطا
    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");
    }
     // به نظر می‌رسد همه چیز OK باشد – نمایش لیست اقلام
    return new ModelAndView("index");
  }
}

نظرات:

  • سبد خرید از جلسه بازیابی می‌شود. اگر جلسه منقضی شده باشد، نما [ERREURS] ارسال می‌شود:
     // فهرست خطاها برای این اقدام
    ArrayList erreurs = new ArrayList();
     //خریدار سبد خرید خود را تأیید کرده است
    Panier panier = (Panier) request.getSession().getAttribute("panier");
    if (panier == null) {
       // جلسه منقضی شده است
      erreurs.add("Votre session a expiré");
      request.setAttribute("erreurs", erreurs);
      request.setAttribute("actions", new Hashtable[] { config
          .getHActionListe() });
      return new ModelAndView("erreurs");
    }
  • ما خریدهای سبد را تأیید می‌کنیم. اگر موجودی برای تکمیل خریدها کافی نباشد، ممکن است خطاهایی رخ دهد. در این صورت، نما [ERREURS] را نمایش می‌دهیم:
     //سبد خرید تأیید می‌شود
    try {
      config.getArticlesDomain().acheter(panier);
    } catch (UncheckedAccessArticlesException ex) {
       // غیرعادی
      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");
    }
     // در حال بازیابی خطاها
    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");
    }
  • اگر همه چیز به درستی پیش برود، فهرست اقلام دوباره نمایش داده می‌شود:
     // به نظر می‌رسد همه چیز OK - نمایش لیست اقلام
    return new ModelAndView("index");

ما می‌دانیم که نما [/vues/index.jsp] مشتری را به اقدام [/main.do] هدایت می‌کند. این اقدام نما [LISTE] را نمایش خواهد داد.