5. Spring IoC في الممارسة العملية
سنستخدم الآن أمثلة لتطبيق ما رأيناه سابقًا عمليًا.
5.1. المثال 1
فئة [Person.vb] هي كما يلي:
Namespace istia.st.springioc.demos
Public Class Personne
' private fields
Private _nom As String
Private _age As Integer
' default builder
Public Sub New()
End Sub
' properties associated with private fields
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
' identity chain
Public Overrides Function tostring() As String
Return String.Format("[{0},{1}]", nom, age)
End Function
' init method
Public Sub init()
Console.WriteLine("init personne {0}", Me.ToString)
End Sub
' close method
Public Sub close()
Console.WriteLine("destroy personne {0}", Me.ToString)
End Sub
End Class
End Namespace
تحتوي الفئة على:
- حقلين خاصين، الاسم والعمر، يمكن الوصول إليهما عبر الخصائص
- طريقة toString لاسترداد قيمة كائن [Person] كسلسلة
- طريقة init التي سيتم استدعاؤها بواسطة Spring عند إنشاء الكائن، وطريقة close التي سيتم استدعاؤها عند تدمير الكائن
لإنشاء كائنات من النوع [Person]، سنستخدم ملف 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>
يحدد هذا الملف
- يحدد كائنين بمفاتيح "person1" و "person2" على التوالي من النوع [Person]
- يقوم بتهيئة الخصائص [name, 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
' the test object
Private factory As XmlObjectFactory
<SetUp()> _
Public Sub init()
' create a factory instance
factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
' log
Console.WriteLine("setup test")
End Sub
<Test()> _
Public Sub demo()
' retrieve [Person] objects from the Spring file using their keys
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()
' we destroy the singletons
factory.Dispose()
' release the factory resource
factory = Nothing
' follow-up
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]، وهو في هذه الحالة كائن
factory. باستخدام هذا الكائن، يمكن استرداد كائن فريد محدد بالمفتاح**C**باستخدام**factory.getObject(C)**. - تسترد الطريقة [demo] قيم الكائنات الفردية ذات المفاتيح "person1" و "person2" وتعرضها.
هيكل مشروع 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: العملية
أجبرت على إنشاء الفول [person1]. ولأننا كتبنا [init-method="init"] في تعريف الفول [person1]، تم تنفيذ طريقة [init] للكائن [Person] الذي تم إنشاؤه. يتم عرض الرسالة المقابلة.
- السطر 4: العملية
عرضت قيمة الكائن [Person] الذي تم إنشاؤه.
- السطران 5-6: يحدث الأمر نفسه بالنسبة لـ bean المفتاح [person2].
- السطر 7: العملية الأخيرة
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
أدى ذلك إلى ظهور سطر واحد فقط من النتائج:
وبالتالي، لم يؤد ذلك إلى إنشاء كائن جديد من النوع [Person]. لو كان الأمر كذلك، لظهرت طريقة [init]، وهو ما لم يحدث هنا. هذا هو مبدأ singleton. بشكل افتراضي، يقوم Spring بإنشاء مثيل واحد فقط من beans في ملف التكوين الخاص به. إنها خدمة مرجعية للكائنات. إذا طُلبت مرجعية لكائن لم يتم إنشاؤه بعد، فإنه يقوم بإنشائه وإرجاع مرجعية إليه. إذا كان الكائن قد تم إنشاؤه بالفعل، فإن Spring يقوم ببساطة بإرجاع مرجعية إليه.
- الأسطر 8–10: يتم تنفيذ طريقة السمة <TearDown()> (السطر 10). داخلها، يتم تنفيذ العملية
يؤدي إلى تدمير العناصر الفردية التي أنشأتها Spring. وهذا يؤدي إلى قيام كل منها بتنفيذ طريقة [close] الخاصة بها لأننا كتبنا، في ملف التكوين، لكل منها: "destroy-method=close". وهذا ما تشير إليه الشاشات:
الآن بعد أن غطينا أساسيات تكوين Spring، سنتمكن من المضي قدماً في شرحنا بوتيرة أسرع قليلاً.
5.2. المثال 2
لننظر إلى فئة [Car] الجديدة التالية:
Imports istia.st.springioc.demos
Namespace istia.st.springioc.demos
Public Class Voiture
' private fields
Private _marque As String
Private _type As String
Private _propriétaire As Personne
' public property
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
' default builder
Public Sub New()
End Sub
' three-parameter constructor
Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
' initialize private fields via their associated properties
With Me
.marque = marque
.type = type
.propriétaire = propriétaire
End With
End Sub
' car object identity string
Public Overrides Function tostring() As String
Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
End Function
' init-destroy methods
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 و_make و_owner. يمكن تهيئة هذه الحقول وقراءتها عبر الخصائص العامة. كما يمكن تهيئتها باستخدام منشئ Car(String, String, Person). تحتوي الفئة أيضًا على منشئ بدون حجج يسمح لك أولاً بإنشاء كائن [Car] غير مهيأ ثم تهيئته عبر خصائصه العامة.
- طريقة toString لاسترداد قيمة كائن [Car] كسلسلة
- طريقة init التي سيستدعيها Spring فور إنشاء الكائن، وطريقة destroy التي سيتم استدعاؤها عند إزالة الكائن
لإنشاء كائنات من النوع [Car]، سنستخدم ملف 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>
<!-- people -->
<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>
<!-- a car -->
<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>
يضيف هذا الملف كائنًا بالمفتاح "car1" من النوع [Car] إلى التعريفات السابقة. لتهيئة هذا الكائن، كان بإمكاننا كتابة:
<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>
بدلاً من اختيار الطريقة التي تم عرضها سابقًا، اخترنا هنا استخدام منشئ الفئة Car(String, String, Person). بالإضافة إلى ذلك، يحدد العنصر الفردي [car1] الطريقة التي سيتم استدعاؤها أثناء الإنشاء الأولي للكائن [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
' the singletons factory
Private factory As XmlObjectFactory
<SetUp()> _
Public Sub init()
' create a factory instance
factory = New XmlObjectFactory(New FileStream("spring-config-2.xml", FileMode.Open))
' log
Console.WriteLine("setup test")
End Sub
<TearDown()> _
Public Sub destroy()
' we destroy the singletons
factory.Dispose()
' we free the factory
factory = Nothing
' follow-up
Console.WriteLine("teardown test")
End Sub
<Test()> _
Public Sub demo2()
' singleton retrieval [car1]]
Dim voiture1 As Voiture = CType(factory.GetObject("voiture1"), Voiture)
Console.WriteLine("Voiture1=[{0}]", voiture1)
End Sub
End Class
End Namespace
تعليقات:
- تظل طرق السمات <Setup()> و <TearDown()> دون تغيير.
- تسترد الطريقة [demo2] مرجعًا إلى العنصر الفردي [car1] وتعرضه.
تظل بنية مشروع Visual Studio كما كانت من قبل. تمت إضافة فئتين جديدتين فقط، إلى جانب ملف تكوين Spring:

يؤدي تشغيل اختبار NUnit إلى الحصول على النتائج التالية:

دعونا نعلق على مخرجات الشاشة:
تعليقات:
- السطر 2: يتم تنفيذ طريقة السمة [Setup] قبل كل اختبار، وهنا [demo]
- السطر 3: يبدأ Spring في إنشاء الكائن الفردي [car1]. هذا الكائن الفردي يعتمد على الكائن الفردي [person2]، الذي لا يوجد بعد. لذلك يتم إنشاء هذا الأخير وتنفيذ طريقة [init] الخاصة به.
- السطر 4: يمكن الآن إنشاء الكائن الفردي [car1]. ثم يتم تنفيذ طريقة [init] الخاصة به.
- السطر 5: تعرض طريقة [demo2] قيمة الكائن الفردي [car1]
- الأسطر 6-8: يتم تنفيذ طريقة [TearDown] الخاصة بالاختبار. يتم تدمير الكائنات الفردية، مما يؤدي إلى تنفيذ طرق [destroy] الخاصة بها
5.3. المثال 3
نقدم الفئة الجديدة التالية [PersonGroup]:
Imports istia.st.springioc.demos
Namespace istia.st.springioc.demos
Public Class GroupePersonnes
' private fields
Private _membres() As Personne
Private _groupesDeTravail As Hashtable
' public property
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
' default builder
Public Sub New()
End Sub
' object identity string GroupeDePersonnes
Public Overrides Function tostring() As String
' browse the list of group members
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=("
' browse the workgroup dictionary
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
' we return the result
Return identité + "]"
End Function
' init-destroy methods
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
عضواها الخاصان هما:
_members: مصفوفة تضم الأشخاص الأعضاء في المجموعة
_workGroups: قاموس يربط كل شخص بمجموعة عمل
يتم إتاحة الوصول إلى هذه العناصر الخاصة عبر خصائص عامة. الهدف هنا هو توضيح كيف تتيح لك 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>
<!-- people -->
<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>
<!-- a car -->
<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>
<!-- a group of people -->
<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
' the test object
Private factory As XmlObjectFactory
<SetUp()> _
Public Sub init()
' create a factory instance
factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
' log
Console.WriteLine("setup test")
End Sub
<TearDown()> _
Public Sub destroy()
' we destroy the singletons
factory.Dispose()
' follow-up
Console.WriteLine("teardown test")
End Sub
<Test()> _
Public Sub demo3()
' retrieving the singleton [group1]
Dim groupe1 As GroupePersonnes = CType(factory.GetObject("groupe1"), GroupePersonnes)
Console.WriteLine("groupe1={0}", groupe1)
End Sub
End Class
End Namespace
تسترد الطريقة [demo3] العنصر الفردي [group1] وتعرضه.
تظل بنية مشروع Visual Studio كما هي، باستثناء أنه يحتوي الآن على فئتين إضافيتين وملف تكوين.

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

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