الفصل 2: حقن التبعيات (DI)
مقدمة
في الفصل 1، فهمت لماذا يوجد IoC. الآن، ستتعلم كيف يقوم Spring بحقن التبعيات.
ما ستتعلمه
- الأنواع الثلاثة لحقن التبعيات
- لماذا يُفضل الحقن عبر المُنشئ
- كيفية استخدام
@Autowired,@Inject,@Resource - إدارة التبعيات الاختيارية
المدة: 45 دقيقة
الأنواع الثلاثة للحقن
يوفر Spring ثلاث طرق لحقن التبعيات:
- الحقن عبر المُنشئ ✅ (موصى به)
- الحقن عبر Setter
- الحقن عبر الحقل ⚠️ (غير موصى به)
لنستعرض كل منها بالتفصيل.
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 فقط للتبعيات الاختيارية
❌ تجنب الحقن عبر الحقل
التقييم الذاتي
- ما هي الأنواع الثلاثة للحقن؟
- لماذا يُفضل الحقن عبر المُنشئ؟
- ما الفرق بين @Primary و @Qualifier؟
- كيف تجعل التبعية اختيارية؟
الخطوة التالية
أنت تعرف الآن كيف تحقن التبعيات. لننتقل إلى إنشاء وإدارة Beans!