@Autowired و @Qualifier و @Primary
@Autowired و @Qualifier و @Primary
تبدأ قوة حقن التبعيات في Spring حين تتوقف عن توصيل الـ beans يدويًا وتترك للإطار مهمة حلّها تلقائيًا. التعليقات التوضيحية الثلاثة التي يتناولها هذا الدرس — @Autowired و@Qualifier و@Primary — هي الأدوات الأساسية التي يوفّرها Spring لهذا الغرض. فهمُ كيفية استخدامها ومتى ولماذا سيوفّر عليك أكثر الأخطاء شيوعًا في حقن التبعيات ويُبقي إعداداتك نظيفة على نطاق واسع.
كيف يحلّ Spring التبعية
حين يصادف Spring نقطة حقن تبعية — معامل منشئ، أو ضبّاط (setter)، أو حقل — يتّبع بحثًا من خطوتين:
- المطابقة بالنوع: إيجاد جميع الـ beans في السياق التطبيقي التي يكون نوعها متوافقًا مع النوع المطلوب.
- المطابقة بالاسم (فاصل التعادل): إذا بقي مرشّح واحد بالضبط، يُستخدم. أما إذا بقي عدة مرشّحين، تُحاول المطابقة باسم متغيّر نقطة الحقن مع اسم bean. وإذا ظلّ الغموض قائمًا، يُطلق الاستثناء
NoUniqueBeanDefinitionException.
تمنحك التعليقات التوضيحية في هذا الدرس تحكّمًا صريحًا في كل خطوة من خطوات تلك العملية.
@Autowired — تحديد نقطة الحقن
يُخبر @Autowired (من حزمة org.springframework.beans.factory.annotation) Spring بأن: احشُ هذه التبعية من السياق. يمكن وضعه على منشئ، أو دالة ضبط، أو مباشرةً على حقل.
@Service بمنشئ واحد بلا @Autowired على الإطلاق.
السمة required = false مهمة: فهي تُخبر Spring بعدم إيقاف التطبيق عند بدء التشغيل إذا لم يُعثر على bean مطابق. استخدمها للتكاملات الاختيارية حقًا (التحليلات، مزوّدو أعلام الميزات) — لا لإخفاء تبعية إلزامية مفقودة.
مشكلة الغموض
فور وجود أكثر من bean من النوع ذاته في السياق، لا يستطيع Spring تحديد أيّهما يحقن ويُطلق الاستثناء NoUniqueBeanDefinitionException. وهذا شائع جدًا مع الواجهات:
الآن أصبح حقن PaymentGateway في أي مكان غامضًا. يجد Spring مرشّحَين ولا قاعدة للاختيار بينهما. لديك أداتان لحل هذا: @Primary و@Qualifier.
@Primary — تحديد القيمة الافتراضية
تُعلّم @Primary bean معيّنًا بوصفه المرشّح المُفضَّل عند الطلب بالنوع دون تحديد qualifier. فكّر فيه كتعيين الافتراضي على مستوى النظام.
الآن أي نقطة حقن تطلب PaymentGateway — دون تأهيل إضافي — تستقبل StripeGateway:
@Primary يُعطي وضوحًا زائفًا — بدلًا من ذلك استخدم @Qualifier في كل مكان.
@Qualifier — الاختيار الدقيق لكل نقطة
تُرفق @Qualifier تسمية نصية بـ bean وتشترط تلك التسمية بالضبط عند نقطة الحقن، متجاوزةً الغموض الناتج عن تطابق النوع كليًا. التسمية تأخذ افتراضيًا اسم الفئة بحرف أول صغير (الاسم الافتراضي للـ bean)، أو يمكنك تعيينها صراحةً.
عند نقطة الحقن تكرّر الـ qualifier:
تعليقات Qualifier المخصصة — النمط الاحترافي
تعليق qualifier مخصّص يُلغي مشكلة القيم النصية المكتوبة صراحةً كليًا. تعرّف تعليقًا توضيحيًا مزوّدًا بـ meta-annotation مرةً واحدة وتستخدمه كـ qualifier آمن من حيث النوع في كل مكان:
الآن يُطبّق المُترجم العقدَ. إذا أُعيدت تسمية @PayPalPayment أو حُذفت، يفشل كل موقع استخدام في الترجمة فورًا.
دمج @Primary و@Qualifier معًا
يعمل هذان التعليقان التوضيحيان معًا بصورة متوقّعة: @Qualifier يتغلّب دائمًا على @Primary. إذا كان bean مُعلَّمًا بـ @Primary لكن نقطة الحقن تحمل @Qualifier، يتجاهل Spring تعيين primary ويستخدم الـ qualifier لاختيار الـ bean.
- لا qualifier عند نقطة الحقن ← استخدم الـ bean ذا
@Primary(إن وُجد)، وإلا أخفق بغموض. - Qualifier موجود ← طابق بالـ qualifier وتجاهل
@Primaryكليًا. - لا هذا ولا ذاك ← يلجأ Spring إلى مطابقة اسم المتغيّر باسم الـ bean (هشّ؛ تجنّب الاعتماد عليه).
حقن كل المرشّحين — المجموعات والـ Optional
في بعض الأحيان تريد كل التنفيذات — مثلًا، لإرسال إشعار لجميع القنوات المسجّلة. يتعامل Spring مع هذا تلقائيًا حين تحقن List أو Map:
حقن Map<String, NotificationSender> يمنحك اسم الـ bean كمفتاح — مفيد حين تحتاج إلى اختيار تنفيذ في وقت التشغيل بالاسم.
الخلاصة
استخدم @Autowired (أو ببساطة فئة بمنشئ واحد) للإعلان عن نقاط الحقن. حين يوجد مرشّحون متعددون من النوع ذاته، حلّ الغموض بـ @Primary لـ bean افتراضي واحد، أو بـ @Qualifier للدقة في كل موقع. فضّل تعليقات qualifier المخصصة على القيم النصية الحرفية في أي قاعدة كود تتوقع صيانتها. وتذكّر: @Qualifier يتغلّب دائمًا على @Primary.