5. Spring IoC من الناحية العملية
سنناقش الآن، من خلال أمثلة، تطبيق ما رأيناه سابقًا عمليًّا.
5.1. المثال 1
الفئة [Personne.vb] هي كما يلي:
Namespace istia.st.springioc.demos
Public Class Personne
' الحقول الخاصة
Private _nom As String
Private _age As Integer
' منشئ افتراضي
Public Sub New()
End Sub
' الخصائص المرتبطة بالحقول الخاصة
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = Value
End Set
End Property
' سلسلة الهوية
Public Overrides Function tostring() As String
Return String.Format("[{0},{1}]", nom, age)
End Function
' طريقة init
Public Sub init()
Console.WriteLine("init personne {0}", Me.ToString)
End Sub
' طريقة الإغلاق
Public Sub close()
Console.WriteLine("destroy personne {0}", Me.ToString)
End Sub
End Class
End Namespace
تحتوي الفئة على:
- حقلين خاصين هما «الاسم» و«العمر» يمكن الوصول إليهما عبر خصائص
- طريقة toString لاسترداد قيمة الكائن [Personne] في شكل سلسلة أحرف
- طريقة init التي سيستدعيها Spring عند إنشاء الكائن، وطريقة close التي سيستدعيها عند تدمير الكائن
لإنشاء كائنات من النوع [Personne]، سنستخدم ملف Spring [spring-config-1.xml] التالي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
destroy-method="close">
<property name="nom">
<value>Simon</value>
</property>
<property name="age">
<value>40</value>
</property>
</object>
<object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
destroy-method="close">
<property name="nom">
<value>Brigitte</value>
</property>
<property name="age">
<value>20</value>
</property>
</object>
</objects>
يُعرّف هذا الملف
- يحدد كائنين للمفاتيح "personne1" و"personne2" على التوالي من النوع [Personne]
- ويقوم بتهيئة خصائص [nom, age] لكل شخص
- ويحدد الطرق التي يجب استدعاؤها عند الإنشاء الأولي للكائن [init-method] وعند إتلاف الكائن [destroy-method]
لإجراء اختباراتنا، سنستخدم فئات اختبار NUnit. وستكون الفئة الأولى كما يلي:
Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos
Namespace istia.st.springioc.tests
<TestFixture()> _
Public Class NunitTestSpringIocDemo1
' الكائن المراد اختباره
Private factory As XmlObjectFactory
<SetUp()> _
Public Sub init()
' يتم إنشاء مثيل من المصنع
factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
' السجل
Console.WriteLine("setup test")
End Sub
<Test()> _
Public Sub demo()
' استرداد الكائنات [Personne] من ملف Spring باستخدام مفاتيحها
Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)
Console.WriteLine("personne1=" + personne1.ToString())
Dim personne2 As Personne = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
End Sub
<TearDown()> _
Public Sub destroy()
' يتم تدمير الكائنات الفردية
factory.Dispose()
' تحرير مورد المصنع
factory = Nothing
' المتابعة
Console.WriteLine("teardown test")
End Sub
End Class
End Namespace
تعليقات:
- للحصول على الكائنات المُعرَّفة في الملف [spring-config-1.xml]، نستخدم كائنًا من النوع [XmlObjectFactory]. وهناك أنواع أخرى من «المصانع» التي تتيح الوصول إلى الكائنات الفردية في ملف التكوين. يتم الحصول على الكائن [XmlObjectFactory] في طريقة السمة [SetUp] الخاصة بفئة الاختبار ويتم تخزينه في متغير خاص. تجدر الإشارة إلى أن الطريقة المزودة بالسمة <Setup()> يتم تنفيذها قبل كل اختبار.
- سيتم وضع الملف [spring-config-1.xml] في المجلد [bin] الخاص بالتطبيق
- يمكن لـ Spring استخدام ملفات التكوين ذات التنسيقات المختلفة. يتيح الكائن [XmlObjectFactory] تحليل ملف تكوين بتنسيق XML.
- يؤدي تشغيل ملف Spring إلى إنتاج كائن من النوع [XmlObjectFactory]، وهو هنا كائن المصنع. باستخدام هذا الكائن، يمكن الحصول على كائن فريد (singleton) محدد بالمفتاح C، عن طريق factory.getObject(C).
- تقوم الطريقة [demo] باستدعاء وعرض قيمة الكائنات الفردية ذات المفتاحين "personne1" و"personne2".
هيكل مشروع Visual Studio هو كما يلي:

تعليقات:
- يُسمى المشروع VS باسم [demo1]
- يحتوي المجلد [istia] على أكواد المصدر. ستُنقل الأكواد المُجمَّعة إلى المجلد [bin] الموجود داخل المجلد DLL [demo1.dll]:

- يوجد الملف [spring-config-1.xml] في المجلد [bin] الخاص بالمشروع.
- يحتوي المجلد [bin] على الملفات اللازمة للتطبيق:
- Spring.Core.dll، Spring.Core.xml لفئات Spring
- log4net.dll لسجلات Spring
- demo1.dll وهو ملف DLL الذي تم إنشاؤه أثناء إنشاء المشروع
يتم تحميل ملف DLL و [demo1.dll] الناتجين عن إنشاء المشروع في أداة الاختبار الرسومية [Nunit-Gui 2.2]. وتقوم هذه الأداة تلقائيًا بعرض فئات Nunit الموجودة في ملف DLL:

يؤدي تنفيذ الطريقة [demo] الخاصة باختبار NUnit إلى النتائج الموضحة أعلاه والمذكورة أدناه:
تعليقات:
- السطر 2: يبدأ الاختبار بتنفيذ طريقة السمة <Setup()>
- السطر 3: العملية
أجبرت على إنشاء البين [personne1]. ونظرًا لأننا كتبنا [init-method="init"] في تعريف الكائن الفردي [personne1]، فقد تم تنفيذ الأسلوب [init] الخاص بالكائن [Personne] الذي تم إنشاؤه. ويتم عرض الرسالة المقابلة.
- السطر 4: العملية
أدت إلى عرض قيمة الكائن [Personne] الذي تم إنشاؤه.
- السطران 5-6: تتكرر نفس الظاهرة بالنسبة لـ bean المفتاح [personne2].
- السطر 7: العملية الأخيرة
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
لم تسفر إلا عن سطر واحد معروض:
وبالتالي، لم تؤدِ إلى إنشاء كائن جديد من النوع [Personne]. لو كان الأمر كذلك، لكان قد تم عرض طريقة [init]، وهو ما لم يحدث هنا. هذا هو مبدأ الكائن الفريد (singleton). لا يقوم Spring، بشكل افتراضي، بإنشاء سوى نسخة واحدة من الكائنات (beans) الموجودة في ملف التكوين الخاص به. وهو عبارة عن خدمة مرجعية للكائنات. إذا طُلب منه مرجع كائن لم يتم إنشاؤه بعد، فإنه يقوم بإنشائه ويقدم مرجعًا له. أما إذا كان الكائن قد تم إنشاؤه بالفعل، فإن Spring يكتفي بتقديم مرجع له.
- الأسطر 8-10: يتم تنفيذ طريقة السمة <TearDown()> (السطر 10). وفي داخلها، تتم العملية
إتلاف الكائنات الفردية التي أنشأها Spring. ويؤدي ذلك إلى تنفيذ طريقة كل منها [close] لأننا كتبنا، في ملف التكوين، لكل منها: "destroy-method=close". وهذا ما تشير إليه العروض التالية:
وبما أننا اكتسبنا الآن أساسيات تكوين Spring، فسنكون من الآن فصاعدًا أسرع قليلاً في شرحنا.
5.2. المثال 2
لننظر إلى الفئة الجديدة التالية [Voiture]:
Imports istia.st.springioc.demos
Namespace istia.st.springioc.demos
Public Class Voiture
' الحقول الخاصة
Private _marque As String
Private _type As String
Private _propriétaire As Personne
' الخصائص العامة
Public Property marque() As String
Get
Return _marque
End Get
Set(ByVal Value As String)
_marque = Value
End Set
End Property
Public Property type() As String
Get
Return _type
End Get
Set(ByVal Value As String)
_type = Value
End Set
End Property
Public Property propriétaire() As Personne
Get
Return _propriétaire
End Get
Set(ByVal Value As Personne)
_propriétaire = Value
End Set
End Property
' منشئ افتراضي
Public Sub New()
End Sub
' منشئ بثلاثة معلمات
Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
' يتم تهيئة الحقول الخاصة عبر خصائصها المرتبطة
With Me
.marque = marque
.type = type
.propriétaire = propriétaire
End With
End Sub
' سلسلة تعريف كائن «السيارة»
Public Overrides Function tostring() As String
Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
End Function
' طريقتا init وdestroy
Public Sub init()
Console.WriteLine("init voiture {0}", Me.ToString)
End Sub
Public Sub destroy()
Console.WriteLine("destroy voiture {0}", Me.ToString)
End Sub
End Class
End Namespace
تحتوي الفئة على:
- ثلاثة حقول خاصة هي _type و _marque و _propriétaire. يمكن تهيئة هذه الحقول وقراءتها من خلال الخصائص العامة. كما يمكن تهيئتها باستخدام منشئ Voiture(String, String, Personne). تحتوي الفئة أيضًا على مُنشئ بدون معلمات يسمح أولاً بإنشاء كائن [Voiture] غير مُهيأ، ثم تهيئته لاحقًا عبر خصائصه العامة.
- طريقة toString لاسترداد قيمة الكائن [Voiture] في شكل سلسلة أحرف
- طريقة init التي سيستدعيها Spring مباشرة بعد إنشاء الكائن، وطريقة destroy التي سيستدعيها عند تدمير الكائن
لإنشاء كائنات من النوع [Voiture]، سنستخدم ملف Spring [spring-config-2.xml] التالي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<!-- الأشخاص -->
<object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
destroy-method="close">
<property name="nom">
<value>Simon</value>
</property>
<property name="age">
<value>40</value>
</property>
</object>
<object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
destroy-method="close">
<property name="nom">
<value>Brigitte</value>
</property>
<property name="age">
<value>20</value>
</property>
</object>
<!-- سيارة -->
<object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
destroy-method="destroy">
<constructor-arg index="0">
<value>Peugeot</value>
</constructor-arg>
<constructor-arg index="1">
<value>307</value>
</constructor-arg>
<constructor-arg index="2">
<ref object="personne2"/>
</constructor-arg>
</object>
</objects>
يضيف هذا الملف إلى التعريفات السابقة كائن مفتاح "voiture1" من النوع [Voiture]. لتهيئة هذا الكائن، كان من الممكن كتابة:
<object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
destroy-method="destroy">
<constructor-arg index="0">
<value>Peugeot</value>
</constructor-arg>
<constructor-arg index="1">
<value>307</value>
</constructor-arg>
<constructor-arg index="2">
<ref object="personne2"/>
</constructor-arg>
</object>
بدلاً من اختيار هذه الطريقة التي سبق عرضها، اخترنا هنا استخدام منشئ Voiture(String, String, Personne) الخاص بالفئة. من ناحية أخرى، يحدد العنصر الفردي [voiture1] الطريقة التي يجب استدعاؤها عند الإنشاء الأولي للكائن [init-method] والطريقة التي يجب استدعاؤها عند تدمير الكائن [destroy-method].
لإجراء اختباراتنا، سنستخدم فئة الاختبار NUnit [NunitTestSpringIocDemo2] التالية:
Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos
Namespace istia.st.springioc.tests
<TestFixture()> _
Public Class NunitTestSpringIocDemo2
' مصنع العناصر الفردية
Private factory As XmlObjectFactory
<SetUp()> _
Public Sub init()
' يتم إنشاء مثيل لمصنع
factory = New XmlObjectFactory(New FileStream("spring-config-2.xml", FileMode.Open))
' السجل
Console.WriteLine("setup test")
End Sub
<TearDown()> _
Public Sub destroy()
' يتم تدمير العناصر الفردية
factory.Dispose()
' تحرير المصنع
factory = Nothing
' المتابعة
Console.WriteLine("teardown test")
End Sub
<Test()> _
Public Sub demo2()
' استرداد الكائن الفردي [voiture1]
Dim voiture1 As Voiture = CType(factory.GetObject("voiture1"), Voiture)
Console.WriteLine("Voiture1=[{0}]", voiture1)
End Sub
End Class
End Namespace
تعليقات:
- تظل طرق السمات <Setup()> و<TearDown()> دون تغيير.
- تسترد الطريقة [demo2] مرجعًا إلى العنصر الفردي [voiture1] وتعرضه.
تظل بنية مشروع Visual Studio كما كانت من قبل. لم تظهر سوى فئتين جديدتين بالإضافة إلى ملف تكوين Spring:

يُظهر تنفيذ الاختبار NUnit النتائج التالية:

دعونا نعلق على ما يظهر على الشاشة:
التعليقات:
- السطر 2: يتم تنفيذ طريقة السمة [Setup] قبل كل اختبار، وهنا [demo]
- السطر 3: يبدأ Spring في إنشاء الكائن الفردي [voiture1]. وهذا الكائن يعتمد على الكائن الفردي [personne2] الذي لا وجود له. لذا يتم إنشاء هذا الأخير وتنفيذ طريقة [init] الخاصة به.
- السطر 4: يمكن الآن إنشاء الكائن الفردي [voiture1]. ثم يتم تنفيذ طريقة [init] الخاصة به.
- السطر 5: تعرض الطريقة [demo2] قيمة الكائن الفردي [voiture1]
- الأسطر 6-8: يتم تنفيذ الطريقة [TearDown] الخاصة بالاختبار. يتم تدمير الكائنات الفردية، مما يؤدي إلى تنفيذ طرقها [destroy]
5.3. المثال 3
نقوم بإدخال الفئة الجديدة [GroupePersonnes] التالية:
Imports istia.st.springioc.demos
Namespace istia.st.springioc.demos
Public Class GroupePersonnes
' الحقول الخاصة
Private _membres() As Personne
Private _groupesDeTravail As Hashtable
' الخصائص العامة
Public Property membres() As Personne()
Get
Return _membres
End Get
Set(ByVal Value() As Personne)
_membres = Value
End Set
End Property
Public Property groupesDeTravail() As Hashtable
Get
Return _groupesDeTravail
End Get
Set(ByVal Value As Hashtable)
_groupesDeTravail = Value
End Set
End Property
' منشئ افتراضي
Public Sub New()
End Sub
' سلسلة هوية الكائن GroupeDePersonnes
Public Overrides Function tostring() As String
' يتم تصفح قائمة أعضاء المجموعة
Dim identité As String = "[membres=("
Dim i As Integer
For i = 0 To membres.Length - 2
identité += membres(i).ToString + ","
Next
identité += membres(i).ToString + "), groupes de travail=("
' يتم تصفح قاموس مجموعات العمل
Dim clés As IEnumerator = groupesDeTravail.Keys.GetEnumerator
Dim clé As Object
While clés.MoveNext
clé = clés.Current
identité += String.Format("[{0},{1}] ", clé, groupesDeTravail(clé))
End While
' إرجاع النتيجة
Return identité + "]"
End Function
' طرق init-destroy
Public Sub init()
Console.WriteLine("init GroupeDePersonnes {0}", Me.ToString)
End Sub
Public Sub destroy()
Console.WriteLine("destroy GroupeDePersonnes {0}", Me.ToString)
End Sub
End Class
End Namespace
عضواها الخاصان هما:
_membres: جدول بأسماء أعضاء المجموعة
_groupesDeTravail: قاموس يربط شخصًا بمجموعة عمل
يتم إتاحة الوصول إلى هذه العناصر الخاصة عبر خصائص عامة. نهدف هنا إلى توضيح كيفية قيام Spring بتهيئة الكائنات المعقدة، مثل الكائنات التي تحتوي على حقول من نوع المصفوفة أو القاموس.
سيكون ملف تكوين Spring [spring-config-3.xml] كما يلي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<!-- الأشخاص -->
<object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
destroy-method="close">
<property name="nom">
<value>Simon</value>
</property>
<property name="age">
<value>40</value>
</property>
</object>
<object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
destroy-method="close">
<property name="nom">
<value>Brigitte</value>
</property>
<property name="age">
<value>20</value>
</property>
</object>
<!-- سيارة -->
<object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
destroy-method="destroy">
<constructor-arg index="0">
<value>Peugeot</value>
</constructor-arg>
<constructor-arg index="1">
<value>307</value>
</constructor-arg>
<constructor-arg index="2">
<ref object="personne2" />
</constructor-arg>
</object>
<!-- مجموعة من الأشخاص -->
<object id="groupe1" type="istia.st.springioc.demos.GroupePersonnes" init-method="init"
destroy-method="destroy">
<property name="membres">
<list>
<ref object="personne1" />
<ref object="personne2" />
</list>
</property>
<property name="groupesDeTravail">
<dictionary>
<entry key="Brigitte">
<value>Marketing</value>
</entry>
<entry key="Simon">
<value>Ressources humaines</value>
</entry>
</dictionary>
</property>
</object>
</objects>
- تسمح العلامة <list> بتهيئة حقل من نوع المصفوفة أو من نوع IList بقيم مختلفة.
- تسمح العلامة <dictionary> بالقيام بنفس الشيء مع حقل ينفذ واجهة IDictionary
لإجراء اختباراتنا، سنستخدم فئة الاختبار التالية: NUnit [NunitTestSpringIocDemo3]:
Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos
Namespace istia.st.springioc.tests
<TestFixture()> _
Public Class NunitTestSpringIocDemo3
' العنصر المراد اختباره
Private factory As XmlObjectFactory
<SetUp()> _
Public Sub init()
' يتم إنشاء مثيل من المصنع
factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
' سجل
Console.WriteLine("setup test")
End Sub
<TearDown()> _
Public Sub destroy()
' يتم تدمير العناصر الفردية
factory.Dispose()
' المتابعة
Console.WriteLine("teardown test")
End Sub
<Test()> _
Public Sub demo3()
' استرداد الكائن الفردي [groupe1]
Dim groupe1 As GroupePersonnes = CType(factory.GetObject("groupe1"), GroupePersonnes)
Console.WriteLine("groupe1={0}", groupe1)
End Sub
End Class
End Namespace
تسترد الطريقة [demo3] العنصر الفردي [groupe1] وتعرضه.
تظل بنية مشروع Visual Studio كما كانت من قبل، باستثناء أنه يحتوي على فئتين وملف تكوين إضافيين.

يؤدي تنفيذ الطريقة [demo3] في الاختبار NUnit إلى النتائج التالية:

تعليقات:
- السطر 2: طريقة السمة [Setup] التي يتم تنفيذها قبل كل اختبار، وهي هنا [demo]
- السطران 3-4: يبدأ Spring في إنشاء الكائن الفردي [groupe1]. وهذا الكائن يعتمد على الكائنات الفردية [personne1, personne2] التي لا توجد. يتم إنشاء هذه الكائنات وتنفيذ طرقها [init].
- السطر 5: يمكن الآن إنشاء الكائن الفردي [groupe1]. ثم يتم تنفيذ طريقة [init] الخاصة به.
- السطر 6: تعرض الطريقة [demo3] قيمة الكائن الفردي [groupe1]
- الأسطر 7-10: يتم تنفيذ الطريقة [TearDown] الخاصة بالاختبار. يتم تدمير الكائنات الفردية، مما يؤدي إلى تنفيذ طرقها [destroy]