5. [Cours]: مقدمة إلى إطار عمل Spring
الكلمات المفتاحية: بنية متعددة الطبقات، Spring، حقن التبعيات.
ظهر Spring في عام 2004 في البداية كحاوية كائنات. ومنذ ذلك الحين، تطور إلى عدة فروع: Spring MVC، Spring Data، Spring Batch، ... [http://spring.io]. نقدم في هذا الفصل حاوية الكائنات فقط. فيما يلي بعض النقاط المرجعية:
- يحتوي التطبيق على فئات متعددة، وتشترك بعضها في كائنات يجب أن تكون فريدة (singletons). يقوم Spring بإنشاء وإدارة هذه الكائنات الفريدة؛
- يضع Spring هذه العناصر الفردية في بنية تسمى السياق؛
- تتمكن الفئات من الوصول إلى العناصر الفردية للتطبيق عن طريق طلبها من Spring باستخدام اسمها أو نوعها أو كليهما؛
- يقوم Spring بإنشاء الكائنات الفردية وإدارة تبعياتها المحتملة: فقد يحتوي الكائن الفردي على مراجع إلى كائن فردي آخر أو أكثر. وعندما يقوم Spring بإنشاء كائن فردي، فإنه يقوم أيضًا بإنشاء تبعياته؛
- عندما يبدأ تشغيل تطبيق يعتمد على Spring، يمكنه أن يطلب من Spring إنشاء جميع العناصر الفردية للتطبيق. وستكون هذه العناصر متاحة بعد ذلك في سياق Spring؛
- يسهل Spring استخدام البنى الطبقية والبرمجة عبر الواجهات. في الحالات البسيطة، يتم تنفيذ كل طبقة بواسطة عنصر فريد وتنفذ واجهة. إذا كان التطبيق يعمل مع واجهات الطبقات وليس مع فئات التنفيذ الخاصة بها، فإننا نحصل على بنية قابلة للتطوير تسمح بتغيير تنفيذ طبقة ما دون تغيير الطبقات الأخرى بفضل الميزتين التاليتين:
- يحصل التطبيق على مرجع للطبقة عبر اسمها. يزود Spring التطبيق بمرجع للفئة التي تنفذ الطبقة؛
- يستخدم التطبيق هذه الإشارة كإشارة لواجهة الطبقة وليس كإشارة لفئة؛
يمكن إعلان العناصر الفردية بثلاث طرق يمكن مزجها:
- داخل ملف XML،
- في فئة تكوين خاصة؛
- مع أي فئة باستخدام التعليقات التوضيحية؛
نقدم فيما يلي ثلاثة أمثلة للتكوين:
- [exemple-01]: تكوين مركزي في ملف واحد XML؛
- [exemple-02]: تكوين مركزي في فئة Java واحدة؛
- [exemple-03]: تكوين موزع على عدة فئات Java؛
المثال الأخير [exemple-04] يركز على تكوين Spring لهيكلية ذات طبقات. هذا هو المثال الأهم. وسيتم استخدامه باستمرار لتكوين هياكل المستند.
تضع هذه الأمثلة الأربعة الأسس لما يلي:
- تكوين Spring وحقن التبعيات؛
- استخدام Maven لإدارة تبعيات المشروع؛
- استخدام JUnit لاختبار المشاريع؛
5.1. Support
![]() | ![]() |
يحتوي المجلد [support / chap-05] على مشاريع Eclipse الخاصة بهذا الفصل.
5.2. Exemple-01
5.2.1. مشروع Eclipse
![]() |
5.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;
}
}
ملاحظة: يمكن إنشاء متغيرات القراءة والكتابة تلقائيًا بالطريقة التالية [1-2]:
![]() |
5.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);
}
}
ملاحظة: لا تحتوي هذه الفئة على منشئ صريح. في هذه الحالة، يوجد دائمًا بشكل افتراضي منشئ بدون معلمات لا يقوم بأي شيء. عند إنشاء منشئات، لا يعود هذا المنشئ الافتراضي موجودًا ضمنيًا. لذا يجب تعريفه صراحةً:
5.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]، يتم استخدام مُعيّن للفئة. على سبيل المثال، بالنسبة للسطر 13، سيتم تنفيذ الطريقة [setNom("martin")]. لذا يجب أن تكون الطريقة [setNom] موجودة. هذه نقطة مهمة يجب تذكرها؛
- السطور 18-21: تسمح العلامة <util:list> بتعريف عنصر فريد (singleton) وهو عبارة عن قائمة؛
- السطر 19: يشير إلى العنصر الفردي [personne_01] المحدد في السطر 6. ما لدينا هنا هو ما يُسمى بحقن التبعيات. يمكن استخدام سمتين لتهيئة حقل عنصر فردي:
- [value]: لتعيين قيمة أولية للحقل (سلسلة، رقم، تاريخ، ...)،
- [ref]: لتعيين مرجع كائن Spring للحقل؛
ملاحظة: يمكن إنشاء ملف تكوين Spring بالطريقة التالية [1-4]:
![]() |
5.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);
// البيانات المسترجعة هي عناصر فردية
// يمكن طلبها عدة مرات، ويتم استرداد نفس الحبة دائمًا
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- السطر 14: ينشئ سياق Spring. يتم بعد ذلك إنشاء مثيلات لجميع العناصر الفردية المحددة في الملف [config-01.xml]؛
- السطر 16: يطلب مرجعًا عن العنصر الفردي المُعرَّف بـ [personne_01] من النوع [Personne]. هذا المعامل الثاني اختياري، ولكن في هذه الحالة يتم استلام مرجع لنوع [Object]، وهو مرجع يجب تحويله إلى النوع [Personne]؛
- السطر 19: لا نستخدم اسم الفول بل نوعه فقط لأنه لا يوجد سوى عنصر فردي من النوع [Appartement]؛
- السطر 18: تم استخدام كل من المعرف ونوع العنصر الفردي المطلوب. المعرف زائد عن الحاجة نظرًا لوجود عنصر فردي واحد فقط من النوع [new ArrayList<Personne>().getClass()]؛
- السطران 32-33: يوضحان أنه إذا طلبنا نفس العنصر الفردي عدة مرات، فسنحصل دائمًا على نفس المرجع، مما يدل على أننا نتعامل بالفعل مع عنصر فردي. من المهم فهم هذه النقطة؛
ملاحظة: يمكن إنشاء فئة قابلة للتنفيذ بالطريقة التالية [1-6]:
![]() |
![]() |
- إن تحديد [6] هو ما سيجعل الفئة التي تم إنشاؤها تحتوي على طريقة ثابتة [main] التي ستجعلها قابلة للتنفيذ؛
5.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] لمشروع ما هو مجموعة المجلدات التي تستكشفها JVM (Java Virtual Machine) التي تنفذ المشروع، للعثور على فئة يشير إليها المشروع. بالنسبة لمشروع Eclipse، يتكون [ClassPath] من العناصر التالية:
- مجلد [bin] الخاص بالمشروع؛
- عناصر [Build Path] الخاصة بالمشروع؛
المجلد [bin] هو المجلد الناتج عن تجميع المجلد [src]. لذلك، فإن كل ما يتم وضعه في المجلد [src] يصبح تلقائيًا جزءًا من [ClassPath] (حتى لو لم يكن ملف .java). لذلك، في المشروع السابق، سيكون ملف تكوين Spring [config-01.xml] الموجود في المجلد [src] جزءًا من [Classpath] للمشروع عند التنفيذ.
5.2.7. النتائج
5.3. Exemple-02
5.3.1. مشروع Eclipse
![]() |
5.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.
5.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);
// البيانات التي تم استردادها هي عناصر فردية
// يمكن طلبها عدة مرات، يتم استرداد نفس البين دائمًا
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- السطر 13 يؤدي إلى إنشاء مثيلات لجميع الفاصوليا المحددة في الفئة [Config]؛
- لا يتغير باقي الكود؛
5.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، مما قد يؤدي أحيانًا إلى مشاكل غير متوقعة.
5.3.5. إنشاء أداة Maven للمشروع
نسمي عنصر مشروع 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 محلي آخر من استخدام هذا الأرشيف.
5.4. Exemple-03
5.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]: الموارد التي يجب أن تكون في مسار فئة المشروع دون أن تكون مصادر Java. سيتم نسخها كما هي في مجلد [target/classes] الخاص بالمشروع؛
- [src / test / java]: أكواد مصدر اختبارات المشروع. ستنتقل المنتجات المجمعة من هذه المصادر إلى مجلد [target/test-classes] الخاص بالمشروع. لا يتم تضمين هذه العناصر في أرشيف Maven الخاص بالمشروع؛
- [src / test / resources]: الموارد التي يجب أن تكون موجودة في مسار فئة المشروع (Classpath) للاختبارات دون أن تكون مصادر Java. سيتم نسخها كما هي في مجلد [target/test-classes] الخاص بالمشروع؛
نكمل المشروع على النحو التالي:
![]() |
5.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 الخاصة بالمشروع؛
5.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: bean [mySurface]؛
5.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 بأن الفئة هي عنصر فردي يجب على إطار العمل إنشاء مثيل له وإدارته. وذلك لأننا في الفئة [Config]، كتبنا [@ComponentScan({ "istia.st.spring.core.entities" })] الذي سيتم العثور عليه؛
- السطر 11: يطلب من Spring حقن مرجع أحد الكائنات الفردية في الحقل. يمكن تعريف هذا المرجع بطريقتين:
- من خلال معرّفه (السطران 12 و16)،
- من خلال نوعه إذا كان هناك كائن فردي واحد فقط من هذا النوع؛
5.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]:
لم يتبق سوى سطر واحد من السجلات.
5.4.6. إنشاء أرشيف المشروع مع تبعياته
يمكن أيضًا استخدام الأرشيف الذي تم إنشاؤه في المشروع السابق في مشروع Eclipse غير Maven. تستخدم بعض المشاريع العديد من المكتبات وقد يكون من الصعب عدم نسيان أي منها. وهنا يأتي دور Maven حيث يكفي تسمية التبعية ذات المستوى الأعلى حتى يتم إضافة التبعيات الأخرى ذات المستوى الأدنى تلقائيًا إلى مسار فئة المشروع. عندما يتعين على مشروع 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]، فئات المشروع نفسه؛
5.5. Exemple-04
5.5.1. الهدف
يستند هذا المثال إلى أحد الأمثلة الواردة في المستند [Introduction à Spring IoC] الذي يوضح مساهمة Spring في تكوين البنى متعددة الطبقات. في المستند الأصلي، يتم التعامل مع المثال باستخدام تكوين Spring تم إنشاؤه بواسطة ملف XML. هنا نعالج المثال باستخدام تكوين عبر فئات Java والتعليقات التوضيحية.
نريد هنا تكوين مشروع Spring للبنية التالية:
![]() |
تقدم كل طبقة واجهة تم تنفيذها باستخدام فئتين. نريد أن نوضح أنه بفضل Spring، يمكننا تغيير تنفيذ طبقة ما دون أي تأثير على كود الطبقات الأخرى.
5.5.2. مشروع Eclipse
5.5.2.1. Génération
نقوم بإنشاء نوع جديد من المشاريع:
![]() |
![]() |
- في [4]، أدخل اسم مشروع Eclipse؛
- في [5]، اختر مشروع Maven؛
- في [6]، اختر إصدار Java >=1.7؛
- في [7]، اختر إصدار Spring Boot المقترح؛
- المعلومات في [8-11] هي معلومات Maven؛
- في [12]، يمكن اختيار واحد أو أكثر من التبعيات المقترحة. سيؤدي ذلك إلى دمج عدد معين من التبعيات في ملف [pom.xml] الخاص بـ Maven؛
![]() |
- في [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]:
![]() |
سنرى أنها كافية لما نريد القيام به هنا.
5.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] صالحة أيضًا.
![]() |
نحصل على النتيجة التالية:
- الأسطر 14-28: حبات سياق Spring. لا نعرف دورها. نجد الحبة [springCore04Application] في السطر 18 التي تصبح تلقائيًا حبة Spring بفضل تعليقها [@SpringBootApplication]؛
- الأسطر الأخرى هي سجلات Spring من المستوى [INFO]. كما رأينا سابقًا، يمكن التحكم في هذه السجلات من خلال الملف [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="warn"> <!-- معلومات، تصحيح أخطاء، تحذير -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
إذا استبدلنا في السطر 12 أعلاه المستوى [warn] بمستوى [info]، نحصل على النتيجة التالية:
اختفت السجلات. لا تظهر سوى رسائل المستوى [warn]، ولم تكن هناك أي رسائل هنا.
5.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;
}
}
5.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]؛
5.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-6،
- assertEquals(message, expression1, expression2) و assertEquals(message, real1, real2, 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]، فإننا نعمل مع البنية الأولى. لتغيير البنية، يكفي تغيير الحبات المحقونة. ويتم ذلك دون تغيير كود الطبقات التي تنفذ الواجهات. يُطلق على هذا النوع من البرمجة اسم البرمجة عبر الواجهات لأننا لا نستخدم مثيلات الفئات التي تنفذ الطبقات، بل مثيلات واجهاتها.
5.6. Conclusion
- يدير Spring الكائنات التي هي كائنات فردية (نسخة واحدة). يدير Spring أيضًا الكائنات التي يتم إنشاء مثيل لها في كل مرة يتم فيها طلب مثيل من Spring. سيتم عرض هذه الحالة أيضًا في هذا المستند؛
- يمكن إعلان هذه الكائنات بطرق متنوعة يمكن مزجها:
- في ملف XML،
- في فئة Java مزودة بتعليق توضيحي بواسطة [@Configuration]،
- مع أي فئة Java مزودة بعلامة [@Component, @Service, ...]؛
- يمكن حقن كائن Spring في كائن Spring آخر باستخدام التعليق التوضيحي [@Autowired]. ويُطلق على ذلك حقن التبعيات (DI: Dependency Injection)؛
- يُظهر Spring فائدة كبيرة في تكوين بنى متعددة الطبقات مع الاستخدام المشترك لنموذج البرمجة عبر الواجهات؛














































