2. تكوين تطبيق باستخدام Spring
لنأخذ تطبيقًا كلاسيكيًا ثلاثي المستويات كمثال:
![]() |
سنفترض أن الوصول إلى الطبقة DAO يتم التحكم فيه بواسطة واجهة [IArticlesDao]:
....
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' قائمة بجميع المقالات
Function getAllArticles() As IList
' تضيف مادة
Function ajouteArticle(ByVal unArticle As Article) As Integer
' حذف عنصر
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' تعديل عنصر
Function modifieArticle(ByVal unArticle As Article) As Integer
' البحث عن مقال
Function getArticleById(ByVal idArticle As Integer) As Article
' حذف جميع المقالات
Sub clearAllArticles()
' إدراج بنود ضمن معاملة
Sub doInsertionsInTransaction(ByVal articles As Article())
' يغير مخزون أحد الأصناف
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
في طبقة الوصول إلى البيانات أو طبقة DAO (Data Access Object)، من الشائع العمل باستخدام كائن SGBD. لنفترض الحالة التي يتم فيها الوصول إلى هذا الكائن عبر برنامج تشغيل ODBC. قد يكون الهيكل الأساسي لفئة تصل إلى مصدر ODBC هذا كما يلي:
NameSpace istia.st.articles.dao
Imports System.Data.Odbc
...
Public Class ArticlesDaoPlainODBC
Implements istia.st.articles.dao.IArticlesDao
' الحقول الخاصة
Private connexion As OdbcConnection = Nothing
Private DSN As String
Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)
'استرداد اسم المصدر ODBC
Me.DSN = DSN
' يتم إنشاء سلسلة الاتصال
Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
'إنشاء مثيل للاتصال
connexion = New OdbcConnection(connectString)
End Sub
....
End Class
End NameSpace
لإجراء عملية على المصدر ODBC، تحتاج أي طريقة إلى كائن [OdbcConnection] الذي يمثل الاتصال بقاعدة البيانات والذي سيمر عبره التبادل بينها وبين التطبيق. لإنشاء هذا الكائن، نحتاج إلى ثلاث معلومات:
اسم المصدر ODBC | |
الهوية التي يتم من خلالها إنشاء الاتصال | |
كلمة المرور المرتبطة بهذه الهوية |
تحصل فئتنا [ArticlesDaoPlainODBC] على هذه المعلومات عبر الوكيل الخارجي الذي يقوم بإنشاء مثيل لأحد أعضاء الفئة. قد نتساءل كيف يحصل هذا الوكيل على المعلومات الثلاث اللازمة لإنشاء مثيل لفئة [ArticlesDaoPlainODBC]. لنأخذ مثالاً. لنفترض أننا نريد كتابة فئة اختبار للطبقة [Dao]. سيكون لدينا البنية التالية:
![]() |
قد يكون الهيكل الأساسي لفئة اختبار Nunit [http://www.nunit.org/] كما يلي:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
<TestFixture()> _
Public Class NunitTestArticlesDaoPlainOdbc
' الكائن المراد اختباره
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' يتم إنشاء مثيل للكائن المراد اختباره
articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
End Sub
<Test()> _
Public Sub testGetAllArticles()
' التحقق البصري
listArticles()
End Sub
' عرض الشاشة
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
بيئة الاختبار Nunit هي نسخة مقتبسة لمنصة .NET من بيئة JUnit الموجودة لمنصة Java. في الفئة أعلاه، يتم تنفيذ الأسلوب الذي يحمل السمة <SetUp()> قبل كل أسلوب اختبار. أما الأسلوب الذي يحمل السمة <TearDown()>، فيتم تنفيذه بعد كل اختبار. ولا يوجد أي منها في المثال أعلاه. هنا، نرى أن الطريقة [init] التي تحمل السمة <SetUp()>، تقوم بإنشاء مثيل لكائن [ArticlesDaoPlainODBC] عن طريق تمرير المعلومات الثلاث التي يحتاجها منشئ هذا الكائن بشكل «ثابت».
تصبح فئة الاختبار الخاصة بنا عرضة لأي تغيير في إحدى المعلومات المكتوبة بشكل ثابت. ومن الأفضل أن يتم تسجيل هذه المعلومات في ملف تكوين، لتجنب عمليات إعادة التجميع غير الضرورية عند تغييرها. الحل المعتاد لتكوين التطبيق هو استخدام ملف يحتوي على جميع المعلومات التي قد تتغير بمرور الوقت. هناك تنوع كبير في أنواع ملفات التكوين. الاتجاه السائد حاليًا هو استخدام ملفات XML. وهذا هو الخيار الذي اتخذته Spring. قد يكون الملف الذي يقوم بتكوين كائن [ArticlesDaoPlainODBC] كما يلي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<!-- فئة تنفيذ الواجهة IArticlesDao -->
<description>Gestion d'une table d'articles</description>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
<constructor-arg index="0">
<value>odbc-firebird-articles</value>
</constructor-arg>
<constructor-arg index="1">
<value>SYSDBA</value>
</constructor-arg>
<constructor-arg index="2">
<value>masterkey</value>
</constructor-arg>
</object>
</objects>
يصف ملف تكوين Spring الكائنات التي سيتم إنشاء مثيلات لها. ولا يحدد، بشكل عام، متى سيتم إنشاء هذه المثيلات. وبالتالي، يتم تحديد توقيت إنشاء المثيلات من خلال الكود الذي يستخدم هذا الملف. يمكن إنشاء مثيلات الكائنات الموصوفة في ملف تكوين Spring وتهيئتها بطريقتين مختلفتين:
- عن طريق تحديد المعلمات التي سيتم تمريرها إلى منشئ الكائن، كما هو موضح أعلاه
- عن طريق توفير قيم لخصائص الكائن (Property). في هذه الحالة، يجب أن يمتلك الكائن منشئًا افتراضيًا يستخدمه Spring لإنشاء مثيل له.
غالبًا ما يتم إنشاء الكائنات التي تقوم بدور تقديم خدمة في التطبيق بنسخة واحدة فقط. وتُسمى هذه الكائنات «singletons». وهكذا، في مثال التطبيق متعدد المستويات المقدم في بداية هذا المستند، سيتم توفير الوصول إلى قاعدة بيانات المقالات من خلال نسخة واحدة فقط من الفئة [ArticlesDaoPlainODBC]. بالنسبة لتطبيق الويب، تخدم كائنات الخدمة هذه عدة عملاء في آن واحد. ولا يتم إنشاء كائن خدمة لكل عميل على حدة.
يسمح ملف تكوين Spring أعلاه بإنشاء كائن خدمة فريد من النوع [ArticlesDaoPlainODBC] في حزمة تسمى [istia.st.articles.dao]. يتم تعريف المعلومات الثلاثة اللازمة لمنشئ هذا الكائن داخل علامة <object>...</object>. سيكون عدد علامات <object> هذه مساوياً لعدد الكائنات الفردية المطلوب إنشاؤها.
دعونا نستعرض التكوين بالتفصيل:
<objects> هي العلامة الجذرية لملف تكوين Spring. وهي تحدد وصف الكائنات الفردية (singleton) المطلوب إنشاء مثيلات لها.
العلامة <description> اختيارية. يمكن استخدامها، على سبيل المثال، لوصف دور ملف التكوين.
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
...
</object>
تُستخدم العلامة <object> لوصف كائن سيتم إنشاء مثيل له. ولها هنا سمتان:
- name: معرّف الكائن. سيشير الكود الخارجي إلى الكائن عبر هذا الاسم.
- class: على شكل «اسم الفئة، اسم التجميع». المعلومة الأولى هي الاسم الكامل للفئة المراد إنشاء مثيل لها. أما الثانية، فهي اسم التجميع الذي يحتوي على هذه الفئة. في مثالنا، توجد الفئة في ملف باسم [articlesdao.dll]
يُستخدم محتوى العلامة <object> لوصف طريقة إنشاء مثيل للكائن:
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
<constructor-arg index="0">
<value>odbc-firebird-articles</value>
</constructor-arg>
<constructor-arg index="1">
<value>SYSDBA</value>
</constructor-arg>
<constructor-arg index="2">
<value>masterkey</value>
</constructor-arg>
</object>
لنتذكر توقيع الشركة المصنعة للفئة [ArticlesDaoPlainODBC]:
سيتم إنشاء مثيل للكائن Spring [articlesdao] بواسطة المنشئ أعلاه باستخدام المعلومات الثلاثة الموجودة في ملف التكوين: [odbc-firebird-articles, SYSDBA, masterkey].
متى سيتم إنشاء الكائنات المُعرَّفة في ملف Spring؟ يوجد في كل تطبيق طريقة يُضمن أنها ستكون أول ما يتم تنفيذه. وعادةً ما يتم طلب إنشاء الكائنات الفردية (singletons) ضمن هذه الطريقة. يمكن أن تُعهد عملية تهيئة التطبيق إلى الطريقة main في هذا التطبيق نفسه، إن كان يحتوي على واحدة. بالنسبة للتطبيق ASP.NET، قد تكون هذه هي الطريقة [Application_Start] الموجودة في الملف [global.asax]. بالنسبة لفئة الاختبار الخاصة بنا [Nunit]، تتم تهيئة التطبيق في الطريقة المرتبطة بالسمة <Setup()>.
كيف يمكن استخدام ملف التكوين أعلاه في فئتنا [Nunit]؟ إليك مثال على ذلك:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
<TestFixture()> _
Public Class NunitSpringTestArticlesDaoPlainOdbc
' الكائن المراد اختباره
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' يتم استرداد مثيل من مُنشئ الكائنات Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
' طلب إنشاء مثيل للكائن articles dao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' التحقق البصري
listArticles()
End Sub
' عرض الشاشة
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
تعليقات:
- لإنشاء مثيلات للكائنات الموجودة في ملف تكوين Spring، نستخدم كائنًا من النوع [XmlObjectFactory]. وهو كائن من نوع "Factory"، c.a.d، وهو كائن يُستخدم لإنشاء كائنات أخرى (Factory = مصنع). يحتوي Spring على عدة أنواع من «Factory» وفقًا لملف التكوين المستخدم. هنا، الملف هو XML، وبالتالي نستخدم النوع [XmlObjectFactory].
- وبشكل منطقي إلى حد ما، يحتاج كائن من النوع [XmlObjectFactory] إلى اسم ملف التكوين XML، وهو هنا [spring-config-plainodbc.xml]. وبشكل دقيق تمامًا، يتم إنشاء مثيل للنوع [XmlObjectFactory] باستخدام تدفق قراءة تم إنشاؤه من الملف XML الذي يتم تحديد اسمه.
- بمجرد إنشاء الكائن من النوع [XmlObjectFactory]، يتم الحصول على كائن من ملف التكوين بواسطة [XmlObjectFactory].getObject("المعرف") حيث "المعرف" هو السمة [id] لأحد الكائنات الموجودة في ملف التكوين.
- إذا لم يكن الكائن المطلوب قد تم إنشاء مثيل له من قبل، فإن Spring تقوم بإنشاء مثيل له باستخدام المعلومات الموجودة في ملف التكوين الخاص به، وتقوم بإرجاع مرجع له إلى البرنامج المستدعي. أما إذا كان الكائن قد تم إنشاء مثيل له من قبل، فإن Spring تكتفي بإرجاع مرجع الكائن الموجود بالفعل. وهذا هو مبدأ الكائن الفردي (singleton).
- تجدر الإشارة إلى أن فئة الاختبار [Nunit] لا تعرف اسم فئة الوصول إلى البيانات. هذا الاسم موجود في ملف التكوين. تكتفي فئة الاختبار بطلب كائن ينفذ الواجهة [IArticlesDao]:
' الكائن المراد اختباره
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
...
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
وهنا يكمن الجوهر في Spring. فإذا قمنا بتغيير فئة التنفيذ، فلن نضطر إلى تغيير فئة الاختبار الخاصة بنا. سنقوم ببساطة بتعديل ملف تكوين Spring. أما فئة الاختبار، فهي تكتفي بالعمل مع واجهة وليس مع فئة.
لنختتم هذا العرض ببعض النقاط العملية.
عندما نكتب «Spring سيقوم بإنشاء مثيل...»، ما الذي نعنيه بالضبط؟ بالنسبة لمنصة .NET، يتضمن Spring ثلاثة ملفات:

بالنسبة لمشروع .NET الذي تم إنشاؤه باستخدام Visual Studio ويجب أن يستخدم Spring، سنقوم بما يلي:
- سيتم وضع الملفات الثلاثة المذكورة أعلاه في مجلد [bin] الخاص بالمشروع
- يجب أن يكون ملف [Spring.Core.dll] ضمن مراجع المشروع:

- يجب أن تستورد الفئات التي تستخدم Spring بعض مساحات الأسماء، ومن بينها غالبًا ما يلي:
نقطة عملية أخرى: أين نضع ملف تكوين Spring؟ هناك عدة أماكن محتملة. أحدها هو المجلد [bin] الخاص بالمشروع. وهنا تم وضع الملف [spring-config-plainodbc.xml] الخاص بالمثال الذي تمت دراسته.

