2. مقدمة إلى إطار عمل Spring
ظهر Spring في عام 2004 في البداية كحاوية للكائنات. ومنذ ذلك الحين، تطور إلى عدة فروع: Spring MVC، وSpring Data، وSpring Batch، ... [http://spring.io]. لن نستعرض في هذا الفصل سوى حاوية الكائنات. وفيما يلي بعض النقاط المرجعية:
- يحتوي التطبيق على فئات متعددة، وبعضها يتشارك كائنات يجب أن تكون فريدة (singletons). يقوم Spring بإنشاء وإدارة هذه الكائنات الفريدة؛
- يضع Spring هذه الكائنات الفردية في بنية تُسمى السياق؛
- تتمكن الفئات من الوصول إلى الكائنات الفردية للتطبيق عن طريق طلبها من Spring باستخدام اسمها أو نوعها أو كليهما؛
- يقوم Spring بإنشاء الكائنات الفردية وإدارة تبعياتها المحتملة: فقد يحتوي الكائن الفردي بالفعل على مراجع إلى كائن فردي آخر أو أكثر. وعندما يقوم Spring بإنشاء كائن فردي، فإنه يقوم أيضًا بإنشاء تبعياته؛
- عندما يبدأ تشغيل تطبيق يعتمد على Spring، يمكنه أن يطلب من Spring إنشاء جميع الكائنات الفردية الخاصة بالتطبيق. وستكون هذه الكائنات متاحة بعد ذلك في سياق Spring؛
- يسهل Spring استخدام البنى الطبقية والبرمجة عبر الواجهات. في الحالات البسيطة، يتم تنفيذ كل طبقة بواسطة كائن فريد (singleton) ويقوم بتنفيذ واجهة. إذا كان التطبيق يعمل مع واجهات الطبقات وليس مع فئات التنفيذ الخاصة بها، فإننا نحصل على بنية قابلة للتطوير تسمح بتغيير تنفيذ طبقة ما دون تغيير الطبقات الأخرى بفضل الميزتين التاليتين:
- يحصل التطبيق على مرجع للطبقة عبر اسمها. ويقدم Spring له مرجعًا للفئة التي تنفذ الطبقة؛
- يستخدم التطبيق هذه الإشارة كإشارة إلى واجهة الطبقة وليس كإشارة إلى فئة؛
يمكن إعلان العناصر الفردية (singletons) بثلاث طرق يمكن مزجها:
- داخل ملف XML،
- في فئة تكوين خاصة؛
- مع أي فئة باستخدام التعليقات التوضيحية؛
نقدم فيما يلي ثلاثة أمثلة للتكوين:
- [exemple-01]: تكوين مركزي في ملف واحد XML؛
- [exemple-02]: تكوين مركزي في فئة Java واحدة؛
- [exemple-03]: تكوين موزع على عدة فئات Java؛
المثال الأخير [exemple-04] يركز على تكوين Spring لبنية ذات طبقات. وهذا هو المثال الأهم. وسيتم الرجوع إليه باستمرار لتكوين البنى الواردة في هذا المستند.
تضع هذه الأمثلة الأربعة الأسس لما يلي:
- تكوين Spring وحقن التبعيات؛
- استخدام Maven لإدارة تبعيات المشروع؛
- استخدام JUnit لاختبار المشاريع؛
2.1. إعداد بيئة العمل
يجب أن يكون لديك:
- تثبيت JDK (Java Development Kit) (الفقرة 23.1)؛
- تثبيت مدير التبعيات Maven (الفقرة 23.2)؛
- تثبيت مجموعة أدوات Spring (IDE) (STS) (الفقرة 23.3)؛
- قم بتنزيل أكواد المستند [http://tahe.developpez.com/java/spring-database]؛
استيراد إعدادات التشغيل من المجلد [eclipse config] الخاص بالأمثلة إلى STS. هذه الإعدادات مهمة بشكل خاص. تتطلب بعض المشاريع، من أجل تنفيذها، تمرير معلمات إلى JVM، وعادةً ما يمثل هذا النوع من التكوينات مشكلة صعبة. علاوة على ذلك، يستخدم هذا المستند مشاريع Maven. عندما تظهر لك التحذير التالي:
ملاحظة: قم بتشغيل [Alt-F5] لإعادة إنشاء جميع مشاريع Maven.
يُنصح بشدة باتباع هذه الإرشادات. فبدون اتخاذ هذا الإجراء الاحترازي، قد تظهر أخطاء غير مفهومة في المشاريع لمجرد أن التبعيات بين مشاريع Maven غير صحيحة.
![]() |
- إلى [1]، انقر بزر الماوس الأيمن على [Package Explorer]؛
![]() |
- في [4a-4b-4c] حدد المجلد [eclipse config / launch configurations] [4b] الخاص بالأمثلة؛
- في [5]، ستظهر الإعدادات المتاحة. اختر جميعها؛
- في [6]، أكمل المعالج؛
- في [7-8]، اعرض تكوينات التشغيل المستوردة؛
![]() |
- في [8-9]، قم بإلغاء تحديد [9] لعرض تكوينات المشاريع غير المحملة في STS. وهذا هو الحال حاليًا؛
![]() |
- في [10]، تطبيقات Java، وفي [11]، تكوينات التشغيل للأمثلة الثلاثة الأولى التي سندرسها؛
- في [12]، الاختبارات JUnit وفي [13] تكوين التشغيل للمثال الرابع في هذه الفقرة؛
الآن، قم بإنشاء متغير Eclipse باسم [M2_REPO] سيشير إلى مجلد مستودع Maven المحلي (انظر الفقرة 23.2). يُستخدم هذا المتغير في عدة تكوينات للتنفيذ:
![]() |
تم تعيين القيمة الموضحة في [6] أدناه للمتغير [M2_REPO]:
![]() |
الآن، قم باستيراد الأمثلة الأربعة من المجلد [spring-core]:
![]() |
- إلى [1]، انقر بزر الماوس الأيمن في [Package Explorer]؛
![]() |
- إلى [4a-4b]، ثم حدد المجلد [spring-core] الذي يحتوي على الأمثلة؛
- في [5]، حدد جميع المشاريع الموجودة في المجلد وقم بتنفيذ [Finish]؛
- في [6]، المشاريع الأربعة الموجودة في [Package Explorer]؛
2.2. مثال-01
2.2.1. مشروع Eclipse
![]() |
2.2.2. الفئة [Personne]
![]() |
package istia.st.spring.core;
public class Personne {
// الحقول
private String nom;
private String prenom;
private int age;
// المنشئات
public Personne() {
}
public Personne(String nom, String prénom, int âge) {
this.nom = nom;
this.prenom = prénom;
this.age = âge;
}
// toString
public String toString() {
return String.format("Personne[%s, %s,%d]", prenom, nom, age);
}
// أدوات الاسترجاع والتعيين
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
ملاحظة: يمكن إنشاء دالات الاسترجاع (getters) والتعيين (setters) تلقائيًا بالطريقة التالية [1-2]:
![]() |
2.2.3. الفئة [Appartement]
![]() |
package istia.st.spring.core;
public class Appartement {
// الحقول
private Personne proprietaire;
private int surface;
// وظائف الحصول والتعيين
public Personne getProprietaire() {
return proprietaire;
}
public void setProprietaire(Personne proprietaire) {
this.proprietaire = proprietaire;
}
public int getSurface() {
return surface;
}
public void setSurface(int surface) {
this.surface = surface;
}
// toString
public String toString() {
return String.format("Appartement[%s, %s]", proprietaire, surface);
}
}
ملاحظة: لا تحتوي هذه الفئة على مُنشئ صريح. في هذه الحالة، يوجد دائمًا بشكل افتراضي مُنشئ بدون معلمات لا يقوم بأي شيء. عند إنشاء مُنشئات، لا يعود هذا المُنشئ الافتراضي موجودًا ضمنيًا. لذا يجب تعريفه صراحةً:
2.2.4. ملف تكوين Spring
![]() |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- الشخص 01 -->
<bean id="personne_01" class="istia.st.spring.core.Personne">
<constructor-arg index="0" value="dubois" />
<constructor-arg index="1" value="paul" />
<constructor-arg index="2" value="34" />
</bean>
<!-- الشخص 02 -->
<bean id="personne_02" class="istia.st.spring.core.Personne">
<property name="nom" value="martin" />
<property name="prenom" value="micheline" />
<property name="age" value="18" />
</bean>
<!-- قائمة بالأشخاص -->
<util:list id="club">
<ref bean="personne_01" />
<ref bean="personne_02" />
</util:list>
<!-- شقة -->
<bean id="appartement" class="istia.st.spring.core.Appartement">
<property name="surface" value="100" />
<property name="proprietaire" ref="personne_01" />
</bean>
</beans>
- السطران 2 و27: يتم تعريف العناصر الفردية داخل علامة <beans>؛
- الأسطر 6-10: يتم تعريف كل عنصر فريد بواسطة علامة <bean>؛
- السطر 6: [id] هو معرّف الكائن الفردي. [class] هو الاسم الكامل للفئة المراد إنشاء مثيل لها؛
- الأسطر 7-9: القيم الثلاث التي يجب تمريرها إلى منشئ الفئة [Personne]؛
- الأسطر 12-16: يتم أولاً إنشاء الفئة [Personne] باستخدام منشئها الافتراضي [new Personne()]. ثم بالنسبة لكل علامة [property]، يتم استخدام دالة تعيين (setter) تابعة للفئة. على سبيل المثال، بالنسبة للسطر 13، سيتم تنفيذ الدالة [setNom("martin")]. لذا يجب أن تكون الدالة [setNom] موجودة. هذه نقطة مهمة يجب تذكرها؛
- الأسطر 18-21: تسمح العلامة <util:list> بتعريف عنصر فردي (singleton) يمثل قائمة؛
- السطر 19: يشير إلى الكائن الفردي [personne_01] المُعرَّف في السطر 6. ما لدينا هنا هو ما يُسمى «حقن التبعيات». يمكن استخدام سمتين لتهيئة حقل الكائن الفردي:
- [value]: لتعيين قيمة بدائية للحقل (سلسلة، رقم، تاريخ، ...)،
- [ref]: لتعيين مرجع كائن Spring للحقل؛
ملاحظة: يمكن إنشاء ملف تكوين Spring بالطريقة التالية [1-4]:
![]() |
2.2.5. الفئة القابلة للتنفيذ
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo01 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// استرداد سياق Spring
ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
// استرداد الفاصوليا
Personne p01 = ctx.getBean("personne_01", Personne.class);
Personne p02 = ctx.getBean("personne_02", Personne.class);
List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
Appartement appart01 = ctx.getBean(Appartement.class);
// عرضها
System.out.println("personnes--------");
System.out.println(p01);
System.out.println(p02);
System.out.println("club--------");
for (Personne p : club) {
System.out.println(p);
}
System.out.println("appartement--------");
System.out.println(appart01);
// الـ beans التي تم استردادها هي كائنات فردية
// يمكن طلبها عدة مرات، ويتم استرداد نفس الكائن دائمًا
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- السطر 14: ينشئ سياق Spring. يتم عندئذٍ إنشاء مثيلات لجميع العناصر الفردية (singletons) المُعرَّفة في الملف [config-01.xml]؛
- السطر 16: يطلب مرجعًا إلى الكائن الفردي المحدد بـ [personne_01] من النوع [Personne]. هذا المعامل الثاني اختياري، ولكن في هذه الحالة يتم الحصول على مرجع لنوع [Object]، وهو مرجع يجب تحويله إلى النوع [Personne]؛
- السطر 19: لا نستخدم اسم البين، بل نوعه فقط، لأنه لا يوجد سوى عنصر فردي من النوع [Appartement]؛
- السطر 18: تم استخدام كل من المعرف ونوع الكائن الفريد المطلوب. المعرف زائد عن الحاجة نظرًا لوجود كائن فريد واحد فقط من النوع [new ArrayList<Personne>().getClass()]؛
- السطران 32-33: يوضحان أنه عند طلب نفس العنصر الفريد عدة مرات، نحصل دائمًا على نفس المرجع، مما يدل على أننا نتعامل بالفعل مع عنصر فريد. من المهم فهم هذه النقطة؛
ملاحظة: يمكن إنشاء فئة قابلة للتنفيذ بالطريقة التالية [1-6]:
![]() |
![]() |
- إن تحديد خانة [6] هو ما سيجعل الفئة التي تم إنشاؤها تحتوي على طريقة ثابتة [main]، مما يجعلها قابلة للتنفيذ؛
2.2.6. تبعيات المشروع
![]() |
- تبعيات Spring: [spring-core, spring-beans, spring-context, spring-expression, commons-logging]؛
تُضاف التبعيات إلى المشروع بالطريقة التالية:
![]() |
- في [1]: انقر بزر الماوس الأيمن على المشروع / [Build Path] / [Configure Build Path]؛
![]() |
- إلى [2]: [Add JARs] إذا كانت ملفات JARs المراد إضافتها موجودة في مجلد تابع للمشروع. وإلا، فسيكون [Add External JARs]؛
![]() |
- إلى [3]، حدد ملفات JARs المراد إضافتها إلى ملف ClassPath الخاص بالمشروع (توجد هنا في المجلد [lib] داخل المشروع)؛
التعريف: ملف [ClassPath] الخاص بمشروع ما هو مجموعة المجلدات التي تستكشفها آلة Java الافتراضية (JVM) التي تقوم بتنفيذ المشروع، بحثًا عن فئة يشير إليها المشروع. بالنسبة لمشروع Eclipse، يتكون [ClassPath] من العناصر التالية:
- مجلد [bin] الخاص بالمشروع؛
- عناصر مجلد [Build Path] الخاص بالمشروع؛
المجلد [bin] هو المجلد الناتج عن ترجمة المجلد [src]. وبالتالي، فإن كل ما يتم وضعه في المجلد [src] يصبح تلقائيًا جزءًا من المجلد [ClassPath] (حتى لو لم يكن ملفًا بصيغة .java). وبالتالي، في المشروع السابق، سيصبح ملف تكوين Spring [config-01.xml] الموجود في المجلد [src] جزءًا من المجلد [Classpath] للمشروع عند التنفيذ.
2.2.7. النتائج
févr. 21, 2014 1:16:23 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3ac67f69: startup date [Fri Feb 21 13:16:23 CET 2014]; root of context hierarchy
févr. 21, 2014 1:16:23 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
Infos: Loading XML bean definitions from class path resource [config-01.xml]
personnes--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
club--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
appartement--------
Appartement[Personne[paul, dubois,34], 100]
beans [p01,p01b] identiques ? true
2.3. مثال-02
2.3.1. مشروع Eclipse
![]() |
2.3.2. فئة التكوين في Spring
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public Personne personne_01() {
return new Personne("Paul", "Dubois", 34);
}
@Bean
public Personne personne_02() {
return new Personne("Martin", "Micheline", 18);
}
@Bean
public List<Personne> club(Personne personne_01, Personne personne_02) {
List<Personne> personnes = new ArrayList<Personne>();
personnes.add(personne_01);
personnes.add(personne_02);
return personnes;
}
@Bean
public Appartement appartement(Personne personne_01) {
Appartement appartement = new Appartement();
appartement.setSurface(200);
appartement.setPropriétaire(personne_01);
return appartement;
}
}
- السطر 9: التعليق التوضيحي [@Configuration] هو تعليق توضيحي لـ Spring. وهو يشير إلى أن الفئة المُعلَّمة تُعرِّف كائنات فردية. ويتم تعريف هذه الكائنات باستخدام التعليق التوضيحي [@Bean]. سيقوم Spring بتنفيذ جميع الطرق المُعلَّمة بعلامة [@Bean]. وتقوم هذه الطرق بإنشاء الكائنات الفردية للتطبيق؛
- الأسطر 12-15: تُعرّف كائنًا فريدًا يُعرف بـ [personne_01]، أي اسم الأسلوب.
- السطر 23: تحمل المعلمات [personne_01, personne_02] أسماء الكائنات الفردية. سيقوم Spring تلقائيًا بتهيئتها باستخدام مراجع هذه الكائنات الفردية. ويُطلق على ذلك اسم «حقن المعلمات»؛
هذه الطريقة في تكوين الكائنات الفردية أكثر وضوحًا من تلك التي تستخدم الملف XML. في الواقع، نحن نعيد إنتاج ما كان يقوم به Spring ضمناً استناداً إلى الملف XML.
2.3.3. الفئة القابلة للتنفيذ
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo02 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// استرداد سياق Spring
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
// يتم استرداد الكائنات
Personne p01 = ctx.getBean("personne_01", Personne.class);
Personne p02 = ctx.getBean("personne_02", Personne.class);
List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
Appartement appart01 = ctx.getBean(Appartement.class);
// يتم عرضها
System.out.println("personnes--------");
System.out.println(p01);
System.out.println(p02);
System.out.println("club--------");
for (Personne p : club) {
System.out.println(p);
}
System.out.println("appartement--------");
System.out.println(appart01);
// الـ beans التي تم استردادها هي كائنات فردية
// يمكن طلبها عدة مرات، ويتم استرداد نفس البين دائمًا
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- السطر 13 يؤدي إلى إنشاء مثيلات لجميع الفاصوليا المُعرَّفة في الفئة [Config]؛
- لا يتغير باقي الكود؛
2.3.4. تبعيات المشروع
![]() | ![]() |
يتم تحديد التبعيات بواسطة الملف [pom.xml] التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-core-02</name>
<description>Introduction à Spring</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
</dependencies>
<!-- المكونات الإضافية -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
تصبح إدارة تبعيات المشروع يدويًا أمرًا صعبًا عند استخدام مكتبات Java التي لا نعرف تبعياتها. فعلى سبيل المثال، يحتوي إطار العمل [Hibernate] الذي يدير الوصول إلى قواعد البيانات على عشرات التبعيات. ويحل المشروع [Maven] هذه المشكلة. يتم تحديد اسم التبعية المطلوبة، ثم يتم البحث عنها تلقائيًا في مستودعات Maven المنتشرة عبر الإنترنت. وإذا كانت التبعية المطلوبة تحتوي بدورها على تبعيات أخرى، فسيتم تنزيلها تلقائيًا أيضًا. ويتم تخزين هذه التبعيات التي تم تنزيلها في مستودع محلي على الجهاز. وإذا احتاج تطبيق آخر لاحقًا إلى نفس التبعية، فلن يتم تنزيلها من جديد بل سيتم البحث عنها في المستودع المحلي. تتميز التبعية بالعناصر التالية:
- السطر 17: علامة <dependency>؛
- السطر 18: سمة [groupId] التي تحدد عادةً الشركة التي أنشأت التبعية؛
- السطر 19: سمة [artifactId] التي تحدد التبعية؛
- السطر 20: سمة [version] التي تحدد الإصدار المطلوب؛
سيؤدي إنشاء المشروع بحد ذاته إلى إنتاج مكون Maven محدد بالسطور 4-8:
- الأسطر 4-6: السمات [ groupId, artifactId,version] التي وصفناها للتو؛
- الأسطر 7-8: هي سمات اختيارية؛
سنعود لاحقًا إلى دور الأسطر 24-40. لتحويل مشروع Eclipse عادي إلى مشروع Maven، يجب القيام بأمرين:
- إنشاء الملف [pom.xml] السابق؛
- الإعلان عن أن المشروع أصبح الآن مشروع Maven [1-4]:
![]() |
يحتوي رمز مشروع Maven على حرف M [4]. يشير الحرف S إلى أن المشروع يحتوي على عناصر Spring. لا يُنصح بتحويل (كما فعلنا للتو) مشروع Eclipse إلى مشروع Maven لأن المشروع عندئذٍ لا يمتلك البنية المتوقعة لمشروع Maven، مما قد يؤدي أحيانًا إلى مشاكل غير متوقعة.
2.3.5. إنشاء أرتيفاكت Maven للمشروع
نُطلق على «أرتيفاكت مافن» للمشروع اسم العنصر المُعرَّف في الأسطر 4-6 من الملف [pom.xml]:
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
لإنشاء هذا الأرتيفاكت، يجب أن تتواجد الأسطر 3-7 التالية في الملف [pom.xml]:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
فهي تحدد المكون الإضافي لـ Maven القادر على إنشاء عنصر المشروع. ثم يتم اتباع الخطوات التالية:
![]() |
يتم نقل الأرتيفاكت الذي تم إنشاؤه بهذه الطريقة إلى مستودع Maven المحلي. يمكن العثور على موقع هذا المستودع في إعدادات Eclipse:
![]() |
ومن ثم يمكن التحقق من التثبيت الصحيح لـ «أرتيفاكت» Maven:
![]() |
من الآن فصاعدًا، سيتمكن أي مشروع Maven محلي آخر من استخدام هذا الأرشيف.
2.4. مثال-03
2.4.1. مشروع Eclipse
سنقوم هذه المرة بإنشاء مشروع Maven [1-8]:
![]() |
![]() |
- في [3b]: حدد مجلدًا فارغًا سيتم إنشاء المشروع فيه؛
![]() |
- إلى [4]: معرّف مجموعة Maven التي سينتمي إليها المشروع؛
- في [5]: اسم عنصر Maven الذي تم إنتاجه:
- في [6]: إصداره؛
- في [7]: طريقة التعبئة (توجد أيضًا war، ear، apk، ...)؛
- في [8]: المشروع الذي تم إنشاؤه بهذه الطريقة؛
يحتوي مشروع Maven افتراضيًا على هيكل مجلدات محدد:
- [src / main / java]: شفرات المصدر الخاصة بالمشروع. ستنتقل النتائج المُجمَّعة من هذه المصادر إلى مجلد [target/classes] الخاص بالمشروع؛
- [src / main / resources]: الموارد التي يجب أن تكون موجودة في مسار الفئات (Classpath) للمشروع دون أن تكون شفرات Java. سيتم نسخها كما هي إلى مجلد [target/classes] الخاص بالمشروع؛
- [src / test / java]: أكواد مصدر اختبارات المشروع. ستُنقل النتائج المُجمَّعة من هذه المصادر إلى مجلد [target/test-classes] الخاص بالمشروع. لا يتم تضمين هذه العناصر في أرشيف Maven الخاص بالمشروع؛
- [src / test / resources]: الموارد التي يجب أن تكون موجودة في مسار الفئات (Classpath) للمشروع من أجل إجراء الاختبارات، دون أن تكون بالضرورة ملفات مصدر جافا. سيتم نسخها كما هي إلى المجلد [target/test-classes] التابع للمشروع؛
نكمل المشروع على النحو التالي:
![]() |
2.4.2. تكوين Maven
يتم إنشاء ملف [pom.xml] افتراضيًا. نقوم بتعديله على النحو التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-core-03</name>
<description>Introduction à Spring</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<!-- مشروع Maven الأصلي -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- سياق Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- السجلات -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- المكونات الإضافية -->
<build>
<plugins>
<!-- لإنشاء أرشيف المشروع مع تبعياته -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- لتثبيت مكون المشروع في مستودع Maven المحلي -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- السطر 11: يتم ترميز المشروع بـ UTF-8؛
- السطر 12: نستخدم JDK 1.8 لتجميع المشروع؛
- الأسطر 16-20: بالنسبة للمشاريع التي تستخدم مكتبات Spring، من الملائم استخدام مشروع Maven أم يُسمى [spring-boot-starter-parent]. ويحدد هذا المشروع إصدارات مكتبات Spring المختلفة بالإضافة إلى إصدارات تبعياتها. وهذا يتيح عدم الحاجة إلى تحديدها في تعريف التبعيات. وبالتالي، في الأسطر 24-27، لا يتم تحديد الإصدار المطلوب من [spring-context]. سيكون الإصدار هو الذي حدده المشروع الأصلي [spring-boot-starter-parent]. تتيح هذه التقنية عدم القلق بشأن أي تعارض محتمل بين إصدارات التبعيات. فالتبعيات التي يحددها المشروع الأصلي متوافقة فيما بينها؛
- الأسطر 29-32: يقوم Spring بكتابة كمية كبيرة من المعلومات على وحدة التحكم من خلال مكتبة سجلات. يتم استيراد هذه المكتبة هنا؛
- الأسطر 40-47: مكون إضافي لـ Maven سنعود إليه لاحقًا؛
- الأسطر 50-52: المكون الإضافي لإنشاء أرتيفاكت Maven الخاص بالمشروع؛
2.4.3. فئة تكوين Spring
![]() |
فئة [Config] هي كما يلي:
package istia.st.spring.core.config;
import istia.st.spring.core.entities.Personne;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {
@Bean
public Personne personne_01() {
return new Personne("Paul", "Dubois", 34);
}
@Bean
public Personne personne_02() {
return new Personne("Martin", "Micheline", 18);
}
@Bean
public List<Personne> club(Personne personne_01, Personne personne_02) {
List<Personne> personnes = new ArrayList<Personne>();
personnes.add(personne_01);
personnes.add(personne_02);
return personnes;
}
@Bean
public int mySurface() {
return 200;
}
}
- نجد هنا كودًا تم التعليق عليه مسبقًا مع إضافة ميزتين جديدتين:
- السطر 13: يشير إلى وجود مكونات أخرى يجب إنشاء مثيلات لها في الحزمة [spring.core.entities]،
- الأسطر 34-37: مكون [mySurface]؛
2.4.4. الفئة [Appartement]
![]() |
package spring.core.entities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class Appartement {
// الحقول التي يتم حقنها بواسطة Spring
@Autowired
@Qualifier("personne_01")
private Personne propriétaire;
@Autowired
@Qualifier("mySurface")
private int surface;
// وظائف الحصول والتعيين
public Personne getPropriétaire() {
return propriétaire;
}
public void setPropriétaire(Personne propriétaire) {
this.propriétaire = propriétaire;
}
public int getSurface() {
return surface;
}
public void setSurface(int surface) {
this.surface = surface;
}
// toString
public String toString() {
return String.format("Appartement[%s, %s]", propriétaire, surface);
}
}
- السطر 7: تشير التعليقة التوضيحية [@Component] إلى Spring بأن الفئة هي فئة فردية (singleton) يجب على إطار العمل إنشاء مثيل لها وإدارتها. وذلك لأننا في الفئة [Config]، كتبنا [@ComponentScan({ "istia.st.spring.core.entities" })]، وبالتالي سيتم العثور على هذه الفئة الفردية؛
- السطر 11: يطلب من Spring حقن مرجع أحد الكائنات الفردية في الحقل. ويمكن تعريف هذا المرجع بطريقتين:
- عن طريق معرّفه (السطران 12 و16)،
- عن طريق نوعه إذا كان هناك كائن فريد واحد فقط من هذا النوع؛
2.4.5. تنفيذ المشروع
يؤدي تشغيل المشروع إلى ظهور النتيجة التالية في وحدة التحكم:
- الأسطر 1-3: يُنشئ Spring عددًا كبيرًا جدًّا من السجلات، يصل إلى عدة عشرات من الأسطر. قد تكون هذه السجلات مفيدة جدًّا لتصحيح أخطاء مشروع لا يعمل. وعندما يعمل المشروع، يمكن تقليل السجلات بالطريقة التالية:
![]() |
في المجلد [src / main / resources]، نقوم بإنشاء الملف [logback.xml] التالي:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- يتم تعيين النوع افتراضيًا للمشفرات
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- التحكم في مستوى السجلات -->
<root level="info"> <!-- معلومات، تصحيح أخطاء، تحذير -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
- في السطر 12، نحدد مستوى السجلات. يمثل [debug] مستوىً مفصلاً للغاية، بينما يمثل [info] مستوىً أقل تفصيلاً بكثير؛
فيما يلي النتائج باستخدام [level=info]:
لم يتبق سوى سطر واحد من السجلات.
2.4.6. إنشاء أرشيف المشروع مع تبعياته
يمكن أيضًا استخدام الأرشيف الذي تم إنشاؤه في المشروع السابق في مشروع Eclipse لا يستخدم Maven. تستخدم بعض المشاريع العديد من المكتبات وقد يكون من الصعب عدم نسيان أي منها. وهنا يبرز دور Maven بشكل رائع، حيث يكفي تسمية التبعية ذات المستوى الأعلى حتى يتم إضافة التبعيات الأخرى ذات المستويات الأدنى تلقائيًا إلى مسار الفئات (Classpath) للمشروع. عندما يحتاج مشروع Eclipse غير مستخدم لـ Maven إلى استخدام أرشيفات مشروع Maven، يمكن إنشاء أرتيفاكت هذا الأخير مع جميع تبعياته (وهو ما لم يكن الحال في المشروع السابق). لإجراء هذا الإنشاء، يجب أن تكون الأسطر 3-10 التالية موجودة في الملف [pom.xml]:
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
فهي تحدد المكون الإضافي لـ Maven القادر على إنشاء منتج المشروع مع تبعياته. بعد ذلك، يتم اتباع الخطوات التالية في ملف [1-6]:
![]() |
![]() |
- [4-6] تمثل تكوينًا لتنفيذ Maven؛
- في [4]، أدخل أي اسم؛
- في [5]، حدد مجلد المشروع؛
- في [6]، أدخل أهداف Maven (goals):
- [clean]: يتم حذف مجلد المشروع [target]؛
- [compile]: يتم تجميع المشروع. يتم وضع مخرجات التجميع في مجلد [target] الذي تم إعادة إنشاؤه؛
- [assembly:single]: يتم وضع فئات المشروع وتبعياته في أرشيف jar واحد داخل المجلد [target]؛
بعد التنفيذ، نحصل على النتيجة التالية:
![]() |
ملف jar هو ملف مضغوط يمكن فتحه باستخدام برنامج فك الضغط. بمجرد فك ضغط الملف السابق، نحصل على الشجرة التالية:
- في [8]، فئات تبعيات المشروع؛
- في [9]، فئات المشروع نفسه؛
2.5. مثال-04
2.5.1. الهدف
يستند هذا المثال إلى أحد الأمثلة الواردة في المستند [Introduction à Spring IoC] الذي يوضح دور Spring في تكوين البنى متعددة الطبقات. في المستند الأصلي، تمت معالجة المثال باستخدام تكوين Spring تم إنشاؤه بواسطة ملف XML. هنا نعالج المثال باستخدام تكوين عبر فئات Java والتعليقات التوضيحية.
نريد هنا تكوين مشروع Spring للبنية التالية:
![]() |
تتميز كل طبقة بواجهة مُنفَّذة بواسطة فئتين. نريد أن نوضح أنه بفضل Spring، يمكن تغيير تنفيذ إحدى الطبقات دون أن يؤثر ذلك بأي شكل على كود الطبقات الأخرى.
2.5.2. مشروع Eclipse
2.5.2.1. Génération
نقوم بإنشاء نوع جديد من المشاريع:
![]() |
![]() |
- في [4]، أدخل اسم مشروع Eclipse؛
- في [5]، اختر مشروع Maven؛
- في [6]، اختر إصدار Java >=1.7؛
- في [7]، اختر إصدار Spring Boot المقترح؛
- المعلومات الواردة في [8-11] هي معلومات خاصة بـ Maven؛
- في [12]، يمكن اختيار واحد أو أكثر من التبعيات المقترحة. سيؤدي ذلك إلى دمج عدد معين من التبعيات في ملف Maven [pom.xml]؛
![]() |
- في [13]، حدد مجلدًا موجودًا وفارغًا لاستيعاب المشروع؛
- في [14]، المشروع الذي تم إنشاؤه. سنقوم بتحليل عناصره؛
المشروع هو مشروع Maven تم تكوينه بواسطة الملف [pom.xml] التالي:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-core-04</name>
<description>Programmation par interfaces</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- البحث عن العنصر الأصلي من المستودع -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.SpringCore04Application</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- الأسطر 6-12: تتضمن المعلومات التي تم إدخالها في معالج إنشاء المشروع؛
- الأسطر 14-19: مشروع Maven الأصلي الذي يحدد عددًا من المكتبات مع إصداراتها. إذا كانت إحداها تابعة للمشروع، يتم ذكرها في الملف [pom.xml] دون ذكر إصدارها؛
- السطر 23: لا يُستخدم هذا السطر إلا إذا كنا ننوي إنشاء أرشيف قابل للتنفيذ للمشروع. وإلا فإنه يظل غير مستخدم؛
- الأسطر 28-31: التبعيات الدنيا لمشروع Spring Boot. تجدر الإشارة إلى أننا لم نختر أي تبعيات من قائمة الاختيار؛
- الأسطر 33-37: التبعية اللازمة لإدارة اختبارات الوحدة JUnit [http://junit.org/] المدمجة مع Spring. تشير السطر 36 إلى أن التبعية ضرورية للاختبارات فقط. وبالتالي، لن يتم تضمينها في أرشيف المشروع؛
- الأسطر 42-45: المكون الإضافي الذي يسمح بإنشاء أرتيفاكت Maven الخاص بالمشروع؛
قائمة التبعيات التي يجلبها هذا الملف هي التالية [1]:
![]() |
سنرى أنها كافية لما نريد القيام به هنا.
2.5.2.2. الفئة القابلة للتنفيذ
![]() |
الفئة القابلة للتنفيذ [SpringCore04Application] [[2] هي كما يلي:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringCore04Application {
public static void main(String[] args) {
SpringApplication.run(SpringCore04Application.class, args);
}
}
- في السطر 6، التعليق التوضيحي [@SpringBootApplication] هو اختصار للتعليقات التوضيحية الثلاثة [@Configuration, @EnableAutoConfiguration, @ComponentScan]، مما يعني:
- أن الفئة [SpringCore04Application] هي فئة تكوين Spring؛
- أنه يُطلب من Spring Boot إجراء التكوينات استنادًا إلى الفئات التي سيجدها في مسار الفئات (Classpath) للمشروع، أي هنا في تبعيات Maven؛
- أن يفحص المجلد الحالي (مجلد الفئة [SpringCore04Application]) للبحث عن أي مكونات Spring أخرى محتملة؛
- السطر 10: يتم تنفيذ الطريقة الثابتة [SpringApplication.run]. المعلمة الأولى لها هي فئة تكوين Spring، وهي هنا الفئة [SpringCore04Application]. أما المعلمة الثانية فهي هنا قائمة الحجج التي تم تمريرها إلى الطريقة [main] (السطر 9). تتمثل وظيفة الطريقة الثابتة [SpringApplication.run] في إنشاء سياق Spring، أي إنشاء مختلف «البيانات» الموجودة إما في فئات التكوين أو في المجلدات التي تم استكشافها بواسطة التعليق التوضيحي [@ComponentScan]. أما الطريقة [main] هنا فلا تقوم بأي شيء آخر. ولإضفاء المزيد من الجوهر عليها، سنقوم بتعديلها على النحو التالي:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringCore04Application {
public static void main(String[] args) {
// إنشاء مثيل لسياق Spring
ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
// عرض السياق
System.out.println("---------------- Liste des beans Spring");
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName);
}
// إغلاق السياق
context.close();
}
}
- السطر 12: تُرجع الطريقة الثابتة [SpringApplication.run] سياق Spring الذي قامت بإنشائه؛
- الأسطر 15-17: يتم عرض أسماء جميع الفاصوليا في هذا السياق؛
يمكن تشغيل التطبيق بالطريقة التالية: [1-3]. الطريقة المعتادة [Run As Java Application] صالحة أيضًا.
![]() |
نحصل على النتيجة التالية:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.2.3.RELEASE)
2015-04-08 11:18:38.254 INFO 4796 --- [ main] demo.SpringCore04Application : Starting SpringCore04Application on Gportpers3 with PID 4796 (D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04\target\classes started by ST in D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04)
2015-04-08 11:18:38.295 INFO 4796 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.776 INFO 4796 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2015-04-08 11:18:38.788 INFO 4796 --- [ main] demo.SpringCore04Application : Started SpringCore04Application in 0.773 seconds (JVM running for 1.335)
---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
2015-04-08 11:18:38.789 INFO 4796 --- [ main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.790 INFO 4796 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
- الأسطر 14-28: مكونات بيئة Spring. لا نعرف دورها. نجد المكون [springCore04Application] في السطر 18 الذي يصبح تلقائيًا مكونًا لـ Spring بفضل تعليمه التوضيحي [@SpringBootApplication]؛
- الأسطر الأخرى هي سجلات Spring من المستوى [INFO]. وكما رأينا سابقًا، يمكن التحكم في هذه السجلات من خلال الملف [logback.xml] الموجود في مسار الفئات (Classpath) للمشروع:
![]() |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- يتم تعيين النوع افتراضيًا للمشفرات
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- التحكم في مستوى السجلات -->
<root level="warn"> <!-- معلومات، تصحيح أخطاء، تحذير -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
إذا استبدلنا في السطر 12 أعلاه المستوى [warn] بمستوى [info]، فسنحصل على النتيجة التالية:
اختفت السجلات. لا تظهر سوى الرسائل ذات المستوى [warn]، ولم تكن هناك أي رسائل بهذا المستوى هنا.
2.5.3. تنفيذ الطبقات المختلفة للبنية
![]() |
سنقوم الآن بتنفيذ الطبقات الثلاث للبنية المذكورة أعلاه:
![]() |
يتم تنفيذ الطبقة [DAO] بواسطة الحزمة [spring.core.dao]. وهي تقدم واجهة [IDao] التالية:
package spring.core.dao;
public interface IDao {
public int doSomethingInDaoLayer(int a, int b);
}
لهذه الواجهة تطبيقان: [Dao1] و [Dao2]:
package spring.core.dao;
public class Dao1 implements IDao {
public int doSomethingInDaoLayer(int a, int b) {
return a+b;
}
}
package spring.core.dao;
public class Dao2 implements IDao {
public int doSomethingInDaoLayer(int a, int b) {
return a-b;
}
}
يتم تنفيذ الطبقة [métier] بواسطة الحزمة [spring.core.metier]. وهي تقدم الواجهة [IMetier] التالية:
package spring.core.metier;
public interface IMetier {
public int doSomethingInMetierLayer(int a, int b);
}
لهذه الواجهة تطبيقان: [Metier1] و [Metier2]:
package spring.core.metier;
import spring.core.dao.IDao;
public class Metier1 implements IMetier {
private IDao dao;
public int doSomethingInMetierLayer(int a, int b) {
a++;
b++;
return dao.doSomethingInDaoLayer(a, b);
}
public void setDao(IDao dao) {
this.dao = dao;
}
}
package spring.core.metier;
import spring.core.dao.IDao;
public class Metier2 implements IMetier {
private IDao dao;
public int doSomethingInMetierLayer(int a, int b) {
a--;
b++;
return dao.doSomethingInDaoLayer(a, b);
}
public void setDao(IDao dao) {
this.dao = dao;
}
}
يتم تنفيذ الطبقة [UI] بواسطة الحزمة [spring.core.ui]. وهي تقدم الواجهة [IUi] التالية:
package spring.core.ui;
public interface IUi {
public int doSomethingInUiLayer(int a, int b);
}
لهذه الواجهة تطبيقان: [Ui1] و [Ui2]:
package spring.core.ui;
import spring.core.metier.IMetier;
public class Ui1 implements IUi {
private IMetier metier;
public int doSomethingInUiLayer(int a, int b) {
a++;
b++;
return metier.doSomethingInMetierLayer(a, b);
}
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
package spring.core.ui;
import spring.core.metier.IMetier;
public class Ui2 implements IUi {
private IMetier metier;
public int doSomethingInUiLayer(int a, int b) {
a--;
b++;
return metier.doSomethingInMetierLayer(a, b);
}
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
2.5.4. إعدادات مشروع Spring
![]() |
فئة التكوين [Config] هي كما يلي:
package spring.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;
@Configuration
public class Config {
// -------------- التنفيذ [Ui1, Metier1, Dao1]
@Bean
public IDao dao1() {
return new Dao1();
}
@Bean
public IMetier metier1(IDao dao1) {
Metier1 metier = new Metier1();
metier.setDao(dao1);
return metier;
}
@Bean
public IUi ui1(IMetier metier1) {
Ui1 ui = new Ui1();
ui.setMetier(metier1);
return ui;
}
// -------------- التنفيذ [Ui2, Metier2, Dao2]
@Bean
public IDao dao2() {
return new Dao2();
}
@Bean
public IMetier metier2(IDao dao2) {
Metier2 metier = new Metier2();
metier.setDao(dao2);
return metier;
}
@Bean
public IUi ui2(IMetier metier2) {
Ui2 ui = new Ui2();
ui.setMetier(metier2);
return ui;
}
}
- الأسطر 20-23: يُعتبر البين المسمى [dao1] (اسم الأسلوب) مثيلًا لفئة [Dao1] (السطر 22) التي تُعتبر تنفيذًا للواجهة [IDao] (السطر 21). وبالتالي، يُنظر إلى البين [dao1] على أنه مثيل لواجهة (المصطلح غير صحيح ولكنه يمكن فهمه) وليس مثيلًا لفئة. وهذه نقطة مهمة يجب فهمها. وستكون جميع البينات الأخرى أيضًا مثيلات لواجهات؛
- الأسطر 25-30: مثيل للواجهة [IMetier] التي تنفذها الفئة [Metier1]؛
- الأسطر 32-37: مثيل للواجهة [IUi] التي تنفذها الفئة [Ui1]؛
- الأسطر 20-37: تُنفذ الطبقات [UI, Metier, DAO] باستخدام مثيلات [Ui1, Metier1, Dao1]؛
- الأسطر 40-57: تُنفذ الطبقات [UI, Metier, DAO] باستخدام مثيلات [Ui2, Metier2, Dao2]؛
2.5.5. اختبار وحدة [JUnitTest]
![]() |
يتم وضع الفئة [JUnitTest] في الفرع [src / test / java] من مشروع Maven. لا يتم وضع عناصر هذا الفرع في الأرشيف النهائي للمشروع. وفيما يلي شفرته:
package spring.core.tests;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;
@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
- السطر 16: التعليق التوضيحي [@SpringApplicationConfiguration] هو تعليق توضيحي لمشروع Spring Boot Test (السطر 8). وقد تم إدراجه من خلال التبعية التالية لملف [pom.xml]:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
يحتوي هذا التعليق التوضيحي على قائمة بفئات التكوين التي يجب استخدامها لإنشاء سياق Spring اللازم للاختبار. هنا نستخدم فئة التكوين [Config] التي سبق عرضها؛
- السطر 17: التعليق التوضيحي [@RunWith] هو تعليق توضيحي لـ JUnit (السطر 5). معلمتها هي الفئة المسؤولة عن إجراء الاختبارات بدلاً من الفئة الافتراضية لإطار عمل Junit. هذه الفئة هي فئة Spring (السطر 9). وستستخدم علامات Spring الموجودة في فئة الاختبار؛
والفئة الكاملة هي كما يلي
...
@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
// طبقة [UI]
@Autowired
@Qualifier("ui1")
private IUi ui1;
@Autowired
@Qualifier("ui2")
private IUi ui2;
// طبقة [métier]
@Autowired
@Qualifier("metier1")
private IMetier metier1;
@Autowired
@Qualifier("metier2")
private IMetier metier2;
// طبقة [dao]
@Autowired
@Qualifier("dao1")
private IDao dao1;
@Autowired
@Qualifier("dao2")
private IDao dao2;
@Test
public void testDao() {
Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
}
@Test
public void testMetier() {
Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
}
@Test
public void testUI() {
Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
}
}
- الأسطر 8-10: يتم حقن (السطر 8) البين المسمى (السطر 9) [ui1]. ويُلاحظ في السطر 10 أنه يتم حقن مثيل واجهة وليس مثيل فئة؛
- الأسطر 21-32: يتم حقن البينات الأخرى المُعرَّفة في الفئة [Config] بنفس الطريقة؛
- السطر 34: تشير التعليقة التوضيحية [@Test] إلى طريقة يجب تنفيذها أثناء الاختبارات. التعليقات التوضيحية الأخرى الممكنة هي التالية:
- [@BeforeClass]: طريقة يجب تنفيذها قبل بدء الاختبارات؛
- [@AfterClass]: طريقة يجب تنفيذها بعد الانتهاء من جميع الاختبارات؛
- [@Before]: طريقة يجب تنفيذها قبل كل اختبار؛
- [@After]: الطريقة التي يجب تنفيذها بعد كل اختبار؛
- السطر 36: نتحقق من أن استدعاء [dao1.doSomethingInDaoLayer(10, 20)] يُرجع القيمة 30 بالفعل. المعلمة الأولى هي، وفقًا للأعراف، القيمة المتوقعة، والثانية هي القيمة الفعلية؛
- السطر 36: يختبر مثيل [dao1] للواجهة [IDao]؛
- السطر 37: يختبر المثيل [dao2] للواجهة [IDao]؛
- السطر 42: يختبر المثيل [metier1] للواجهة [IMetier]؛
- السطر 43: يختبر المثيل [metier2] للواجهة [IMetier]؛
- السطر 48: يختبر المثيل [ui1] للواجهة [IUi]؛
- السطر 36: يختبر مثيل [ui2] للواجهة [IUi]؛
التأكيدات التي يمكن استخدامها في طريقة الاختبار هي التالية:
- assertEquals(expression1, expression2): يتحقق من أن قيم التعبيرين متساويتان. يتم قبول أنواع عديدة من التعبيرات (int، String، float، double، boolean، char، short). إذا لم تكن قيم التعبيرين متساويتين، يتم إلقاء استثناء من النوع [AssertionFailedError ]،
- assertEquals(رقم_حقيقي1، رقم_حقيقي2، دلتا): يتحقق من أن الرقمين الحقيقيين متساويان بفارق لا يتجاوز دلتا، c.a.d abs(رقم_حقيقي1-رقم_حقيقي2)<=دلتا. يمكننا على سبيل المثال كتابة assertEquals(رقم حقيقي1، رقم حقيقي2، 1E-6) للتحقق من أن قيمتين متساويتين بفارق لا يتجاوز 10⁻⁶،
- تعد assertEquals(message, expression1, expression2) و assertEquals(message, réel1, réel2, delta) صيغًا بديلة تسمح بتحديد رسالة الخطأ المرتبطة بالاستثناء من النوع [AssertionFailedError] الذي يتم إطلاقه عند فشل الطريقة [assertEquals]،
- assertNotNull(Object) و assertNotNull(message, Object): تتحقق من أن مرجع Object لا يساوي null،
- assertNull(Object) و assertNull(message, Object): يتحقق من أن المرجع Object يساوي null،
- assertSame(Object1، Object2) و assertSame(message، Object1، Object2): يتحقق من أن المرجعين Object1 و Object2 يشيران إلى نفس الكائن،
- assertNotSame(Object1, Object2) و assertNotSame(message, Object1, Object2): يتحقق من أن المرجعين Object1 و Object2 لا يشيران إلى نفس الكائن؛
لإجراء الاختبار، يمكن اتباع الخطوات التالية:
![]() |
ونحصل على النتيجة التالية:
![]() |
هنا نجحت جميع الاختبارات. ما الذي يوضحه هذا المثال؟ إنه يوضح المرونة التي يوفرها إطار عمل Spring في تكوين بنية متعددة الطبقات. يمكننا اختيار استخدام التنفيذ [Ui1, Metier1, Dao1] أو [Ui2, Metier2, Dao2] ببساطة عن طريق التكوين. وهكذا، في الاختبار JUnit السابق، إذا احتفظنا فقط بحقن مكونات [ui1, metier1, dao1]، فإننا نعمل مع البنية الأولى. لتغيير البنية، يكفي تغيير المكونات المحقونة. ويتم ذلك دون تغيير كود الطبقات التي تنفذ الواجهات. يُطلق على هذا النوع من البرمجة اسم «البرمجة عبر الواجهات» لأننا لا نستخدم مثيلات الفئات التي تنفذ الطبقات، بل مثيلات واجهاتها.
2.6. الخلاصة
- يدير Spring الكائنات التي هي كائنات فردية (نسخة واحدة فقط). كما يدير Spring الكائنات التي يتم إنشاء مثيل لها في كل مرة يُطلب فيها مثيل منها من Spring. وسيتم عرض هذه الحالة أيضًا في هذا المستند؛
- ويمكن إعلان هذه الكائنات بطرق متنوعة يمكن مزجها:
- في ملف XML،
- في فئة Java مُعلَّمة بـ [@Configuration]،
- مع أي فئة Java مُعلَّمة بـ [@Component, @Service, ...]؛
- يمكن حقن كائن Spring في كائن Spring آخر باستخدام التعليق التوضيحي [@Autowired]. ويُطلق على ذلك اسم حقن التبعيات (DI: Dependency Injection)؛
- يُظهر Spring فائدة كبيرة في تكوين البنى الطبقية مع الاستخدام المشترك لنموذج البرمجة عبر الواجهات؛




















































