إطار Spring وحاوية IoC

أساليب الإعداد: Java مقابل التعليقات التوضيحية مقابل XML

18 دقيقة الدرس 5 من 13

أساليب الإعداد: Java مقابل التعليقات التوضيحية مقابل XML

يمنحك Spring ثلاثة أساليب متمايزة لإخبار حاوية IoC بالـ beans التي تنشئها وكيفية ربطها معًا. لكل أسلوب مبرراته التي أوجدته، ولا تزال الأساليب الثلاثة حاضرة في قواعد الكود الإنتاجية اليوم. فهمها — وامتلاك القدرة على اختيار الأنسب منها — هو الفرق بين المطوّر الذي يستخدم Spring ومن يفهمه فعلًا.

نظرة عامة على الأساليب الثلاثة

  • إعداد XML — الأصل (Spring 1.x، نحو 2003). تُعلَن الـ beans وتبعياتها في ملف applicationContext.xml.
  • الإعداد بالتعليقات التوضيحية — أُدرج في Spring 2.5 (2007). تُقيم البيانات الوصفية داخل الفئة نفسها عبر تعليقات توضيحية كـ @Component و@Autowired.
  • الإعداد بلغة Java — رُسِّخ في Spring 3.0 (2009). فئة Java عادية مُزيَّنة بـ @Configuration تعمل كمصنع للـ beans.

تجمع المشاريع الحديثة في معظمها بين الإعداد بالتعليقات التوضيحية والإعداد ببصمة Java، غير أنك ستصادف الأساليب الثلاثة عند قراءة الكود القديم أو مكتبات المصدر المفتوح.

الأسلوب الأول: إعداد XML

قبل وجود التعليقات التوضيحية، كان المطوّرون يصفون كل bean وتبعياتها في ملف XML. كانت Spring تقرأ الملف وتُنشئ الـ beans وتربطها معًا.

<!-- src/main/resources/applicationContext.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userRepository" class="com.example.UserRepository"/> <bean id="userService" class="com.example.UserService"> <constructor-arg ref="userRepository"/> </bean> </beans>

تحميله في وقت التشغيل:

import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService svc = ctx.getBean(UserService.class);
XML آمن النوع في وقت التشغيل لكن ليس في وقت الترجمة. خطأ مطبعي في اسم فئة أو تبعية مفقودة لن يُكتشف إلا عند تحميل السياق، لا عند ترجمة الكود. كما يستلزم إعادة تسمية الفئات بحثًا واستبدالًا دقيقًا عبر ملفات XML، إذ لا تستطيع بيئة التطوير دائمًا تتبع هذه المراجع تلقائيًا.

متى لا تزال ترى XML: التطبيقات المؤسسية القديمة (كود ما قبل 2010)، وخطوط أنابيب Spring Integration/Spring Batch التي لم تُهاجَر بعد، والمكتبات الخارجية التي تشحن ملف إعداد افتراضيًا تستورده.

الأسلوب الثاني: الإعداد بالتعليقات التوضيحية

ينقل هذا الأسلوب بيانات الربط الوصفية إلى داخل الفئات نفسها. تُعلّم الفئة كـ bean بتعليق توضيحي نمطي (@Component أو @Service أو @Repository أو @Controller) وتطلب من Spring اكتشافها بالمسح الآلي.

package com.example; import org.springframework.stereotype.Repository; @Repository public class UserRepository { public User findById(long id) { /* استدعاء JDBC أو ORM */ return null; } }
package com.example; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { private final UserRepository repo; @Autowired // تحقن Spring هنا UserRepository public UserService(UserRepository repo) { this.repo = repo; } public User getUser(long id) { return repo.findById(id); } }

لتفعيل المسح وجِّه Spring نحو الحزمة المناسبة:

import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.example") // مسح هذه الحزمة والحزم الفرعية public class AppConfig {} // التهيئة AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); UserService svc = ctx.getBean(UserService.class); ctx.close();
فضّل حقن المُنشئ على حقن الحقل. يجعل حقن المُنشئ التبعيات صريحةً، ويتيح الحقول final (اللاتغيّرية)، ويجعل الفئة قابلة للاختبار بسهولة دون حاوية Spring — يكفي new UserService(mockRepo). أما الحقن عبر الحقل مباشرةً بـ @Autowired فيخفي التبعيات ويستلزم الانعكاس للاختبار.

التعليقات التوضيحية النمطية تحمل معنىً يتجاوز الاكتشاف:

  • @Repository — تُعلّم طبقة الوصول للبيانات (DAO)؛ تترجم Spring استثناءات الاستمرارية إلى هيكلية DataAccessException الموحّدة.
  • @Service — تُعلّم منطق الأعمال؛ لا سلوك إضافي اليوم لكنها تُعبّر عن النية وهي الهدف الاصطلاحي لوكلاء المعاملات.
  • @Controller / @RestController — تُعلّم متحكمات MVC؛ يربط Spring MVC توابع معالجة الطلبات على هذه الـ beans.
  • @Component — التعليق العام الشامل لكل ما لا يندرج ضمن الثلاثة أعلاه.

الأسلوب الثالث: الإعداد ببصمة Java

يستبدل الإعداد ببصمة Java ملف XML بفئة Java. تُزيّن الفئة بـ @Configuration وتُعلن كل bean كتابع مُزيَّن بـ @Bean. تستدعي Spring هذه التوابع وتدير الكائنات المُعادة كـ beans.

package com.example; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public UserRepository userRepository() { return new UserRepository(); } @Bean public UserService userService() { // تعترض Spring هذا الاستدعاء؛ لا تُنشئ UserRepository ثانية. // تُعيد الـ bean المُفرد الموجود بالفعل في الحاوية. return new UserService(userRepository()); } }
import org.springframework.context.annotation.AnnotationConfigApplicationContext; AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); UserService svc = ctx.getBean(UserService.class); ctx.close();
كيف تعترض Spring استدعاءات @Bean: تُصنع فئات @Configuration من فئات فرعية بواسطة CGLIB عند بدء التشغيل. تُجاوز الفئة المُنشأة كل تابع @Bean للتحقق من الحاوية أولًا؛ إن كانت الـ bean موجودةً تُعيدها بدلًا من استدعاء كود المصنع مجددًا. لهذا يمنحك استدعاء userRepository() داخل userService() المُفرد لا نسخة جديدة. لذلك لا يجوز أن تكون الفئات المُزيَّنة بـ @Configuration من نوع final.

يمنحك إعداد Java دعم IDE الكامل: عند إعادة تسمية فئة يُبلّغ المترجم فورًا عن المراجع المكسورة. يمكنك أيضًا استخدام شروط وحلقات أو أي منطق Java داخل تابع @Bean — وهو أمر مستحيل في XML.

@Bean public DataSource dataSource() { // منطق مدفوع بالبيئة أو الملفات الشخصية (profiles) String url = System.getenv("DB_URL"); if (url == null) { // الرجوع إلى H2 في الذاكرة للتطوير المحلي return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .build(); } HikariConfig cfg = new HikariConfig(); cfg.setJdbcUrl(url); cfg.setUsername(System.getenv("DB_USER")); cfg.setPassword(System.getenv("DB_PASS")); return new HikariDataSource(cfg); }

دمج الأساليب — الواقع العملي

الأساليب قابلة للتركيب. يمكن لفئة @Configuration استيراد ملف XML، ويمكن لملف XML تفعيل مسح التعليقات التوضيحية. إعداد احترافي شائع:

@Configuration @ComponentScan("com.example") // يلتقط فئات @Service / @Repository @ImportResource("classpath:legacy.xml") // يدمج إعداد XML قديمًا public class AppConfig { @Bean // @Bean صريح للبنية التحتية public DataSource dataSource() { /* ... */ return null; } }
تجنب الإعلان عن نفس الـ bean في مكانَين. إذا اكتشفت الفئةَ @ComponentScan لأنها تحمل @Service ثم أضفت تابع @Bean لها في فئة @Configuration، نشأ تعارض. Spring 6 أكثر صرامةً في هذا الشأن وقد يُطلق استثناءً بدلًا من اختيار أحدهما بصمت.

أي أسلوب تختار اليوم

  • المشاريع الجديدة: التعليقات التوضيحية النمطية (@Service، @Repository) لفئات المجال الخاصة بك، مع @Configuration + @Bean لـ beans البنية التحتية (مصادر البيانات، عملاء HTTP، محوّلات الطرف الثالث). هذا المزيج ما تستخدمه Spring Boot تحت الغطاء.
  • XML: تجنبه في الكود الجديد. اقرأه وصنه في الأنظمة القديمة، وانتهز الفرصة لترحيله إلى إعداد Java.
  • إعداد Java الخالص (بلا مسح): مفيد حين تريد تحكمًا صريحًا وقابلًا للتدقيق في كل bean — شائع في كود المكتبات والفرق التي تتحاشى الاكتشاف السحري.

الخلاصة

يقدّم Spring إعداد XML والتعليقات التوضيحية وإعداد Java — وكلها مدعومة في آنٍ واحد. XML مفصَّل لكن صريح. التعليقات التوضيحية موجزة لكنها تشتّت البيانات الوصفية عبر الفئات. يمنحك إعداد Java سلامة النوع المترجمة وتعبيرية الكود الحقيقي. تستند تطبيقات Spring الاحترافية اليوم إلى التعليقات التوضيحية النمطية لكود المجال وإعداد Java للبنية التحتية، تاركةً الإعداد التلقائي لـ Spring Boot يتولى الباقي. فهم الأساليب الثلاثة يعني قدرتك على قراءة أي قاعدة كود Spring واتخاذ قرارات مستنيرة فيما تكتبه لاحقًا.