Skip to content

15. اسپرینگ IoC

15.1. Introduction

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

بیایید نگاهی به برنامهٔ سه‌لایه‌ای بیندازیم که همین‌الان ساخته‌ایم:

برای پاسخ به درخواست‌های کاربر، کنترل‌کننده [Application] باید با لایه [service] ارتباط برقرار کند. در مثال ما، این یک نمونه از نوع [DaoImpl] بود. کنترل‌کننده [Application] در متد [init] خود (بخش 14.8.3) یک مرجع به لایه [service] را به دست آورد:

@SuppressWarnings("serial")
public class Application extends HttpServlet {
...

     // سرویس
    ServiceImpl service=null;
...
     // ابتدا
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
         // نمونه سازی لایه [dao]
        DaoImpl dao = new DaoImpl();
        dao.init();
         // مثال‌سازی لایه [service]
        service = new ServiceImpl();
        service.setDao(dao);
    }
  • خط ۶: لایه [dao] با ایجاد صریح یک نمونه از [DaoImpl] نمونه سازی شد
  • خط ۹: لایه [service] با ایجاد صریح یک نمونه از [ServiceImpl] نمونه سازی شد

توجه داشته باشید که کلاس‌های [DaoImpl] و [ServiceImpl] به ترتیب رابط‌های [IDao] و [IService] را پیاده‌سازی می‌کنند. در نسخه‌های آینده، رابط [IDao] توسط کلاسی پیاده‌سازی خواهد شد که فهرستی از افراد را که در یک پایگاه داده ذخیره شده‌اند، مدیریت می‌کند. برای این مثال، بیایید این کلاس را [DaoBD] بنامیم. جایگزینی پیاده‌سازی [DaoImpl] لایه [dao] با پیاده‌سازی [DaoBD] مستلزم کامپایل مجدد لایه [web] خواهد بود. این به این دلیل است که خط ۶ بالا، که لایه [dao] را با نوع [DaoImpl] نمونهسازی می‌کند، اکنون باید آن را با نوع [DaoBD] نمونهسازی کند. لایه [web] ما بنابراین به لایه [dao] وابسته است. خط ۹ بالا نشان می‌دهد که این لایه همچنین به لایه [service] وابسته است.

اسپرینگ IoC به ما امکان می‌دهد یک برنامه سه‌لایه ایجاد کنیم که در آن لایه‌ها از یکدیگر مستقل هستند، c.a.d، به این معنی که تغییر یکی مستلزم تغییر دیگری نیست. این امر انعطاف‌پذیری زیادی در تکامل برنامه فراهم می‌کند.

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

کنترل‌کننده [Application]، مرجع مورد نیاز خود را از لایه [service] به شرح زیر دریافت خواهد کرد:

  1. در متد [init]، این کنترل‌کننده درخواست می‌کند که لایه [Spring IoC] مرجعی به لایه [service] در اختیار آن قرار دهد
  2. سپس [Spring IoC] از یک فایل پیکربندی، XML، استفاده می‌کند که به آن می‌گوید کدام کلاس باید نمونه‌سازی شود و چگونه باید اولیه شود.
  3. [Spring IoC] مرجع لایهٔ تازه‌ساخت [service] را به کنترلر [Application] بازمی‌گرداند.

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

اکنون با استفاده از چند مثال به قابلیت‌های [Spring IoC] نگاهی می‌اندازیم.

15.2. Spring IoC در عمل

15.2.1. اسپرینگ

[Spring IoC] بخشی از یک پروژه بزرگ‌تر است که در آدرس URL [http://www.springframework.org/] (مه ۲۰۰۶) در دسترس قرار دارد:

  • [1]: Spring از فناوری‌های شخص ثالث مختلفی استفاده می‌کند که در اینجا به عنوان dépendances به آنها اشاره شده است. شما باید نسخه‌ای را دانلود کنید که حاوی dépendances باشد تا مجبور به دانلود کتابخانه‌های ابزار شخص ثالث در آینده نشوید.
  • [2]: ساختار دایرکتوری فایل فشرده‌شده دانلودشده
  • [3]: توزیع‌های [Spring] و c.a.d. آرشیوهای .jar خود پروژه Spring، بدون وابستگی‌های آن. جنبه [IoC] از Spring توسط آرشیوهای [spring-core.jar, spring-beans.jar] فراهم می‌شود.
  • [4,5]: آرشیوهای ابزارهای شخص ثالث

15.2.2. پروژه‌های اکلیپس برای مثال‌ها

ما سه مثال برای نشان دادن نحوه استفاده از Spring IoC خواهیم ساخت. همه آن‌ها در پروژهٔ Eclipse زیر گنجانده شده‌اند:

Image

پروژه [springioc-exemples] طوری پیکربندی شده است که فایل‌های منبع و کلاس‌های کامپایل‌شده در ریشه پوشه پروژه قرار گیرند:

  • [1]: ساختار دایرکتوری پروژه [Eclipse]
  • [2]: فایل‌های پیکربندی Spring در ریشه پروژه قرار دارند، یعنی در پوشه Classpath برنامه
  • [3]: کلاس‌های مثال ۱
  • [4]: کلاس‌های مثال ۲
  • [5]: کلاس‌های مثال ۳
  • [6]: کتابخانه‌های پروژه [spring-core.jar, spring-beans.jar] را می‌توان در پوشه [dist] توزیع Spring و کتابخانه‌های [commons-logging.jar] را در پوشه [lib/jakarta-commons] یافت. این سه آرشیو در Classpath برنامه گنجانده شده‌اند.

15.2.3. مثال ۱

عناصر در مثال ۱ در بسته [springioc01] پروژه قرار داده شده‌اند:

Image

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

package istia.st.springioc01;

public class Personne {

     //ویژگی‌ها
    private String nom;
    private int age;

     //نمای شخص
    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() + "]");
    }

     // گیرنده و تنظیم‌کننده
    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'
  • خطوط 23–38: متدهای get و set برای این دو فیلد
  • خطوط ۱۰–۱۲: متد toString برای بازیابی مقدار شی [Personne] به صورت یک رشته
  • خطوط ۱۵–۲۱: یک متد `init` که هنگام ایجاد شی توسط Spring فراخوانی می‌شود، و یک متد `close` که هنگام تخریب شی فراخوانی می‌شود

برای نمونه‌سازی اشیاء از نوع [Personne] با استفاده از Spring، از فایل زیر [spring-config-01.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.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
</beans>
  • خطوط ۳ و ۱۲: تگ <beans> تگ ریشه برای فایل‌های پیکربندی Spring است. در داخل این تگ، تگ <bean> برای تعریف اشیاء مختلف مورد استفاده قرار می‌گیرد.
  • خطوط ۴–۷: تعریف یک bean
  • خط ۴: این بیون [personne1] نامیده می‌شود (ویژگی id) و نمونه‌ای از کلاس [istia.st.springioc01.Personne] است (ویژگی class). متد [init] این نمونه پس از ایجاد آن (ویژگی init-method) فراخوانی خواهد شد و متد [close] این نمونه قبل از نابودی آن فراخوانی خواهد شد (ویژگی destroy-method).
  • خط ۵: مقداری را که باید به ویژگی [nom] (صفت name) از نمونهٔ ایجادشدهٔ [Personne] اختصاص داده شود، تعریف می‌کند. برای انجام این инициализация، اسپرینگ از متد [setNom] استفاده خواهد کرد. بنابراین این متد باید وجود داشته باشد. در اینجا این شرایط برقرار است.
  • خط ۶: همین موضوع در مورد خصوصیت [age] نیز صدق می‌کند.
  • خطوط ۸–۱۱: تعریف مشابهی از یک bean به نام [personne2]

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

package istia.st.springioc01;

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

public class Main {

    public static void main(String[] args) {
         // با استفاده از فایل پیکربندی Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
         //بازیابی بین [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
         //بازیابی bean [personne2]
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         //بازیابی مجدد دانه [personne2]
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
         // تمام باین‌ها حذف می‌شوند
        bf.destroySingletons();
    }
}

توضیحات:

  • خط ۱۰: برای بازیابی بین‌های تعریف‌شده در فایل [spring-config-01.xml]، از یک شیء از نوع [XmlBeanFactory] استفاده می‌کنیم که به ما امکان می‌دهد بین‌های تعریف‌شده در فایل XML را نمونه‌سازی کنیم. فایل [spring-config-01.xml] در [ClassPath] برنامه، c.a.d، در یکی از دایرکتوری‌هایی قرار می‌گیرد که ماشین مجازی جاوا هنگام جستجوی کلاسی که برنامه به آن ارجاع داده است، بررسی می‌کند. ابژه [ClassPathResource] برای جستجو در میان منابع داخل [ClassPath] یک برنامه، در این مورد فایل [spring-config-01.xml]، استفاده می‌شود. شیء حاصل [bf] (کارخانه بین) برای به‌دست‌آوردن مرجع یک بین با نام «XX» از طریق عبارت bf.getBean("XX") استفاده می‌شود.
  • خط ۱۲: یک مرجع برای بین با نام [personne1] در فایل [spring-config-01.xml] درخواست می‌شود.
  • خط ۱۳: مقدار شیء متناظر [Personne] نمایش داده می‌شود.
  • خطوط ۱۵–۱۶: همین کار برای bean با نام [personne2] انجام می‌شود.
  • خطوط ۱۸–۱۹: دانه با نام [personne2] دوباره بازیابی می‌شود.
  • خط ۲۱: تمام بیون‌هایی با نام [bf] و c.a.d و همچنین آن‌هایی که از فایل [spring-config-01.xml] ایجاد شده‌اند، حذف می‌شوند.

اجرای کلاس [Main] نتایج زیر را تولید می‌کند:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

توضیحات:

  • خط ۱ با اجرای خط ۱۲ از [Main] به دست آمد. عملیات
Personne personne1 = (Personne) bf.getBean("personne1");

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

  • خط ۲: خط ۱۳ از [Main] مقدار شی ایجاد شده [Personne] را نمایش داد.
  • خطوط ۳–۴: همین پدیده برای بین با نام [personne2] رخ می‌دهد.
  • خط ۵: عملیات در خطوط ۱۸–۱۹ از [Main]
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

موجب ایجاد یک شیء جدید از نوع [Personne] نشد. اگر اینطور بود، متد [init] نمایش داده می‌شد. این اصل الگوی تک‌instance (Singleton) است. به طور پیش‌فرض، اسپرینگ تنها یک instance از هر bean را در فایل پیکربندی خود ایجاد می‌کند. این یک سرویس ارجاع شیء است. اگر برای ارجاع به شیئی که هنوز ایجاد نشده است درخواست شود، آن را ایجاد کرده و ارجاعی به آن بازمی‌گرداند. اگر شیء قبلاً ایجاد شده باشد، اسپرینگ به سادگی ارجاعی به آن بازمی‌گرداند. در اینجا، از آنجایی که [personne2] قبلاً ایجاد شده است، اسپرینگ به سادگی ارجاعی به آن بازمی‌گرداند.

  • خروجی در خطوط ۶–۷ توسط خط ۲۱ از `[Main]` تحریک شد، که درخواست نابودی تمام بیان‌هایی را که توسط شیء `[XmlBeanFactory bf]` ارجاع شده‌اند، یعنی بیان‌های `[personne1, personne2]`، می‌دهد. از آنجا که این دو بیون دارای ویژگی [destroy-method="close"] هستند، متد [close] در هر دو بیون اجرا می‌شود و باعث نمایش خطوط ۶–۷ می‌گردد.

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

15.2.4. مثال ۲

عناصر در مثال ۲ در پکیج [springioc02] پروژه قرار دارند:

Image

پکیج [springioc02] ابتدا با کپی و پیست پکیج [springioc01] ایجاد می‌شود؛ سپس کلاس [Voiture] به آن اضافه شده و کلاس [Main] برای مثال جدید تطبیق داده می‌شود.

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

package istia.st.springioc02;

public class Voiture {
     //ویژگی‌ها
    private String marque;
    private String type;
    private Personne propriétaire;

     // سازنده‌ها
    public Voiture() {
    }

    public Voiture(String marque, String type, Personne propriétaire) {
        setMarque(marque);
        setType(type);
        setProprié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 زیر [spring-config-02.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.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="voiture1" class="istia.st.springioc02.Voiture" init-method="init" destroy-method="close">
        <constructor-arg index="0" value="Peugeot" />
        <constructor-arg index="1" value="307" />
        <constructor-arg index="2">
            <ref local="personne2" />
        </constructor-arg>
    </bean>
</beans>

این فایل یک بین با کلید «voiture1» از نوع [Voiture] (خطوط ۱۲–۱۷) را به بین‌های تعریف‌شده در [spring-config-01.xml] اضافه می‌کند. برای инициалиزه کردن این بین، می‌توانستیم بنویسیم:

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

به جای انتخاب متدی که در مثال ۱ نشان داده شده است، ما در اینجا تصمیم گرفته‌ایم از سازندهٔ کلاس Car(String, String, Person) استفاده کنیم.

  • خط ۱۲: تعریف نام بین، کلاس آن، متدی که پس از نمونه‌سازی اجرا می‌شود و متدی که پس از تخریب اجرا می‌شود.
  • خط ۱۳: مقدار پارامتر اول سازنده [Voiture(String, String, Personne)].
  • خط ۱۴: مقدار پارامتر دوم سازنده [Voiture(String, String, Personne)].
  • خطوط ۱۵–۱۷: مقدار پارامتر سوم سازنده [Voiture(String, String, Personne)]. این پارامتر از نوع [Personne] است. این کلاس با مرجع (برچسب ref) دانه [personne2] که در همان فایل تعریف شده است (ویژگی local) تأمین می‌شود.

برای آزمایش‌های ما، از کلاس زیر [Main] استفاده خواهیم کرد:

package istia.st.springioc02;

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

public class Main {

    public static void main(String[] args) {
         // استفاده از فایل پیکربندی Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
         //بازیابی بین [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
         // حذف بین‌ها
        bf.destroySingletons();
    }
}

متد [main] مرجع به بن [voiture1] (خط ۱۲) را بازیابی کرده و آن را نمایش می‌دهد (خط ۱۳). نتایج به شرح زیر است:

1
2
3
4
5
init personne [nom=[Brigitte], age=[20]]
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]]
destroy voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
destroy personne [nom=[Brigitte], age=[20]]

توضیحات:

  1. متد [main] یک مرجع به بیون [voiture1] (خط ۱۲) درخواست می‌کند. اسپرینگ ساخت بیون [voiture1] را آغاز می‌کند زیرا این بیون هنوز ایجاد نشده است (سینگلتون). از آنجایی که بیِن [voiture1] به بیِن [personne2] ارجاع می‌دهد، بیِن دوم نیز به نوبه خود ایجاد می‌شود. بیِن [personne2] ایجاد شده است. سپس متد آن [init] اجرا می‌شود (خط ۱) در نتایج. سپس بین [voiture1] نمونه سازی می‌شود. سپس متد آن [init] اجرا می‌شود (خط ۲) در نتایج.
  2. خط ۳ نتایج از خط ۱۳ [main] می‌آید: مقدار bean [voiture1] نمایش داده می‌شود.
  3. خط ۱۵ از [main] درخواست نابودی تمام بیین‌های موجود را می‌دهد که باعث نمایش خطوط ۴ و ۵ نتایج می‌شود.

15.2.5. مثال ۳

عناصر در مثال ۳ در پکیج [springioc03] پروژه قرار داده شده‌اند:

Image

پکیج [springioc03] ابتدا با کپی و پیست کردن پکیج [springioc01] ایجاد می‌شود، سپس کلاس [GroupePersonnes] به آن اضافه می‌شود، کلاس [Voiture] حذف شده و کلاس [Main] برای مثال جدید تطبیق داده می‌شود.

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

package istia.st.springioc03;

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() + "]");
    }
}

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

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

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

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

هدف در اینجا نشان دادن نحوهٔ امکان‌پذیر ساختن инициалиزه‌سازی اشیاء پیچیده، مانند اشیائی که دارای فیلدهای از نوع آرایه یا دیکشنری هستند، توسط Spring است. فایل bean با نام [spring-config-03.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.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="groupe1" class="istia.st.springioc03.GroupePersonnes" init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref local="personne1" />
                <ref local="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte" value="Marketing" />
                <entry key="Simon" value="Ressources humaines" />
            </map>
        </property>
    </bean>
</beans>
  • خطوط ۱۴–۱۷: تگ <list> برای مقداردهی اولیه یک فیلد از نوع آرایه یا فیلدی که رابط List را پیاده‌سازی می‌کند، با مقادیر مختلف استفاده می‌شود.
  • خطوط ۲۰–۲۳: تگ <map> به شما امکان می‌دهد همین کار را با فیلدی که رابط Map را پیاده‌سازی می‌کند، انجام دهید.

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

package istia.st.springioc03;

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

public class Main {

    public static void main(String[] args) {
         // پردازش فایل پیکربندی Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
         //بازیابی بین [groupe1]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
         // حذف بیین‌ها
        bf.destroySingletons();
    }
}
  • خطوط ۱۲–۱۳: ما از Spring یک مرجع به bean با نام [groupe1] درخواست می‌کنیم و مقدار آن را نمایش می‌دهیم.

نتایج به‌دست‌آمده به شرح زیر است:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
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}
destroy GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

توضیحات:

  • در خط ۱۲ از [Main]، یک مرجع به بین [groupe1] درخواست می‌شود. اسپرینگ شروع به ایجاد این بین می‌کند. از آنجا که بین [groupe1] به بین‌های [personne1] و [personne2] ارجاع می‌دهد، این دو بین ایجاد می‌شوند (خطوط ۱ و ۲ نتایج). سپس بیون [groupe1] نمونه برداری شده و متد آن [init] اجرا می‌شود (خط ۳ نتایج).
  • خط ۱۳ از [Main] خط ۴ نتایج را نمایش می‌دهد.
  • خط 15 از [Main] خطوط 5 تا 7 نتایج را نمایش می‌دهد.

15.3. پیکربندی یک برنامهٔ چندلایه با Spring

بیایید یک برنامهٔ سه‌لایه با ساختار زیر را در نظر بگیریم:

کاربر

داده‌ها

لایه کسب‌وکار [metier]

لایه دسترسی به داده [dao]

لایه رابط کاربری [ui]

در اینجا هدف ما نشان دادن مزایای استفاده از Spring برای ساخت چنین معماری است.

  • این سه لایه از طریق استفاده از رابط‌های جاوا مستقل خواهند شد
  • ادغام این سه لایه توسط Spring انجام خواهد شد

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

  • [1]: لایه [dao]:
    • [IDao]: رابط لایه
    • [Dao1, Dao2]: دو پیاده‌سازی از این رابط
  • [2]: لایه [metier]:
    • [IMetier]: رابط لایه
    • [Metier1, Metier2]: دو پیاده‌سازی از این رابط
  • [3]: لایه [ui]:
    • [IUi]: رابط لایه
    • [Ui1, Ui2]: دو پیاده‌سازی از این رابط
  • [4]: فایل‌های پیکربندی Spring برنامه. ما برنامه را به دو روش پیکربندی خواهیم کرد.
  • [5]: کتابخانه‌های مورد نیاز برنامه. این‌ها همان کتابخانه‌هایی هستند که در مثال‌های قبلی استفاده شده‌اند.
  • [6]: بستهٔ تست. [Main1] از پیکربندی [spring-config-01.xml] استفاده خواهد کرد و [Main2] از پیکربندی [spring-config-02.xml] استفاده خواهد کرد.

هدف این مثال نشان دادن این است که می‌توانیم پیاده‌سازی یک یا چند لایه از برنامه را بدون هیچ تأثیری بر سایر لایه‌ها تغییر دهیم. همه چیز در فایل پیکربندی Spring مدیریت می‌شود.


لایه [dao]


لایه [dao] رابط زیر [IDao] را پیاده‌سازی می‌کند:

1
2
3
4
5
package istia.st.springioc.troistier.dao;

public interface IDao {
    public int doSomethingInDaoLayer(int a, int b);
}

پیاده‌سازی [Dao1] به شرح زیر خواهد بود:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }
}

پیاده‌سازی [Dao2] به شرح زیر خواهد بود:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }
}

لایه [métier]


لایه [métier] رابط زیر [IMetier] را پیاده‌سازی می‌کند:

1
2
3
4
5
package istia.st.springioc.troistier.metier;

public interface IMetier {
      public int doSomethingInBusinessLayer(int a, int b);
}

پیاده‌سازی [Metier1] به شرح زیر خواهد بود:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier1 implements IMetier {

     //لایه [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

پیاده‌سازی [Metier2] به شرح زیر خواهد بود:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier2 implements IMetier {

     //لایه [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }

    public int doSomethingInBusinessLayer(int a, int b) {
        a--;
        b--;
        return dao.doSomethingInDaoLayer(a, b);
    }

}

لایه [ui]


لایه [ui] رابط زیر [IUi] را پیاده‌سازی می‌کند:

1
2
3
4
5
package istia.st.springioc.troistier.ui;

public interface IUi {
    public int doSomethingInUiLayer(int a, int b);
}

پیاده‌سازی [Ui1] به شرح زیر خواهد بود:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui1 implements IUi {

     // لایه [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

پیاده‌سازی [Ui2] به شرح زیر خواهد بود:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui2 implements IUi {

     //لایه [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

    public void setMetier(IMetier business) {
        this.metier = business;
    }

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b--;
        return metier.doSomethingInBusinessLayer(a, b);
    }

}

فایل‌های پیکربندی Spring


اولین مورد، [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!--کلاس DAO -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
    <!-- کلاس تجاری -->
    <bean id="metier"     class="istia.st.springioc.troistier.metier.Metier1">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!--کلاس UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui1">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

دومین مورد، [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!--کلاس DAO -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
    <!--کلاس کسب‌وکار -->
    <bean id="metier"
        class="istia.st.springioc.troistier.metier.Metier2">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!--کلاس UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

برنامه‌های آزمون


برنامه [Main1] به شرح زیر است:

package istia.st.springioc.troistier.main;

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

import istia.st.springioc.troistier.ui.IUi;

public class Main1 {

    public static void main(String[] args) {
         //ما یک پیاده‌سازی از رابط IUi را بازیابی می‌کنیم
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
         // ما از کلاس استفاده می‌کنیم
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // ما نتیجه را نمایش می‌دهیم
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

برنامه [Main1] از فایل پیکربندی [spring-config-01.xml] و در نتیجه پیاده‌سازی‌های لایه [Ui1, Metier1, Dao1] استفاده می‌کند. نتایج نمایش داده شده در کنسول Eclipse:

ui(10,20)=34

برنامه [Main2] به شرح زیر است:

package istia.st.springioc.troistier.main;

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

import istia.st.springioc.troistier.ui.IUi;

public class Main2 {

    public static void main(String[] args) {
         // ما یک پیاده‌سازی از رابط IUi را بازیابی می‌کنیم
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
         // ما از کلاس استفاده می‌کنیم
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
         // ما نتیجه را نمایش می‌دهیم
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

برنامه [Main2] از فایل پیکربندی [spring-config-02.xml] و در نتیجه پیاده‌سازی‌های لایه‌های [Ui2, Metier2, Dao2] استفاده می‌کند. نتایج نمایش داده شده در کنسول Eclipse:

ui(10,20)=-10

15.4. Conclusion

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