15. Spring IoC
15.1. مقدمة
نقترح اكتشاف إمكانيات تكوين وتكامل إطار عمل Spring (http://www.springframework.org) وكذلك تعريف واستخدام مفهوم IoC (انعكاس التحكم)، المعروف أيضًا باسم حقن التبعية (Dependency Injection)
لننظر إلى التطبيق ثلاثي الطبقات الذي قمنا بإنشائه للتو:
![]() |
للاستجابة لطلبات المستخدم، يجب أن يتواصل وحدة التحكم [Application] مع الطبقة [service]. في مثالنا، كانت هذه الطبقة مثيلًا من النوع [DaoImpl]. حصلت وحدة التحكم [Application] على مرجع على الطبقة [service] في طريقتها [init] (الفقرة 14.8.3):
- السطر 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] بالطريقة التالية:
- في طريقة [init] الخاصة به، سيطلب من الطبقة [Spring IoC] تزويده بمرجع على الطبقة [service]
- ثم سيستخدم [Spring IoC] ملف التكوين XML الذي يحدد له الفئة التي يجب إنشاء مثيل لها وكيفية تهيئتها.
- تقوم [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]: التوزيع [Spring]، c.a.d. أرشيفات .jar لمشروع Spring نفسه بدون تبعياته. يتم توفير مظهر Spring [IoC] بواسطة أرشيفات [spring-core.jar, spring-beans.jar].
- [4,5]: أرشيفات أدوات الجهات الخارجية
15.2.2. مشاريع Eclipse للأمثلة
سنقوم بإنشاء ثلاثة أمثلة توضح استخدام Spring IoC. وستكون جميعها ضمن مشروع Eclipse التالي:

تم تكوين المشروع [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] للمشروع:
![]()
الفئة [Personne] هي كما يلي:
تحتوي الفئة على:
- السطور 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] هي كما يلي:
تعليقات:
- السطر 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 من خلال تنفيذ السطر 12 من [Main]. العملية
أجبرت على إنشاء 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] للمشروع:

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

يتم الحصول على الحزمة [springioc03] أولاً عن طريق نسخ/لصق الحزمة [springioc01] ثم يتم إضافة الفئة [GroupePersonnes] إليها، ونحذف الفئة [Voiture] ونقوم بتكييف الفئة [Main] مع المثال الجديد.
الفئة [GroupePersonnes] هي كما يلي:
عضواها الخاصان هما:
السطر 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] التالية:
- السطور 12-13: نطلب من Spring مرجعًا على bean [groupe1] ونعرض قيمته.
النتائج التي تم الحصول عليها هي التالية:
تعليقات:
- السطر 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] التالية:
سيكون التنفيذ [Dao1] كما يلي:
سيكون التنفيذ [Dao2] كما يلي:
الطبقة [métier]
تنفذ الطبقة [métier] الواجهة [IMetier] التالية:
سيكون تنفيذ [Metier1] كما يلي:
سيكون التنفيذ [Metier2] كما يلي:
الطبقة [ui]
تنفذ الطبقة [ui] الواجهة [IUi] التالية:
سيكون تنفيذ [Ui1] كما يلي:
سيكون التنفيذ [Ui2] كما يلي:
ملفات تكوين 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] هو التالي:
يستخدم البرنامج [Main1] ملف التكوين [spring-config-01.xml] وبالتالي تطبيقات [Ui1, Metier1, Dao1] للطبقات. النتائج التي تم الحصول عليها على وحدة التحكم Eclipse:
البرنامج [Main2] هو التالي:
يستخدم البرنامج [Main2] ملف التكوين [spring-config-02.xml] وبالتالي تطبيقات [Ui2, Metier2, Dao2] للطبقات. النتائج التي تم الحصول عليها على وحدة التحكم Eclipse:
15.4. الخلاصة
يتمتع التطبيق الذي قمنا بإنشائه بمرونة كبيرة في التطور. يمكن تغيير تنفيذ طبقة ما بمجرد التكوين. ويبقى كود الطبقات الأخرى دون تغيير. ويتحقق ذلك بفضل مفهوم IoC الذي يعد أحد ركيزتي Spring. الركيزة الأخرى هي AOP (البرمجة الموجهة نحو الجوانب) التي لم نعرضها. وهي تسمح بإضافة "سلوك" إلى طريقة فئة، أيضًا عن طريق التكوين، دون تعديل كودها.






