انتقل إلى المحتوى الرئيسي

الفصل 2: حقن التبعيات (DI)

مقدمة​

في الفصل 1، فهمت لماذا يوجد IoC. الآن، ستتعلم كيف يقوم Spring بحقن التبعيات.

ما ستتعلمه​

  1. الأنواع الثلاثة لحقن التبعيات
  2. لماذا يُفضل الحقن عبر المُنشئ
  3. كيفية استخدام @Autowired, @Inject, @Resource
  4. إدارة التبعيات الاختيارية

المدة: 45 دقيقة


الأنواع الثلاثة للحقن​

يوفر Spring ثلاث طرق لحقن التبعيات:

  1. الحقن عبر المُنشئ ✅ (موصى به)
  2. الحقن عبر Setter
  3. الحقن عبر الحقل ⚠️ (غير موصى به)

لنستعرض كل منها بالتفصيل.


1. الحقن عبر المُنشئ ✅​

الصياغة​

@Service
public class OrderService {

// التبعيات مُعلنة كـ 'final'
private final EmailService emailService;
private final PaymentService paymentService;

// الحقن عبر المُنشئ
public OrderService(EmailService emailService,
PaymentService paymentService) {
this.emailService = emailService;
this.paymentService = paymentService;
}
}

كيف يعمل؟​

عند بدء تشغيل التطبيق:

1. يجد Spring OrderService
↓
2. يرى Spring المُنشئ مع 2 من المعلمات
↓
3. ينشئ Spring EmailService
↓
4. ينشئ Spring PaymentService
↓
5. ينشئ Spring OrderService بتمرير التبعيات
↓
6. OrderService جاهز!

مزايا الحقن عبر المُنشئ​

ميزة 1: الثبات​

private final EmailService emailService;
// ^^^^^
// لا يمكن التعديل بعد الإنشاء!

لماذا هذا جيد؟

  • ✅ آمن للخيوط (لا مشاكل في التزامن)
  • ✅ متوقع (لا يمكن أن يتغير)
  • ✅ أقل أخطاء

ميزة 2: التبعيات الإلزامية​

public OrderService(EmailService emailService) {
this.emailService = emailService;
}

إذا لم يجد Spring EmailService:

❌ التطبيق يرفض البدء!
❌ خطأ واضح عند البدء

هذا جيد! تكتشف المشكلة فورًا، وليس في الإنتاج!


ميزة 3: سهل الاختبار​

@Test
public void testOrderService() {
// إنشاء مزيفات
EmailService fakeEmail = new FakeEmailService();
PaymentService fakePayment = new FakePaymentService();

// حقن بسهولة عبر المُنشئ
OrderService service = new OrderService(fakeEmail, fakePayment);

// اختبار
service.processOrder(new Order());
}

لا حاجة لـ Spring للاختبار!


ميزة 4: لا حاجة لـ @Autowired​

منذ Spring 4.3، @Autowired اختياري على المُنشئات:

@Service
public class OrderService {
private final EmailService emailService;

// لا حاجة لـ @Autowired!
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

Spring يكتشف المُنشئ تلقائيًا!


ملخص: الحقن عبر المُنشئ​

✅ تبعيات ثابتة (final)
✅ تبعيات إلزامية
✅ سهل الاختبار
✅ لا حاجة لـ @Autowired
✅ موصى به من قبل Spring

👉 استخدم دائمًا الحقن عبر المُنشئ!

2. الحقن عبر Setter​

الصياغة​

@Service
public class OrderService {

// تبعية غير نهائية
private EmailService emailService;

// الحقن عبر Setter
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}

كيف يعمل؟​

1. ينشئ Spring OrderService (مُنشئ فارغ)
↓
2. ينشئ Spring EmailService
↓
3. يستدعي Spring setEmailService(emailService)
↓
4. OrderService جاهز

متى يُستخدم؟​

حالة الاستخدام: تبعيات اختيارية

@Service
public class OrderService {

private EmailService emailService;
private SmsService smsService; // اختياري

@Autowired(required = false) // ← اختياري!
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}

public void notifyCustomer(String message) {
// البريد الإلكتروني يُرسل دائمًا
emailService.send(message);

// الرسائل القصيرة فقط إذا كانت متاحة
if (smsService != null) {
smsService.send(message);
}
}
}

العيوب​

❌ تبعيات قابلة للتغيير (ليست 'final')
❌ يمكن أن تكون null (خطر NullPointerException)
❌ الكائن يُنشأ في حالة غير مكتملة
❌ أصعب في الاختبار

استخدم فقط للتبعيات الاختيارية!


3. الحقن عبر الحقل ⚠️​

الصياغة​

@Service
public class OrderService {

// الحقن المباشر في الحقل
@Autowired
private EmailService emailService;

@Autowired
private PaymentService paymentService;
}

كيف يعمل؟​

1. ينشئ Spring OrderService
↓
2. يستخدم Spring الانعكاس للوصول إلى الحقول الخاصة
↓
3. يحقن Spring التبعيات مباشرة
↓
4. OrderService جاهز

لماذا هو غير موصى به؟​

المشكلة 1: لا يمكن جعله 'final'​

@Autowired
private EmailService emailService; // ❌ لا يمكن جعله 'final'

المشكلة 2: صعب الاختبار​

@Test
public void testOrderService() {
OrderService service = new OrderService();

// ❌ كيف يتم حقن التبعيات؟
// الحقول خاصة!

// يجب استخدام الانعكاس... معقد!
}

المشكلة 3: تبعيات مخفية​

@Service
public class OrderService {
@Autowired
private Dep1 dep1;
@Autowired
private Dep2 dep2;
@Autowired
private Dep3 dep3;
@Autowired
private Dep4 dep4;
@Autowired
private Dep5 dep5;
// ... 10 تبعيات!
}

المشكلة: لا يمكن رؤية جميع التبعيات بسهولة!

مع المُنشئ:

public OrderService(Dep1 d1, Dep2 d2, Dep3 d3, Dep4 d4, Dep5 d5) {
// ❌ واو! الكثير من التبعيات!
// ➡️ إشارة إلى أن الفئة تقوم بالكثير من الأشياء
}

ملخص: الحقن عبر الحقل​

❌ ليس ثابتًا
❌ صعب الاختبار
❌ يخفي مشاكل التصميم
❌ يستخدم الانعكاس (أبطأ)

👉 تجنب الحقن عبر الحقل!

مقارنة الأنواع الثلاثة​

المعيارالمُنشئSetterالحقل
الثبات (final)✅ نعم❌ لا❌ لا
التبعيات الإلزامية✅ نعم❌ لا⚠️ غير واضح
سهل الاختبار✅ نعم⚠️ متوسط❌ لا
الوضوح✅ واضح✅ واضح⚠️ مخفي
@Autowired ضروري❌ لا✅ نعم✅ نعم
التوصية✅ استخدم⚠️ اختياري❌ تجنب

توضيحات الحقن​

يدعم Spring ثلاث توضيحات للحقن:

1. @Autowired (Spring)​

@Service
public class OrderService {

private final EmailService emailService;

@Autowired // توضيح Spring
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

الأصل: Spring Framework
الاستخدام: الأكثر شيوعًا


2. @Inject (Java Standard)​

import jakarta.inject.Inject;

@Service
public class OrderService {

private final EmailService emailService;

@Inject // توضيح Jakarta (Java standard)
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}

الأصل: Jakarta EE (سابقًا Java EE)
الاستخدام: إذا كنت تريد كودًا محمولًا (غير مرتبط بـ Spring)


3. @Resource (Java Standard)​

import jakarta.annotation.Resource;

@Service
public class OrderService {

@Resource(name = "emailService") // الحقن بالاسم
private EmailService emailService;
}

الأصل: Jakarta EE
الفرق: يحقن بالاسم بدلاً من النوع


أيهما تختار؟​

التوصية: @Autowired (أو لا شيء للمُنشئ)

لماذا؟
✅ معيار Spring
✅ اختياري على المُنشئ
✅ المزيد من الخيارات (required, @Qualifier)
✅ موثق بشكل أفضل

إدارة عدة تنفيذات​

المشكلة​

public interface EmailService {
void send(String to, String message);
}

@Service
public class GmailService implements EmailService { ... }

@Service
public class SendGridService implements EmailService { ... }

// ❌ Spring لا يعرف أيهما يختار!

الحل 1: @Primary​

@Service
@Primary // ← هذا التنفيذ الافتراضي
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("إرسال عبر Gmail إلى " + to);
}
}

@Service
public class SendGridService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("إرسال عبر SendGrid إلى " + to);
}
}

Spring سيستخدم GmailService افتراضيًا!


الحل 2: @Qualifier​

@Service
public class OrderService {

private final EmailService emailService;

public OrderService(@Qualifier("sendGridService") EmailService emailService) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// اسم محدد للـ bean
this.emailService = emailService;
}
}

Spring سيستخدم تحديدًا SendGridService!


الحل 3: التسمية الصريحة​

@Service("gmailEmailService")  // ← اسم صريح
public class GmailService implements EmailService { ... }

@Service("sendGridEmailService") // ← اسم صريح
public class SendGridService implements EmailService { ... }

// الاستخدام
public OrderService(@Qualifier("sendGridEmailService") EmailService service) {
this.emailService = service;
}

التبعيات الاختيارية​

مع @Autowired(required = false)​

@Service
public class NotificationService {

private final EmailService emailService;
private SmsService smsService; // اختياري

// إلزامي
public NotificationService(EmailService emailService) {
this.emailService = emailService;
}

// اختياري
@Autowired(required = false)
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}

public void notify(String message) {
emailService.send(message); // دائمًا

if (smsService != null) {
smsService.send(message); // إذا كان متاحًا
}
}
}

مع Optional<T>​

import java.util.Optional;

@Service
public class NotificationService {

private final EmailService emailService;
private final Optional<SmsService> smsService;

public NotificationService(
EmailService emailService,
Optional<SmsService> smsService // ← غلاف Optional
) {
this.emailService = emailService;
this.smsService = smsService;
}

public void notify(String message) {
emailService.send(message);

smsService.ifPresent(sms -> sms.send(message));
// ^^^^^^^^^
// يُستدعى فقط إذا كان موجودًا
}
}

مثال كامل​

// الواجهة
public interface PaymentProcessor {
void process(Order order);
}

// التنفيذ 1
@Service
@Primary // ← افتراضي
public class StripePaymentProcessor implements PaymentProcessor {

@Override
public void process(Order order) {
System.out.println("الدفع عبر Stripe: " + order.getTotal());
}
}

// التنفيذ 2
@Service("paypalProcessor")
public class PayPalPaymentProcessor implements PaymentProcessor {

@Override
public void process(Order order) {
System.out.println("الدفع عبر PayPal: " + order.getTotal());
}
}

// الاستخدام - الخيار 1: استخدام @Primary
@Service
public class OrderService {

private final PaymentProcessor paymentProcessor;

// Spring يحقن StripePaymentProcessor (@Primary)
public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}

// الاستخدام - الخيار 2: التحديد مع @Qualifier
@Service
public class SpecialOrderService {

private final PaymentProcessor paymentProcessor;

// فرض استخدام PayPal
public SpecialOrderService(
@Qualifier("paypalProcessor") PaymentProcessor paymentProcessor
) {
this.paymentProcessor = paymentProcessor;
}
}

ملخص الفصل​

الأنواع الثلاثة للحقن​

// 1. المُنشئ ✅ موصى به
public MyService(Dependency dep) {
this.dep = dep;
}

// 2. Setter ⚠️ للتبعيات الاختيارية
@Autowired(required = false)
public void setDependency(Dependency dep) {
this.dep = dep;
}

// 3. الحقل ❌ لتجنب
@Autowired
private Dependency dep;

اختيار التنفيذ​

// الخيار 1: @Primary
@Primary
@Service
public class DefaultImpl implements MyInterface { }

// الخيار 2: @Qualifier
public MyService(@Qualifier("specificImpl") MyInterface service) { }

النقاط الرئيسية​

✅ استخدم دائمًا الحقن عبر المُنشئ
✅ أعلن التبعيات كـ final
✅ @Autowired اختياري على المُنشئ
✅ @Primary للتنفيذ الافتراضي
✅ @Qualifier للاختيار المحدد
⚠️ Setter فقط للتبعيات الاختيارية
❌ تجنب الحقن عبر الحقل


التقييم الذاتي​

  1. ما هي الأنواع الثلاثة للحقن؟
  2. لماذا يُفضل الحقن عبر المُنشئ؟
  3. ما الفرق بين @Primary و @Qualifier؟
  4. كيف تجعل التبعية اختيارية؟

الخطوة التالية​

أنت تعرف الآن كيف تحقن التبعيات. لننتقل إلى إنشاء وإدارة Beans!

➡️ الفصل 3: Beans وسياق Spring

⬅️ الفصل السابق: IoC

⬅️ العودة إلى الوحدة 5