Skip to content

15. Spring IoC

15.1. مقدمة

نقترح اكتشاف إمكانيات تكوين وتكامل إطار عمل Spring (http://www.springframework.org) وكذلك تعريف واستخدام مفهوم IoC (انعكاس التحكم)، المعروف أيضًا باسم حقن التبعية (Dependency Injection)

لننظر إلى التطبيق ثلاثي الطبقات الذي قمنا بإنشائه للتو:

للاستجابة لطلبات المستخدم، يجب أن يتواصل وحدة التحكم [Application] مع الطبقة [service]. في مثالنا، كانت هذه الطبقة مثيلًا من النوع [DaoImpl]. حصلت وحدة التحكم [Application] على مرجع على الطبقة [service] في طريقتها [init] (الفقرة 14.8.3):

@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);
    }
  • السطر 6: تم إنشاء مثيل للطبقة [dao] عن طريق إنشاء مثيل صريح لـ [DaoImpl]
  • السطر 9: تم إنشاء مثيل للطبقة [service] عن طريق إنشاء مثيل صريح لـ [ServiceImpl]

تجدر الإشارة إلى أن الفئتين [DaoImpl] و [ServiceImpl] تنفذان واجهات، وهما واجهتا [IDao] و [IService] على التوالي. في الإصدارات القادمة، سيتم تنفيذ واجهة [IDao] بواسطة فئة تدير قائمة بالأشخاص المخزنة في قاعدة البيانات. لنسمي هذه الفئة [DaoBD] في هذا المثال. سيتطلب استبدال تنفيذ [DaoImpl] للطبقة [dao) بتنفيذ [DaoBD] إعادة تجميع الطبقة [web]. في الواقع، يجب الآن أن تقوم السطر 6 أعلاه، الذي يقوم بإنشاء مثيل للطبقة [dao] بنوع [DaoImpl]، بإنشاء مثيل لها بنوع [DaoBD]. وبالتالي، فإن طبقتنا [web] تعتمد على الطبقة [dao]. ويُظهر السطر 9 أعلاه أنها تعتمد أيضًا على الطبقة [service].

سيسمح لنا Spring IoC بإنشاء تطبيق ثلاثي الطبقات حيث تكون الطبقات مستقلة عن بعضها البعض، c.a.d. بحيث لا يتطلب تغيير إحداها تغيير الطبقات الأخرى. وهذا يوفر مرونة كبيرة في تطوير التطبيق.

ستتطور البنية السابقة على النحو التالي:

مع [Spring IoC]، سيحصل وحدة التحكم [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

[Spring IoC] هو جزء من مشروع أكبر متاح على الرابط [http://www.springframework.org/] (مايو 2006):

  • [1]: يستخدم Spring تقنيات متنوعة من جهات خارجية تسمى هنا dépendances. يجب تنزيل الإصدار الذي يحتوي على dépendances لتجنب الاضطرار لاحقًا إلى تنزيل مكتبات أدوات الجهات الخارجية.
  • [2]: شجرة الملفات المضغوطة التي تم تنزيلها
  • [3]: التوزيع [Springc.a.d. أرشيفات .jar لمشروع Spring نفسه بدون تبعياته. يتم توفير مظهر Spring [IoC] بواسطة أرشيفات [spring-core.jar, spring-beans.jar].
  • [4,5]: أرشيفات أدوات الجهات الخارجية

15.2.2. مشاريع Eclipse للأمثلة

سنقوم بإنشاء ثلاثة أمثلة توضح استخدام Spring IoC. وستكون جميعها ضمن مشروع Eclipse التالي:

Image

تم تكوين المشروع [springioc-exemples] بحيث تكون الملفات المصدرية والفئات المُجمَّعة في جذر مجلد المشروع:

  • [1]: شجرة مجلد مشروع [Eclipse]
  • [2]: ملفات تكوين Spring موجودة في جذر المشروع، أي في Classpath الخاص بالتطبيق
  • [3]: فئات المثال 1
  • [4]: فئات المثال 2
  • [5]: فئات المثال 3
  • [6]: مكتبات المشروع [spring-core.jar, spring-beans.jar] ستوجد في المجلد [dist] في توزيع Spring و [commons-logging.jar] في المجلد [lib/jakarta-commons]. تم تضمين هذه الأرشيفات الثلاثة في Classpath الخاص بالتطبيق.

15.2.3. مثال 1

تم وضع عناصر المثال 1 في الحزمة [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;
    }

}

تحتوي الفئة على:

  • السطور 6-7: حقلان خاصان هما الاسم والعمر
  • السطور 23-38: طرق القراءة (get) والكتابة (set) لهذين الحقلين
  • السطور 10-12: طريقة toString لاسترداد قيمة الكائن [Personne] في شكل سلسلة أحرف
  • السطور 15-21: طريقة 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>
  • السطران 3 و 12: العلامة <beans> هي العلامة الجذرية لملفات تكوين Spring. داخل هذه العلامة، تُستخدم العلامة <bean> لتعريف الكائنات المختلفة المراد إنشاؤها.
  • الأسطر 4-7: تعريف bean
  • السطر 4: يُسمى bean [personne1] (السمة id) وهو مثيل للفئة [istia.st.springioc01.Personne] (السمة class). سيتم استدعاء الطريقة [init] للمثيل بمجرد إنشائه (السمة init-method) وسيتم استدعاء الطريقة [close] للمثيل قبل تدميره (السمة destroy-method).
  • السطر 5: يحددان القيمة التي ستُعطى للخاصية [nom] (السمة name) للمثيل [Personne] الذي تم إنشاؤه. لإجراء هذا التهيئة، سيستخدم Spring الطريقة [setNom]. لذلك يجب أن تكون هذه الطريقة موجودة. وهذا هو الحال هنا.
  • السطر 6: الأمر نفسه بالنسبة للخاصية [age].
  • الأسطر 8-11: تعريف مماثل لفول يسمى [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"));
        // استرداد bean [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());
        // استرداد bean [personne2] مرة أخرى
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // حذف جميع الفاصوليا
        bf.destroySingletons();
    }
}

تعليقات:

  • السطر 10: للحصول على البينات المحددة في الملف [spring-config-01.xml]، نستخدم كائنًا من النوع [XmlBeanFactory] الذي يسمح بإنشاء مثيلات للبينات المحددة في ملف XML. سيتم وضع الملف [spring-config-01.xml] في [ClassPath] الخاص بالتطبيق، c.a.d، في أحد الدلائل التي تستكشفها الآلة الافتراضية Java عند البحث عن فئة مشار إليها من قبل التطبيق. يُستخدم الكائن [ClassPathResource] للبحث عن مورد في ملف [ClassPath] الخاص بتطبيق ما، وهو في هذه الحالة الملف [spring-config-01.xml]. يسمح الكائن [bf] الذي تم الحصول عليه (Bean Factory) بالحصول على مرجع لـ bean باسم "XX" من خلال الأمر bf.getBean("XX").
  • السطر 12: يتم طلب مرجع للـ bean المسمى [personne1] في الملف [spring-config-01.xml].
  • السطر 13: يتم عرض قيمة الكائن [Personne] المقابل.
  • السطران 15-16: نفعل الشيء نفسه مع الكائن المسمى [personne2].
  • السطران 18-19: يتم إعادة طلب الكائن المسمى [personne2].
  • السطر 21: يتم حذف جميع الكائنات من [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]]

تعليقات:

  • تم الحصول على السطر 1 من خلال تنفيذ السطر 12 من [Main]. العملية
Personne personne1 = (Personne) bf.getBean("personne1");

أجبرت على إنشاء bean [personne1]. نظرًا لأننا كتبنا [init-method="init"] في تعريف bean [personne1]، فقد تم تنفيذ الطريقة [init] للكائن [Personne] الذي تم إنشاؤه. يتم عرض الرسالة المقابلة.

  • السطر 2: أدى السطر 13 من [Main] إلى عرض قيمة الكائن [Personne] الذي تم إنشاؤه.
  • السطران 3-4: تكرر نفس الظاهرة بالنسبة للفول المسمى [personne2].
  • السطر 5: عملية السطرين 18-19 من [Main]
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

لم تؤدِ إلى إنشاء كائن جديد من النوع [Personne]. لو كان الأمر كذلك، لكانت طريقة [init] قد ظهرت. هذا هو مبدأ singleton. لا ينشئ Spring، بشكل افتراضي، سوى نسخة واحدة من البينات الموجودة في ملف التكوين الخاص به. إنها خدمة مراجع الكائنات. إذا طُلب منها مرجع كائن لم يتم إنشاؤه بعد، فإنها تنشئه وتقدم مرجعًا له. إذا كان الكائن قد تم إنشاؤه بالفعل، فإن Spring تكتفي بتقديم مرجع له. هنا، نظرًا لأن [personne2] قد تم إنشاؤه بالفعل، فإن Spring تكتفي بتقديم مرجع له.

  • تم عرض السطور 6-7 بواسطة السطر 21 من [Main] الذي يطلب تدمير جميع الفاصوليا المشار إليها بواسطة الكائن [XmlBeanFactory bf]، وبالتالي الفاصوليا [personne1, personne2]. ونظرًا لأن هذين الكائنين (beans) لهما السمة [destroy-method="close"]، يتم تنفيذ الطريقة [close] الخاصة بهما، مما يؤدي إلى عرض السطرين 6 و7.

بعد أن أصبحنا الآن على دراية بأساسيات تكوين Spring، سنكون أسرع قليلاً في شرحنا.

15.2.4. المثال 2

يتم وضع عناصر المثال 2 في حزمة [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() + "]");
    }
}

تحتوي الفئة على:

  • الأسطر 5-7: ثلاثة حقول خاصة هي type وmarke وpropriétaire. يمكن تهيئة هذه الحقول وقراءتها بواسطة الطرق العامة get وset الخاصة بالفول في الأسطر 26-48. كما يمكن تهيئتها باستخدام منشئ Voiture(String, String, Personne) المحدد في الأسطر 13-17. تحتوي الفئة أيضًا على منشئ بدون معلمات من أجل الامتثال للمعيار JavaBean.
  • الأسطر 20-23: طريقة toString لاسترداد قيمة الكائن [Voiture] في شكل سلسلة أحرف
  • الأسطر 51-57: طريقة 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>

يضيف هذا الملف إلى الحبوب المحددة في [spring-config-01.xml] حبة بمفتاح "voiture1" من النوع [Voiture] (الأسطر 12-17). لتهيئة هذه الحبة، كان بإمكاننا كتابة:

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>

بدلاً من اختيار هذه الطريقة التي سبق عرضها في المثال 1، اخترنا هنا استخدام منشئ Voiture(String, String, Personne) الخاص بالفئة.

  • السطر 12: تعريف اسم bean، وفئته، والطريقة التي سيتم تنفيذها بعد إنشاء مثيل له، والطريقة التي سيتم تنفيذها بعد حذفه.
  • السطر 13: قيمة المعلمة الأولى لمُنشئ [Voiture(String, String, Personne)].
  • السطر 14: قيمة المعلمة الثانية لمُنشئ [Voiture(String, String, Personne)].
  • الأسطر 15-17: قيمة المعلمة الثالثة للمنشئ [Voiture(String, String, Personne)]. هذا المعلمة من النوع [Personne]. يتم تزويده كقيمة بالمرجع (العلامة ref) للـ bean [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"));
        // استرداد bean [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
        // حذف البينات
        bf.destroySingletons();
    }
}

تطلب الطريقة [main] مرجع bean [voiture1] (السطر 12) وتعرضه (السطر 13). النتائج هي كما يلي:

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] مرجعًا إلى bean [voiture1] (السطر 12). يبدأ Spring في إنشاء bean [voiture1] لأن هذا bean لم يتم إنشاؤه بعد (singleton). ونظرًا لأن bean [voiture1] يشير إلى bean [personne2]، يتم إنشاء هذا الأخير بدوره. تم إنشاء bean [personne2]. ثم يتم تنفيذ طريقة [init] الخاصة به (السطر 1) من النتائج. بعد ذلك، يتم إنشاء مثيل للبيان [voiture1]. ثم يتم تنفيذ طريقة [init] الخاصة به (السطر 2) من النتائج.
  2. السطر 3 من النتائج يأتي من السطر 13 من [main]: يتم عرض قيمة bean [voiture1].
  3. السطر 15 من [main] يطلب تدمير جميع الفاصوليا الموجودة، مما يؤدي إلى عرض السطرين 4 و5 من النتائج.

15.2.5. المثال 3

يتم وضع عناصر المثال 3 في حزمة [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();
    }

    // البدء والإغلاق
    public void init() {
        System.out.println("init GroupePersonnes [" + this.toString() + "]");
    }

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

عضواها الخاصان هما:

السطر 8: الأعضاء: مصفوفة تضم الأشخاص الأعضاء في المجموعة

السطر 9: groupesDeTravail: قاموس يربط شخصًا بمجموعة عمل

يُلاحظ هنا أن الفئة [GroupePersonnes] لا تحدد منشئًا بدون وسيطة. نذكر أنه في حالة عدم وجود أي منشئ، يوجد منشئ "افتراضي" وهو المنشئ بدون وسيطات والذي لا يقوم بأي شيء.

نسعى هنا إلى إظهار كيف يتيح Spring تهيئة كائنات معقدة مثل الكائنات التي تحتوي على حقول من نوع المصفوفة أو القاموس. ملف حبوب [spring-config-03.xml] في المثال 3 هو التالي:


<?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>
  • الأسطر 14-17: تسمح العلامة <list> بتهيئة حقل من نوع مصفوفة أو ينفذ واجهة List بقيم مختلفة.
  • الأسطر 20-23: تسمح العلامة <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"));
        // استرداد bean [groupe1]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
        // حذف البينات
        bf.destroySingletons();
    }
}
  • السطور 12-13: نطلب من 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]]

تعليقات:

  • السطر 12 من [Main]، نطلب مرجعًا للبيان [groupe1]. يبدأ Spring في إنشاء هذا البين. نظرًا لأن البين [groupe1] يشير إلى البينين [personne1] و [personne2]، يتم إنشاء هذين البينين (السطران 1 و 2) من النتائج. ثم يتم إنشاء مثيل للبيان [groupe1] وتنفيذ طريقة [init] الخاصة به (السطر 3 من النتائج).
  • يعرض السطر 13 من [Main] السطر 4 من النتائج.
  • السطر 15 من [Main] يعرض الأسطر 5-7 من النتائج.

15.3. تكوين تطبيق متعدد الطبقات باستخدام Spring

لنفترض أن لدينا تطبيقًا ثلاثي الطبقات له الهيكل التالي:

utilisateurDonnéesCouche المهنة [metier] طبقة الوصول إلى البيانات [dao] طبقة واجهة المستخدم [ui]

نقترح هنا إظهار أهمية Spring في بناء مثل هذه البنية.

  • ستصبح الطبقات الثلاث مستقلة بفضل استخدام واجهات Java
  • سيتم تحقيق تكامل الطبقات الثلاث بواسطة Spring

قد تكون بنية التطبيق في Eclipse كما يلي:

  • [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"/>
    <!-- فئة métier -->
    <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. الخلاصة

يتمتع التطبيق الذي قمنا بإنشائه بمرونة كبيرة في التطور. يمكن تغيير تنفيذ طبقة ما بمجرد التكوين. ويبقى كود الطبقات الأخرى دون تغيير. ويتحقق ذلك بفضل مفهوم IoC الذي يعد أحد ركيزتي Spring. الركيزة الأخرى هي AOP (البرمجة الموجهة نحو الجوانب) التي لم نعرضها. وهي تسمح بإضافة "سلوك" إلى طريقة فئة، أيضًا عن طريق التكوين، دون تعديل كودها.