Skip to content

2. ماده ۱ – Spring IoC

اهداف این سند:

  • برای بررسی گزینه‌های پیکربندی و یکپارچه‌سازی چارچوب Spring (http://www.springframework.org)
  • تعریف و استفاده از مفهوم IoC (معکوس‌سازی کنترل)، که با نام تزریق وابستگی نیز شناخته می‌شود

2.1. پیاده‌سازی یک برنامهٔ سه‌لایه با اسپرینگ

بیایید یک برنامه کلاسیک سه‌لایه را در نظر بگیریم:

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

  1. رابط [IArticlesDao] برای لایه دسترسی به داده
  1. رابط [IArticlesManager] برای لایه کسب‌وکار

در لایه دسترسی به داده (یا لایه DAO، شیء دسترسی به داده)، معمول است که با یک SGBD و در نتیجه با یک درایور JDBC کار کرد. بیایید اسکلت یک کلاس را که به یک جدول مادی در یک SGBD دسترسی دارد، در نظر بگیریم:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     //اتصال به منبع داده
    private String driverClassName=null;
    private Connection connexion=null;
    private String url = null;
    private String user = null;
    private String pwd = null;
 ....

    public List getAllArticles() {
        //درخواست فهرست آیتم‌ها
        try {
             //بارگذاری درایور JDBC
            Class.forName(driverClassName);
            // ایجاد اتصال به BD
            connexion = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

برای انجام عملیاتی روی SGBD، هر متدی به یک شیء [Connection] نیاز دارد که نمایانگر اتصال به پایگاه داده است و از طریق آن داده‌ها بین پایگاه داده و کد جاوا مبادله می‌شوند. برای ساخت این شیء، به چهار مورد اطلاعات نیاز است:

String driverClassName
نام کلاس درایور JDBC برای SGBD
String url
آدرس URL پایگاه داده مورد استفاده
String user
هویتی که اتصال تحت آن برقرار می‌شود
String pwd
رمز عبور این شناسه

کلاس قبلی ما [ArticlesDaoPlainJdbc] چگونه می‌تواند این اطلاعات را به‌دست آورد؟ امکانات مختلفی وجود دارد:

راه حل ۱ – اطلاعات به‌صورت کد سخت در کلاس درج شده است:

1
2
3
4
5
6
7
8
public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // اتصال به منبع داده
    private final String driverClassName = "org.firebirdsql.jdbc.FBDriver";
    private String url = "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb";
    private String user = "someone";
    private String pwd = "somepassword";
 ....

نقطه‌ضعف این راه‌حل این است که هرگاه این اطلاعات تغییر کند، برای مثال هنگام تغییر رمز عبور، باید کد جاوا اصلاح شود.

راه حل ۲ – اطلاعات هنگام ایجاد شی به آن منتقل می‌شود:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // اتصال به منبع داده
    private final String driverClassName;
    private String url;
    private String user;
    private String pwd;
 ....
    public ArticlesDaoPlainJdbc(String driverClassName,String url,String user,String pwd) {
      this.driverClassName=driverClassName;
    this.url=url;
    this.user=user;
    this.pwd=pwd;
    ...
    }

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

public class ArticlesManagerWithDataBase implements IArticlesManager {

    // یک نمونه دسترسی به داده
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         //ایجاد سرویس دسترسی به داده
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

می‌توانیم ببینیم که، بار دیگر، اطلاعات مورد نیاز برای ساختن شی [ArticlesDaoPlainJdbc] به سازنده شی [ArticlesManagerWithDataBase] ارائه می‌شود. می‌توانیم تصور کنیم که این اطلاعات توسط یک لایه سطح بالاتر، مانند لایه رابط کاربری، به آن ارسال می‌شود. به این ترتیب، گام به گام به بالاترین لایهٔ برنامه می‌رسیم. این لایه به دلیل موقعیتی که دارد، توسط هیچ لایه‌ای که بتواند اطلاعات پیکربندی مورد نیازش را منتقل کند، فراخوانی نمی‌شود. بنابراین باید جایگزینی برای پیکربندی از طریق سازنده پیدا کنیم. راه‌حل معمول برای پیکربندی یک برنامه در لایه سطح بالای آن، استفاده از فایلی است که حاوی تمام اطلاعاتی است که احتمالاً در طول زمان تغییر می‌کنند. ممکن است چندین فایل از این دست وجود داشته باشد. هنگامی که برنامه شروع می‌شود، یک لایه راه‌اندازی سپس همه یا برخی از اشیاء مورد نیاز لایه‌های مختلف برنامه را ایجاد خواهد کرد.

انواع مختلفی از فایل‌های پیکربندی وجود دارد. روند کنونی استفاده از فایل‌های XML است. این رویکردی است که Spring اتخاذ کرده است. فایلی که یک شیء [ArticlesDaoPlainJdbc] را پیکربندی می‌کند ممکن است به این شکل باشد:

<?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.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
</beans>

یک برنامه مجموعه‌ای از اشیاء است که اسپرینگ آن‌ها را «بیان» (beans) می‌نامد، زیرا آن‌ها از استاندارد JavaBean برای نام‌گذاری گترها و سترهای فیلدهای خصوصی یک شیء پیروی می‌کنند. اشیاء درون یک برنامه که نقششان ارائه یک سرویس است، اغلب به صورت یک نمونه واحد ایجاد می‌شوند. این‌ها تک‌نسخه (singletons) نامیده می‌شوند. بنابراین، در مثال برنامه چندلایه‌ای که در اینجا بررسی می‌شود، دسترسی به پایگاه داده محصولات توسط یک نمونه واحد از کلاس [ArticlesDaoPlainJdbc] مدیریت خواهد شد. در یک برنامه وب، این اشیاء سرویس همزمان به چندین مشتری خدمت‌رسانی می‌کنند. برای هر مشتری یک شیء سرویس ایجاد نمی‌شود.

فایل پیکربندی Spring فوق اجازه می‌دهد تا یک شیء سرویس از نوع [ArticlesDaoPlainJdbc] در بسته‌ای به نام [istia.st.articles.dao] ایجاد شود. چهار مورد اطلاعات مورد نیاز برای سازنده این شیء در داخل یک تگ &lt;bean&gt;...&lt;/bean&gt;** تعریف شده‌اند. به ازای هر تعداد singleton که باید ایجاد شوند، به همان تعداد تگ &lt;bean&gt;** وجود خواهد داشت.

اشیاء تعریف‌شده در فایل Spring چه زمانی ساخته می‌شوند؟ инициализация یک برنامه می‌تواند به متد اصلی (main) آن برنامه، در صورت وجود، واگذار شود. برای یک برنامه وب، این ممکن است متد [init] از servlet اصلی باشد. در هر برنامه‌ای، متدی وجود دارد که تضمین می‌شود اولین متدی باشد که اجرا می‌شود. معمولاً در همین متد است که اشیاء تک‌نسخه‌ای ایجاد می‌شوند.

یک مثال بزنیم. فرض کنید می‌خواهیم کلاس قبلی [ArticlesDaoPlainJdbc] را با استفاده از یک تست JUnit تست کنیم. یک کلاس تست به نام JUnit دارای متدی به نام [setUp] است که قبل از هر متد دیگری اجرا می‌شود. اینجاست که سینگلتون [ArticlesDaoPlainJdbc] ایجاد خواهد شد.

اگر رویکرد ارسال اطلاعات پیکربندی از طریق سازنده را دنبال کنیم، کلاس تست زیر را خواهیم داشت:

public class TestArticlesPlainJdbc extends TestCase {
    // کلاس دسترسی به مقاله را آزمایش می‌کند ArticlesDaoPlainJdbc
     //منبع داده در Sprintest تعریف شده است

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

    protected void setUp() throws Exception{
        //یک نمونه از کلاس دسترسی به داده را بازیابی می‌کند
        articlesDao =
            (IArticlesDao) new ArticlesDaoPlainJdbc("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

کلاس فراخواننده [TestArticlesPlainJdbc] باید چهار اطلاعات مورد نیاز برای راه‌اندازی نمونهٔ واحد [ArticlesDaoPlainJdbc] را که قرار است ساخته شود، بداند.

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

public class TestSpringArticlesPlainJdbc extends TestCase {
    // کلاس دسترسی به آیتم را آزمایش می‌کند ArticlesDaoJdbc
     //منبع داده در Sprintest تعریف شده است

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

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

در اینجا، کلاس فراخواننده [TestSpringArticlesPlainJdbc] نیازی ندارد که اطلاعات مورد نیاز برای راه‌اندازی singleton را بداند. این کلاس صرفاً باید بداند:

  1. [springArticlesPlainJdbc.xml]: نام فایل پیکربندی Spring که در بالا توضیح داده شد
  2. [articlesDao]: نام singleton که باید ایجاد شود

هرگونه تغییر در فایل پیکربندی، به جز این دو عنصر، تأثیری بر کد جاوا ندارد. این روش پیکربندی اشیاء یک برنامه بسیار انعطاف‌پذیر است. برای پیکربندی خود، برنامه تنها به دو چیز نیاز دارد:

  • نام فایل Spring که حاوی تعاریف singletonهای قابل ایجاد است
  • نام این سنگل‌تون‌ها، که کد جاوا برای به‌دست‌آوردن مرجع اشیایی که از طریق فایل پیکربندی با آن‌ها مرتبط شده‌اند، از آن استفاده می‌کند

2.2. تزریق وابستگی و معکوس‌سازی کنترل

اکنون بیایید مفهوم تزریق وابستگی (Dependency Injection) را که توسط Spring برای پیکربندی برنامه‌ها استفاده می‌شود، معرفی کنیم. از اصطلاح معکوس‌سازی کنترل (Inversion of Control) نیز استفاده می‌شود. ایجاد singleton [ArticlesManagerWithDataBase] در لایه کسب‌وکار برنامه ما را در نظر بگیرید:

برای دسترسی به داده‌ها در SGBD، لایه کسب‌وکار باید از خدمات ابجکتی که رابط [IArticlesDao] را پیاده‌سازی می‌کند، استفاده کند؛ برای مثال، ابجکتی از نوع [ArticlesDaoPlainJdbc]. کد کلاس [ArticlesManagerWithDataBase] ممکن است چیزی شبیه به این باشد:

public class ArticlesManagerWithDataBase implements IArticlesManager {

     //یک نمونه دسترسی به داده
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // ایجاد سرویس دسترسی به داده‌ها
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

فرض می‌شود کلاس [ArticlesDaoPlainJdbc] در اینجا رابط [IArticlesDao] را پیاده‌سازی می‌کند:

public class ArticlesDaoPlainJdbc implements IArticlesDao {...}

برای ایجاد نمونهٔ واحد (singleton) از نوع [IArticlesDao] که برای عملکرد کلاس مورد نیاز است، سازندهٔ آن صراحتاً از نام کلاسی که رابط [IArticlesDao] را پیاده‌سازی می‌کند، استفاده می‌کند:

articlesDao =(IArticlesDao) new ArticlesDaoPlainJdbc(...);

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

کلاس [ArticlesManagerWithDataBase] خود ابتکار عمل را برای ایجاد شیء [ArticlesDaoPlainJdbc] که به آن نیاز دارد، به دست می‌گیرد. با بازگشت به اصطلاح «معکوس‌سازی کنترل»، می‌توان گفت که این کلاس است که «کنترل» ایجاد شیء مورد نیازش را در اختیار دارد.

اگر بخواهیم برای کلاس [ArticlesManagerWithDataBase] یک کلاس تست JUnit بنویسیم، ممکن است چیزی شبیه به این باشد:

public class TestArticlesManagerWithDataBase extends TestCase {
    //یک نمونه از کلاس تجاری تحت تست
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
        //یک نمونه از کلاس کسب‌وکار تحت تست ایجاد می‌کند
        articlesManager =
            (IArticlesManager) new ArticlesManagerWithDataBase("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

کلاس تست یک نمونه از کلاس کسب‌وکار [ArticlesManagerWithDataBase] ایجاد می‌کند که به نوبه خود، در داخل سازنده‌اش، یک نمونه از کلاس دسترسی به داده‌ها [ArticlesDaoPlainJdbc] ایجاد می‌کند.

راه حل اسپرینگ نیاز کلاس کسب‌وکار [ArticlesManagerWithDataBase] به دانستن نام کلاس دسترسی به داده [ArticlesDaoPlainJdbc] را که به آن نیاز دارد، از بین می‌برد. این امر اجازه می‌دهد تا نام بدون تغییر دادن کد جاوا کلاس کسب‌وکار تغییر کند. اسپرینگ امکان ایجاد همزمان هر دو نوع singleton – یکی برای لایه دسترسی به داده و دیگری برای لایه کسب‌وکار – را فراهم می‌کند. فایل پیکربندی اسپرینگ یک bean جدید را تعریف خواهد کرد:

<?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.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
</beans>

ویژگی جدید در تعریف یک بیون سینگلتون برای کلاس کسب‌وکار است که باید ایجاد شود:

    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
  1. کلاسی که دانه [articlesManager] را پیاده‌سازی می‌کند تعریف شده است: [ArticlesManagerWithDataBase]
  2. میدان [articlesDao] از بیون از طریق تگ <property name="articlesDao"> مقداردهی می‌شود. این میدانی است که در کلاس [ArticlesManagerWithDataBase] تعریف شده است:
public class ArticlesManagerWithDataBase implements IArticlesManager {

  // رابط دسترسی به داده‌ها
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

برای اینکه فیلد [articlesDao] توسط Spring و تگ <property> آن مقداردهی اولیه شود، این فیلد باید مطابق با استاندارد JavaBean باشد و باید یک متد [setArticlesDao] برای مقداردهی اولیه فیلد [articlesDao] وجود داشته باشد. توجه داشته باشید که نام متد به طور بسیار دقیقی از نام فیلد استخراج می‌شود. به همین ترتیب، اغلب یک متد [get...] برای بازیابی مقدار فیلد وجود دارد. در اینجا، این متد [getArticlesDao] است. در این نسخه جدید، کلاس [ArticlesManagerWithDataBase] دیگر کانتراکتور ندارد. دیگر به آن نیازی نیست.

  • مقداری که Spring به فیلد [articlesDao] اختصاص می‌دهد، همان مقدار bean [articlesDao] است که در فایل پیکربندی آن تعریف شده است:
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
    .............
    </bean>
  • هنگامی که اسپرینگ نمونهٔ واحد [ArticlesManagerWithDataBase] را ایجاد می‌کند، نمونهٔ واحد [ArticlesDaoPlainJdbc] را نیز ایجاد خواهد کرد:
    • اسپرینگ یک گراف وابستگی از بین‌ها ایجاد می‌کند و می‌بیند که بین [articlesManager] به بین [articlesDao] وابسته است
    • این بیون [articlesDao] را می‌سازد، یعنی یک شیء از نوع [ArticlesDaoPlainJdbc]
    • سپس بیون [articlesManager] از نوع [ArticlesManagerWithDataBase] را ایجاد خواهد کرد

حال بیایید یک تست JUnit برای کلاس [ArticlesManagerWithDataBase] را تصور کنیم. ممکن است چیزی شبیه به این باشد:

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // کلاس کسب‌وکار [ArticlesManagerWithDataBase] را آزمایش می‌کند

    //یک نمونه از کلاس کسب‌وکار در حال آزمایش
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
      //یک نمونه از رابط دسترسی به داده را بازیابی می‌کند
      articlesManager = (IArticlesManager) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesManagerWithDataBase.xml"))).getBean("articlesManager");
    }

بیایید فرایند ایجاد دو تک‌نسخه‌ای را که در فایل Spring با نام [springArticlesManagerWithDataBase.xml] تعریف شده‌اند، دنبال کنیم.

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

این مکانیزم را می‌توان به صورت زیر نمودار کرد:

بیایید اسکلت کلاس [ArticlesManagerWithDataBase] را به یاد بیاوریم:

public class ArticlesManagerWithDataBase implements IArticlesManager {

  //رابط دسترسی به داده
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

هنگامی که اسپرینگ ساخت اشیاء تک‌نسخه (singletons) را به پایان رساند، ما یک شیء از نوع [ArticlesManagerWithDataBase] داریم که فیلد [articlesDao] آن بدون اطلاع خود او مقداردهی اولیه شده است. ما می‌گوییم که یک وابستگی به شیء [ArticlesManagerWithDataBase] تزریق شده است. همچنین گفته می‌شود که کنترل معکوس شده است: دیگر شیء [ArticlesManagerWithDataBase] نیست که ابتکار عمل را برای ایجاد به تنهایی شیء پیاده‌ساز رابط [IArticlesDao] که به آن نیاز دارد، به دست می‌گیرد؛ بلکه خودِ برنامهٔ سطح بالا (هنگام راه‌اندازی) است که مسئولیت ایجاد تمام اشیاء مورد نیاز و مدیریت وابستگی‌های بین آن‌ها را بر عهده می‌گیرد.

مزیت اصلی پیکربندی تک‌نسخه‌ای [ArticlesManagerWithDataBase] از طریق یک فایل Spring این است که اکنون می‌توانیم کلاس پیاده‌سازی متناظر با فیلد [articlesDao] از کلاس [ArticlesManagerWithDataBase] را بدون تغییر کد خودِ کلاس، تغییر دهیم. کافی است نام کلاس را در تعریف bean [articlesDao] در فایل Spring تغییر دهید:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
...
    </bean>

به عنوان مثال، به این صورت درخواهد آمد:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoIbatisSqlMap">
...
    </bean>

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

2.3. Spring IoC در عمل

2.3.1. مثال ۱

کلاس زیر را در نظر بگیرید:

package istia.st.springioc.domain;

public class Personne {
  private String nom;
  private int age;

   // نمایهٔ Person
  public String toString() {
    return "nom=[" + this.nom + "], age=[" + this.age + "]";
  }

   // init-close
  public void init() {
    System.out.println("init personne [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy personne [" + this.toString() + "]");
  }

   // getters و setters
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }
}

کلاس دارای:

  • دو فیلد خصوصی، 'name' و 'age'
  • متدهای get و set برای این دو فیلد
  • یک متد `toString` برای بازیابی مقدار شی `[Personne]` به صورت یک رشته
  • یک متد `init` که هنگام ایجاد شی توسط Spring فراخوانی می‌شود، و یک متد `close` که هنگام تخریب شی فراخوانی می‌شود

برای ایجاد اشیاء از نوع [Personne]، از فایل Spring زیر استفاده خواهیم کرد:

<?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="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
</beans>

این فایل با نام config.xml فراخوانی خواهد شد.

  • این دو بیون را با کلیدهای «person1» و «person2» از نوع [Personne] تعریف می‌کند.
  • این فایل فیلدهای [nom, age] را برای هر شخص مقداردهی اولیه می‌کند
  • این کلاس متدهایی را تعریف می‌کند که در هنگام ساخت اولیهٔ شیء [init-method] و در هنگام تخریب شیء [destroy-method] فراخوانی می‌شوند

برای آزمایش‌هایمان از یک کلاس آزمایشی واحد به نام JUnit استفاده خواهیم کرد که به آن به ترتیب متدهایی اضافه خواهیم کرد. نسخهٔ اول این کلاس به شرح زیر خواهد بود:

package istia.st.springioc.tests;

import istia.st.springioc.domain.Personne;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

public class Tests extends TestCase {

   //کارخانه بیان
  private ListableBeanFactory bf;

   // آزمایش‌های init
  public void setUp() {
    bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
  }

  public void test1() {
    //بازیابی بین‌ها بر اساس کلیدشان [Personne] از فایل Spring
    Personne personne1 = (Personne) bf.getBean("personne1");
    System.out.println("personne1=" + personne1.toString());
    Personne personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
  }
}

توضیحات:

  • برای بازیابی بین‌های تعریف‌شده در فایل [config.xml]، از یک شیء از نوع [ListableBeanFactory] استفاده می‌کنیم. انواع شیء دیگری نیز وجود دارند که امکان دسترسی به بین‌ها را فراهم می‌کنند. شیء [ListableBeanFactory] در متد [setUp] از کلاس تست به دست آمده و در یک متغیر خصوصی ذخیره می‌شود. بنابراین، این شیء برای تمام متدهای تست در دسترس خواهد بود.
  • فایل [config.xml] در [ClassPath] و c.a.d برنامه، در یکی از دایرکتوری‌هایی که ماشین مجازی جاوا هنگام جستجوی کلاسی که برنامه به آن ارجاع داده است، جستجو می‌کند، قرار داده خواهد شد. ابژه [ClassPathResource] برای جستجو در میان منابع داخل [ClassPath] یک برنامه، در این مورد فایل [config.xml]، استفاده می‌شود.
  • اسپرینگ می‌تواند از فایل‌های پیکربندی در فرمت‌های مختلف استفاده کند. شیء [XmlBeanFactory] برای تجزیه یک فایل پیکربندی در فرمت XML استفاده می‌شود.
  • پارس کردن یک فایل Spring یک شی از نوع [ListableBeanFactory] را بازمی‌گرداند، که در این مورد شی bf است. با استفاده از این شی، یک بن (bean) با کلید C را می‌توان از طریق bf.getBean(C) بازیابی کرد.
  • متد [test1] مقادیر بیین‌هایی با کلیدهای «person1» و «person2» را بازیابی و نمایش می‌دهد.

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

Image

توضیحات:

  • پوشه [src] حاوی کد منبع است. کد کامپایل‌شده در پوشه‌ای به نام [bin] قرار خواهد گرفت که در اینجا نشان داده نشده است.
  • فایل [config.xml] در ریشه پوشه [src] قرار دارد. هنگامی که پروژه ساخته می‌شود، به طور خودکار به پوشه [bin] کپی می‌شود که بخشی از پوشه [ClassPath] برنامه را تشکیل می‌دهد. این همان جایی است که توسط شیء [ClassPathResource] قرار دارد.
  • پوشه [lib] شامل سه کتابخانه جاوا مورد نیاز برنامه است:
      • commons-logging.jar و spring-core.jar برای کلاس‌های Spring
      • junit.jar برای کلاس‌های JUnit
  • پوشه [lib] نیز بخشی از [ClassPath] برنامه است

اجرای متد [test1] از تست JUnit نتایج زیر را به دست می‌دهد:

18 sept. 2004 11:28:53 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]

توضیحات:

  • Spring با استفاده از کتابخانه [commons-logging.jar] تعدادی رویداد را ثبت می‌کند. این لاگ‌ها به ما امکان می‌دهند تا بهتر درک کنیم که Spring چگونه کار می‌کند.
  • فایل [config.xml] بارگذاری و سپس پردازش شد
  • عملیات*
Personne personne1 = (Personne) bf.getBean("personne1");

ایجاد دانه [personne1] را تحریک کرد. ما می‌توانیم ورودی لاگ Spring مربوط به این موضوع را ببینیم. از آنجا که تعریف بیون [personne1] حاوی متن '[init-method="init"]' بود، متد [init] از شیء ایجاد شده [Personne] اجرا شد. پیام مربوطه نمایش داده می‌شود.

  • عملیات
System.out.println("personne1=" + personne1.toString());

موجب نمایش مقدار شیء ایجاد شده [Personne] شد.

  • همان پدیده برای دانه کلیدی [personne2] رخ می‌دهد.
  • عملیات آخر
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

منجر به ایجاد یک شیء جدید از نوع [Personne] نشد. اگر چنین بود، متد [init] نمایش داده می‌شد، که در اینجا اینطور نیست. این اصل سینگل‌تون است. به طور پیش‌فرض، اسپرینگ تنها یک نمونه از بی́n‌ها را در فایل پیکربندی خود ایجاد می‌کند. این یک سرویس ارجاع شیء است. اگر برای یک شیء که هنوز ایجاد نشده است درخواست ارجاع داده شود، آن را ایجاد کرده و ارجاع آن را برمی‌گرداند. اگر شیء قبلاً ایجاد شده باشد، اسپرینگ به سادگی ارجاع آن را برمی‌گرداند.

  • شایان ذکر است که هیچ اثری از متد [close] از شی [Personne] وجود ندارد، هرچند ما [destroy-method=close] را در تعریف بین (bean) نوشته بودیم. ممکن است این متد تنها زمانی اجرا شود که حافظه اشغال‌شده توسط شی توسط جمع‌کننده زباله (garbage collector) آزاد گردد. تا زمانی که این اتفاق می‌افتد، برنامه از قبل پایان یافته و نوشتن روی صفحه هیچ تأثیری ندارد. باید بررسی شود.

اکنون که مبانی پیکربندی Spring را پوشش داده‌ایم، می‌توانیم توضیحات را کمی سریع‌تر پیش ببریم.

2.3.2. مثال ۲

بیایید کلاس جدید زیر، [Voiture] را در نظر بگیریم:

package istia.st.springioc.domain;

public class Voiture {
  private String marque;
  private String type;
  private Personne propriétaire;

   // سازنده‌ها

  public Voiture() {
  }

  public Voiture(String marque, String type, Personne propriétaire) {
    this.marque = marque;
    this.type = type;
    this.propriétaire = propriétaire;
  }

   //toString
  public String toString() {
    return "Voiture : marque=[" + this.marque + "] type=[" + this.type
        + "] propriétaire=[" + this.propriétaire + "]";
  }

     // گیرنده و تنظیم‌کننده
  public String getMarque() {
    return marque;
  }

  public void setMarque(String marque) {
    this.marque = marque;
  }

  public Personne getPropriétaire() {
    return propriétaire;
  }

  public void setPropriétaire(Personne propriétaire) {
    this.propriétaire = propriétaire;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

   //init-close
  public void init() {
    System.out.println("init voiture [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy voiture [" + this.toString() + "]");
  }

}

کلاس شامل:

  • سه فیلد خصوصی: type، make و owner. این فیلدها را می‌توان با استفاده از متدهای عمومی get و set بیون اولیه کرد و خواند. همچنین می‌توان آن‌ها را با استفاده از سازنده Car(String, String, Person) اولیه کرد. این کلاس همچنین دارای یک سازنده بدون آرگومان برای انطباق با استاندارد JavaBean است.
  • یک متد toString برای بازیابی مقدار شی [Voiture] به صورت یک رشته
  • یک متد `init` که بلافاصله پس از ایجاد شی توسط Spring فراخوانی می‌شود، و یک متد `close` که هنگام تخریب شی فراخوانی خواهد شد

برای ایجاد اشیاء از نوع [Voiture]، از فایل Spring زیر [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="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref bean="personne2"></ref>
        </constructor-arg>
    </bean>
</beans>

این فایل یک بیون با کلید "car1" و نوع [Voiture] را به تعاریف قبلی اضافه می‌کند. برای مقداردهی اولیه این بیون می‌توانست نوشت:

    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <property name="marque">
            <value>Peugeot</value>
        </property>
        <property name="type">
            <value>307</value>
        </property>
        <property name="propriétaire">
            <ref bean="personne2"/>
        </property>
    </bean>

به جای انتخاب این متد که پیش‌تر معرفی شده است، در اینجا تصمیم گرفته‌ایم از سازنده کلاس Voiture(String, String, Personne) استفاده کنیم. علاوه بر این، بین [voiture1] متدی را که باید در حین ساخت اولیه شیء [init-method] فراخوانی شود و متدی را که باید در حین تخریب شیء [destroy-method] فراخوانی شود، تعریف می‌کند.

برای آزمایش‌هایمان، از کلاس تست JUnit که قبلاً معرفی شده است، استفاده خواهیم کرد و متد زیر [test2] را به آن اضافه می‌کنیم:

1
2
3
4
5
  public void test2() {
     //بازیابی bean [voiture1]
    Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
    System.out.println("Voiture1=" + Voiture1.toString());
  }

متد [test2]، بیون [voiture1] را بازیابی کرده و آن را نمایش می‌دهد.

ساختار پروژهٔ Eclipse همانند تست قبلی باقی می‌ماند. اجرای متد [test2] از تست JUnit نتایج زیر را تولید می‌کند:

18 sept. 2004 14:56:10 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'voiture1'
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'voiture1' instantiated via constructor [public istia.st.springioc.domain.Voiture(java.lang.String,java.lang.String,istia.st.springioc.domain.Personne)]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]

توضیحات:

  1. متد [test2] یک مرجع به بین [voiture1] را درخواست می‌کند
  2. خط ۴: اسپرینگ شروع به ایجاد بیون [voiture1] می‌کند زیرا این بیون هنوز ایجاد نشده است (singleton)
  3. خط ۶: از آنجا که بین [voiture1] به بین [personne2] ارجاع می‌دهد، بین دوم نیز به نوبه خود ایجاد می‌شود
  4. خط ۷: دانه [personne2] ایجاد شده است. سپس متد [init] آن اجرا می‌شود.
  5. خط ۹: Spring نشان می‌دهد که از یک سازنده برای ایجاد bean [voiture1] استفاده خواهد کرد
  6. خط ۱۰: دانه [voiture1] ایجاد شده است. متد آن [init] سپس اجرا می‌شود.
  7. خط ۱۱: متد [test2] مقدار بن [voiture1] را نمایش می‌دهد

2.3.3. مثال ۳

ما کلاس جدید زیر را معرفی می‌کنیم، [GroupePersonnes]:

package istia.st.springioc.domain;

import java.util.Map;

public class GroupePersonnes {
  private Personne[] membres;
  private Map groupesDeTravail;

   // گیرنده و تنظیم‌کننده
  public Personne[] getMembres() {
    return membres;
  }

  public void setMembres(Personne[] membres) {
    this.membres = membres;
  }

  public Map getGroupesDeTravail() {
    return groupesDeTravail;
  }

  public void setGroupesDeTravail(Map groupesDeTravail) {
    this.groupesDeTravail = groupesDeTravail;
  }

   // نمایش
  public String toString() {
    String liste = "membres : ";
    for (int i = 0; i < this.membres.length; i++) {
      liste += "[" + this.membres[i].toString() + "]";
    }
    return liste + ", groupes de travail = " + this.groupesDeTravail.toString();
  }

   //init-close
  public void init() {
    System.out.println("init GroupePersonnes [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
  }
}

دو عضو خصوصی آن عبارتند از:

اعضا: آرایه‌ای از افرادی که عضو گروه هستند

groupesDeTravail: یک دیکشنری که یک شخص را به یک گروه کاری نگاشت می‌کند

در اینجا باید توجه داشت که کلاس [GroupePersonnes] مطابق با استاندارد JavaBean، یک سازنده بدون آرگومان تعریف نمی‌کند. باید به خاطر داشت که در صورت عدم وجود هرگونه سازنده، یک سازنده «پیش‌فرض» وجود دارد که همان سازنده بدون آرگومان است و هیچ کاری انجام نمی‌دهد.

هدف در اینجا نشان دادن این است که چگونه Spring امکان инициалиزه‌سازی اشیاء پیچیده، مانند اشیائی که دارای فیلدهای از نوع آرایه یا دیکشنری هستند، را فراهم می‌کند. ما یک bean جدید به فایل قبلی Spring با شناسه [config.xml] اضافه می‌کنیم:

    <bean id="groupe1" class="istia.st.springioc.domain.GroupePersonnes" 
        init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref bean="personne1"/>
                <ref bean="personne2"/>
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </map>
        </property>
    </bean>
  1. تگ <list> به شما امکان می‌دهد یک فیلد از نوع آرایه یا فیلدی که رابط List را پیاده‌سازی می‌کند، با مقادیر مختلف مقداردهی اولیه کنید.
  2. تگ <map> به شما امکان می‌دهد همین کار را با فیلدی که رابط Map را پیاده‌سازی می‌کند، انجام دهید

برای تست‌هایمان، از کلاس تست JUnit که قبلاً معرفی شده است استفاده خواهیم کرد و متد زیر [test3] را به آن اضافه می‌کنیم:

1
2
3
4
5
  public void test3() {
    //بازیابی بین [groupe1]
    GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
    System.out.println("groupe1=" + groupe1.toString());
  }

متد [test3] بیان [groupe1] را بازیابی کرده و نمایش می‌دهد.

ساختار پروژهٔ Eclipse همانند تست قبلی باقی می‌ماند. اجرای متد [test3] از تست JUnit نتایج زیر را تولید می‌کند:

18 sept. 2004 15:51:45 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'groupe1'
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}

توضیحات:

  • متد [test3] یک مرجع به بین [groupe1] را درخواست می‌کند
  • خط ۴: اسپرینگ شروع به ایجاد این بین می‌کند
  • زیرا بین [groupe1] به بین‌های [personne1] و [personne2] ارجاع می‌دهد؛ این دو بین ایجاد می‌شوند (خطوط ۶ و ۹) و متدهای init آنها اجرا می‌شوند (خطوط ۷ و ۱۰)
  • خط ۱۱: بیون [groupe1] ایجاد شده است. متد آن [init] اکنون اجرا می‌شود.
  • خط ۱۲: نمایش درخواستی توسط متد [test3].

2.4. اسپرینگ برای پیکربندی برنامه‌های وب سه‌لایه

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

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

  • سه لایه از طریق استفاده از رابط‌های جاوا مستقل خواهند شد
  • ادغام این سه لایه توسط Spring انجام خواهد شد
  • ما برای هر یک از سه لایه، بسته‌های جداگانه‌ای ایجاد خواهیم کرد که آن‌ها را Control، Domain و Dao می‌نامیم. یک بستهٔ اضافی حاوی برنامه‌های تست خواهد بود.

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

Image

2.4.2. لایه دسترسی به داده DAO

لایه DAO رابط زیر را پیاده‌سازی خواهد کرد:

package istia.st.demo.dao;

public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}
  • دو کلاس Dao1Impl1 و Dao1Impl2 را بنویسید که رابط IDao1 را پیاده‌سازی می‌کنند. متد Dao1Impl1.doSomethingInDaoLayer مقدار a+b را برمی‌گرداند و متد Dao1Impl2.doSomethingInDaoLayer مقدار a-b را برمی‌گرداند.
  • یک کلاس تست JUnit بنویسید تا دو کلاس قبلی را تست کند

2.4.3. لایه کسب‌وکار

لایه کسب‌وکار رابط زیر را پیاده‌سازی خواهد کرد:

package istia.st.demo.domain;

public interface IDomain1 {
  public int doSomethingInDomainLayer(int a, int b);
}
  • دو کلاس Domain1Impl1 و Domain1Impl2 را بنویسید که رابط IDomain1 را پیاده‌سازی می‌کنند. این کلاس‌ها دارای یک سازنده خواهند بود که پارامتر از نوع IDao1 را می‌پذیرد. متد Domain1Impl1.doSomethingInDomainLayer مقدار a و b را یک واحد افزایش می‌دهد، سپس این دو پارامتر را به متد doSomethingInDaoLayer شیء از نوع IDao1 ارسال می‌کند. از سوی دیگر، متد Domain1Impl2.doSomethingInDomainLayer قبل از انجام همان کار، مقادیر a و b را یک واحد کاهش می‌دهد.
  • یک کلاس تست JUnit برای تست دو کلاس قبلی بنویسید

2.4.4. لایه رابط کاربری

لایه رابط کاربری رابط زیر را پیاده‌سازی خواهد کرد:

package istia.st.demo.control;

public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}
  • دو کلاس Control1Impl1 و Control1Impl2 را بنویسید که رابط IControl1 را پیاده‌سازی می‌کنند. این کلاس‌ها دارای یک سازنده خواهند بود که پارامتر از نوع IDomain1 را می‌پذیرد. متد Control1Impl1.doSomethingInControlLayer مقدار a و b را یک واحد افزایش می‌دهد، سپس این دو پارامتر را به متد doSomethingInDomainLayer شیء از نوع IDomain1 ارسال می‌کند. از سوی دیگر، متد Control11Impl2.doSomethingInControlLayer قبل از انجام همان کار، مقادیر a و b را یک واحد کاهش می‌دهد.
  • یک کلاس تست JUnit برای تست دو کلاس قبلی بنویسید

2.4.5. یکپارچه‌سازی با Spring

  • یک فایل پیکربندی Spring بنویسید که مشخص کند هر یک از سه لایه قبلی باید از کدام کلاس‌ها استفاده کنند
  • یک کلاس تست JUnit را با استفاده از پیکربندی‌های مختلف Spring بنویسید تا انعطاف‌پذیری برنامه را برجسته کنید
  • یک برنامه مستقل (با متد main) بنویسید که دو پارامتر را به رابط IControl1 ارسال کرده و نتیجه بازگردانده شده توسط این رابط را نمایش دهد.

2.4.6. یک راه‌حل

2.4.6.1. پروژه اکلیپس

Image

آرشیوهای پوشه [lib] به پروژه [ClassPath] اضافه شده‌اند.

2.4.6.2. پکیج [istia.st.demo.dao]

رابط:

1
2
3
4
5
6
7
8
9
package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}

یک کلاس پیاده‌سازی اولیه:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public class Dao1Impl1 implements IDao1 {

     // انجام کاری در لایه [dao]
  public int doSometingInDaoLayer(int a, int b) {
    return a+b;
  }

}

یک کلاس پیاده‌سازی دوم:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *
 */
public class Dao1Impl2 implements IDao1 {

     // انجام کاری در لایه [dao]
  public int doSometingInDaoLayer(int a, int b) {
    return a-b;
  }
}

2.4.6.3. پکیج [istia.st.demo.domain]

رابط:

package istia.st.demo.domain;

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

     // ما در لایه [domain] کاری انجام می‌دهیم
  public int doSomethingInDomainLayer(int a, int b);
}

یک کلاس پیاده‌سازی اول:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl1 implements IDomain1 {

     //سرویس دسترسی برای لایه [dao]
  private IDao1 dao1;

  public Domain1Impl1() {
     //
  }

     // سرویس دسترسی لایه [dao] را ذخیره می‌کند
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // یک عمل را در لایه [domain] انجام می‌دهد
  public int doSomethingInDomainLayer(int a, int b) {
    a++;
    b++;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

یک کلاس پیاده‌سازی دوم:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl2 implements IDomain1 {

     // سرویس دسترسی لایه [dao]
  private IDao1 dao1;

  public Domain1Impl2() {
     //سازنده بدون هیچ آرگومانی
  }

     // سرویس دسترسی به لایه [dao] را ذخیره می‌کند
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // یک اقدام را در لایه [domain] انجام می‌دهد
  public int doSomethingInDomainLayer(int a, int b) {
    a--;
    b--;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

2.4.6.4. پکیج [istia.st.demo.control]

رابط

1
2
3
4
5
6
7
8
9
package istia.st.demo.control;

/**
 * @author ST-ISTIA
 *  
 */
public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}

یک کلاس پیاده‌سازی اول:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl1 implements IControl1 {
   //کلاس کسب‌وکار در لایه [domain]
    private IDomain1 domain1;

  public Control1Impl1() {
     // سازنده بدون هیچ آرگومانی
  }

     // نمونه سازی سرویس دسترسی برای لایه [domain]
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // ما کاری انجام می‌دهیم
  public int doSometingInControlLayer(int a, int b) {
    a++;
    b++;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

یک کلاس پیاده‌سازی دوم:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl2 implements IControl1 {

     //کلاس دسترسی لایه [domain]
    private IDomain1 domain1;

  public Control1Impl2() {
     // سازنده بدون هیچ آرگومانی
  }

     //کلاس دسترسی لایه [domain] را ذخیره می‌کند
  public Control1Impl2(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // کاری انجام دهد
  public int doSometingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. فایل‌های پیکربندی [Spring]

یک [springMainTest1.xml] اول:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!--کلاس DAO -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl1">
    </bean>
     <!--کلاس کسب‌وکار -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl1">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!--کلاس کنترل -->
    <bean id="control" class="istia.st.demo.control.Control1Impl1">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

یک [springMainTest2.xml] دوم:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- کلاس DAO -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl2">
    </bean>
     <!-- کلاس کسب‌وکار -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl2">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!--کلاس کنترل -->
    <bean id="control" class="istia.st.demo.control.Control1Impl2">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

2.4.6.6. بسته تست [istia.st.demo.tests]

آزمایشی از نوع [main]:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;

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

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest1 {
  public static void main(String[] arguments) {
     // ما یک پیاده‌سازی از رابط IControl1 را بازیابی می‌کنیم
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // ما از کلاس استفاده می‌کنیم
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // ما نتیجه را نمایش می‌دهیم
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

نتایج نمایش‌داده‌شده در کنسول اکلیپس:

11 mars 2005 11:25:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest1.xml]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl1(istia.st.demo.dao.IDao1)]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl1(istia.st.demo.domain.IDomain1)]
control(10,20)=34

آزمون دیگری با استفاده از فایل پیکربندی دوم [Spring]:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest2 {
  public static void main(String[] arguments) {
     // یک پیاده‌سازی از رابط IControl1 را بازیابی می‌کند
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // ما از کلاس استفاده می‌کنیم
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // ما نتیجه را نمایش می‌دهیم
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

نتایج نمایش‌داده‌شده در کنسول اکلیپس:

11 mars 2005 11:28:52 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest2.xml]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl2(istia.st.demo.dao.IDao1)]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl2(istia.st.demo.domain.IDomain1)]
control(10,20)=-10

در نهایت، یک تست JUnit:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

/**
 * @author ST-ISTIA
 *  
 */
public class JunitTest2Control1 extends TestCase {
  public void testControl1() {
     //یک پیاده‌سازی از رابط IControl1 را بازیابی می‌کند
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // ما از کلاس استفاده می‌کنیم
    int a1 = 10, b1 = 20;
    int res1 = control1.doSometingInControlLayer(a1, b1);
    assertEquals(34, res1);
     // ما یک پیاده‌سازی دیگر از رابط IControl1 را بازیابی می‌کنیم
    IControl1 control2 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // ما از کلاس استفاده می‌کنیم
    int a2 = 10, b2 = 20;
    int res2 = control2.doSometingInControlLayer(a2, b2);
    assertEquals(-10, res2);
  }
}

2.5. Conclusion

چارچوب Spring انعطاف‌پذیری واقعی را هم در معماری برنامه و هم در پیکربندی ارائه می‌دهد. ما از مفهوم IoC، یکی از دو ستون Spring، استفاده کرده‌ایم. ستون دیگر AOP (برنامه‌نویسی جنبه‌گرا) است که به آن نپرداخته‌ایم. این امکان را می‌دهد که «رفتار» به یک متد کلاس از طریق پیکربندی اضافه شود بدون آنکه کد متد تغییر یابد. به طور کلی، AOP اجازه می‌دهد فراخوانی برخی متدها فیلتر شود:

  • فیلتر می‌تواند قبل یا بعد از متد هدف M اجرا شود، یا هر دو.
  • متد M از وجود این فیلترها بی‌خبر است. آنها در فایل پیکربندی Spring تعریف می‌شوند.
  • کد متد M تغییر نمی‌کند. فیلترها کلاس‌های Java هستند که باید پیاده‌سازی شوند. Spring فیلترهای از پیش تعریف‌شده‌ای را ارائه می‌دهد، به‌ویژه برای مدیریت تراکنش‌ها در SGBD.
  • فیلترها بیین هستند و به همین دلیل در فایل پیکربندی Spring به‌عنوان بیین تعریف می‌شوند.

یک فیلتر رایج، فیلتر تراکنشی است. بیایید متد M را در لایه کسب‌وکار در نظر بگیریم که دو عملیات جدایی‌ناپذیر را روی داده‌ها (یک واحد کار) انجام می‌دهد. این متد برای انجام این دو عملیات، دو متد M1 و M2 را از لایه DAO فراخوانی می‌کند.

از آنجا که در لایه کسب‌وکار قرار دارد، متد M رسانه ذخیره‌سازی داده را انتزاع می‌کند. برای مثال، فرض می‌کند که داده‌ها در یک SGBD هستند و باید دو فراخوانی متدهای M1 و M2 را در یک تراکنش SGBD قرار دهد. این لایه DAO است که باید این جزئیات را مدیریت کند. بنابراین، یک راه‌حل برای مشکل قبلی این است که متدی در لایه DAO ایجاد شود که خود متدهای M1 و M2 را فراخوانی کند، فراخوانی‌هایی که آن را در یک تراکنش SGBD محصور می‌کند.

راه حل فیلترسازی AOP انعطاف‌پذیرتر است. این امکان را به شما می‌دهد که فیلتری را تعریف کنید که قبل از فراخوانی M، یک تراکنش را آغاز می‌کند و پس از فراخوانی، بسته به شرایط، یک commit یا rollback انجام می‌دهد.

این رویکرد مزایای متعددی دارد:

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

علاوه بر مفاهیم IoC و AOP، اسپرینگ کلاس‌های پشتیبانی متعددی را برای برنامه‌های کاربردی سه‌لایه فراهم می‌کند:

  • برای JDBC، SqlMap (iBATIS)، و برای Hibernate، JDO (Java Data Object) در لایه DAO
  • برای مدل MVC در لایه رابط کاربری

برای اطلاعات بیشتر: http://www.springframework.org.