حقن الضبط وحقن الحقل
حقن الضبط وحقن الحقل
يدعم Spring ثلاثة أساليب لحقن التبعيات: حقن المُنشئ، وحقن الضبط (Setter)، وحقن الحقل (Field). أرسى الدرس السابق حقنَ المُنشئ باعتباره الخيار المُوصى به والافتراضي. يُغطّي هذا الدرس الأسلوبين الآخرين — ليس لأنّك تستخدمهما بحرية، بل لأنّك ستصادفهما في قواعد الكود الفعلية وتحتاج إلى فهم ما يجعل حقن المُنشئ الخيار الأكثر أمانًا. إنّ إدراك المقايضات هو ما يُتيح لك اتخاذ قرارات مدروسة بدلًا من اتباع أعراف بلا فهم.
حقن الضبط (Setter Injection)
في حقن الضبط، تُعلّق على دالة ضبط عامة (أو على مستوى الحزمة) باستخدام @Autowired. يُنشئ Spring الـ bean أولًا ثم يستدعي كل دالة ضبط مُعلَّقة لحقن التبعية.
الفارق الجوهري عن حقن المُنشئ هو التوقيت: حين تُستدعى generate()، قد يظل notificationSender من الناحية النظرية فارغًا إن لم تكتمل عملية التوصيل في Spring بعد — مثلًا داخل جسم المُنشئ نفسه، أو خلال استدعاءات دورة الحياة المبكرة التي تُطلَق قبل اكتمال استدعاء جميع دوال الضبط.
required = false على @Autowired إلى هذه النية. إن كانت التبعية إلزامية حقًا، فإنّ حقن المُنشئ يُطبّق هذا الشرط عند بدء التشغيل؛ بينما لا يفعل ذلك حقن الضبط.
حقن الحقل (Field Injection)
يُعلّق حقن الحقل مباشرةً على الحقل باستخدام @Autowired. يستخدم Spring الانعكاس (reflection) لتعيين القيمة بعد الإنشاء، متجاوزًا أي مُعدِّل وصول.
حقن الحقل هو الأسلوب الأكثر إيجازًا وكان شائعًا في مشاريع Spring المبكرة لأنه يتطلب أقل قدر من الكود المتكرر. أصبح IntelliJ IDEA وتوثيق Spring الرسمي يُحذّران منه الآن: "Field injection is not recommended."
لماذا يُفضَّل حقن المُنشئ — الحجة الكاملة
التفضيل لحقن المُنشئ ليس مسألة ذوق؛ بل يرتبط مباشرةً بمشاكل هندسية ملموسة:
-
الثبات وحقول final. يمكن الإعلان عن حقل محقون عبر المُنشئ بوصفه
final. هذا يضمن تعيينه ويمنع استبداله في وقت التشغيل — مما يقضي على فئة كاملة من الأخطاء التي ينتج عنها مُتعاون مُعاد تعيينه عن طريق الخطأ أو لم يُعيَّن قط. -
ضمان الخلو من Null عند بدء التشغيل. إن كانت الـ bean المطلوبة مفقودة، يرمي Spring استثناء
NoSuchBeanDefinitionExceptionأثناء تهيئة سياق التطبيق، قبل تقديم أي طلب. أما مع حقن الحقل أو الضبط فتظهر التبعية المفقودة فقط كـNullPointerExceptionداخل طلب جارٍ، وغالبًا في بيئة الإنتاج. -
قابلية الاختبار بدون سياق Spring. مع حقن المُنشئ تستطيع إنشاء الفئة واختبارها بجافا عادية:
// لا سياق Spring، لا سحر Mockito — Java خالصة OrderService service = new OrderService( new FakePaymentGateway(), new InMemoryInventoryRepository() );مع حقن الحقل، الحقول خاصة وغير قابلة للوصول بدون Spring أو مشغّل اختبار قائم على الانعكاس. يجب استخدام
@ExtendWith(SpringExtension.class)أوReflectionTestUtils.setField()، وكلاهما يُضيف تعقيدًا ويُبطئ حزمة الاختبارات. -
اكتشاف التبعيات الدائرية. سيرمي Spring استثناء
BeanCurrentlyInCreationExceptionعند بدء التشغيل إن وُجدت تبعية مُنشئ دائرية (A تحتاج B، وB تحتاج A). هذا أمر إيجابي — يُجبرك على إعادة التصميم. أما حقن الحقل أو الضبط فيسمح بصمت للرسوم البيانية الدائرية بالوجود وقد يُخفي مشاكل معمارية لأشهر. -
انتهاك مبدأ المسؤولية الواحدة يصبح واضحًا. حين تحتاج فئة إلى ثماني تبعيات محقونة، ثمانية معاملات مُنشئ مؤلمة للقراءة والكتابة. هذا الألم إشارة لتقسيم الفئة. ثمانية حقول
@Autowiredبالمقابل يسهل تجاهلها وتتراكم في صمت.
@Autowired في حقول خاصة لا يمكن استخدامها خارج Spring على الإطلاق — لا في اختبارات الوحدة، ولا في سكريبت دُفعي، ولا في مكتبة تستهلكها مشاريع أخرى. لقد اقترنت الفئة بشكل دائم بحاوية IoC. أما حقن المُنشئ فيُبقي الفئة كائن Java عادي؛ وSpring مجرد طريقة واحدة لتوصيله.
مزج الأساليب في قاعدة كود حقيقية
قاعدة عملية تُطبّقها الفِرق الاحترافية:
- التبعيات الإلزامية: دائمًا حقن المُنشئ، الحقول مُعلَنة بـ
final. - التبعيات الاختيارية (يمكن أن تغيب والفئة لا تزال تعمل): حقن الضبط مع
@Autowired(required = false). - حقن الحقل: مقتصر على فئات الاختبار قصيرة العمر (مثل اختبارات التكامل عبر
@SpringBootTestحيث تريد فقط سحب bean من السياق دون كتابة مُنشئ). لا تستخدمه أبدًا في beans الإنتاج.
@RequiredArgsConstructor (من Project Lombok) وسيُولَّد مُنشئ لجميع الحقول final وقت الترجمة بدون أي كود متكرر. منذ Spring 4.3، إن كان هناك مُنشئ واحد فقط، فإن @Autowired عليه اختياري — يستنتجه Spring تلقائيًا.
الخلاصة
حقن الضبط مفيد للتبعيات الاختيارية حيث يوجد سلوك افتراضي معقول عند غياب المُتعاون. أما حقن الحقل فهو الأكثر إيجازًا لكن الأكثر إشكالية: يُدمّر قابلية الاختبار بدون Spring، ويُخفي التبعيات المفقودة حتى وقت التشغيل، ويمنع حقول final، ويربط الفئة بالحاوية ربطًا دائمًا. حقن المُنشئ يتجنب كل هذه المشاكل، ويجعل عقد التبعية صريحًا في الواجهة البرمجية، ويجب أن يكون الخيار الافتراضي لكل مُتعاون إلزامي. يفحص الدرس التالي التعليقات التوضيحية — @Autowired و@Qualifier و@Primary — التي تُشغّل الأساليب الثلاثة.