الفصل 3: Beans وسياق Spring
مقدمة
في الفصول السابقة، استخدمت @Service، @Repository، @Controller دون أن تفهم حقًا ما هي. يكشف لك هذا الفصل ما هو Bean وكيف يديرها Spring.
ما ستتعلمه
- ما هو Bean في Spring؟
- التعليقات التوضيحية المختلفة للأنماط
- كيفية إنشاء Beans باستخدام @Bean و@Configuration
- كيفية تسمية Beans
- كيفية الاختيار بين عدة Beans (@Qualifier, @Primary)
المدة: 45 دقيقة
ما هو Bean؟
تعريف بسيط
Bean = كائن يُدار بواسطة Spring
هذا كل شيء! لا شيء سحري.
تشبيه
تخيل Spring كـ فندق:
فندق Spring
├── الغرفة 101: Bean "userService"
├── الغرفة 102: Bean "productRepository"
├── الغرفة 103: Bean "orderController"
└── الغرفة 104: Bean "emailService"
Spring (الفندق):
- ينشئ الكائنات (الغرف)
- يسميها (أرقام الغرف)
- يحتفظ بها في الذاكرة
- يقدمها عند الحاجة
أنت (العميل):
- تطلب Bean: "أريد userService"
- Spring يقدمه لك: "تفضل، الغرفة 101!"
Bean مقابل الكائن العادي
الكائن العادي (بدون Spring)
// أنت تنشئ
EmailService emailService = new EmailService();
// أنت تدير حياة الكائن
emailService.configure();
// يجب عليك تمريره في كل مكان
OrderService orderService = new OrderService(emailService);
المشاكل:
- ❌ يجب عليك الإنشاء يدويًا
- ❌ يجب عليك إدارة التبعيات
- ❌ يجب عليك تمرير الكائنات في كل مكان
Bean في Spring
@Service // ← Spring يدير هذا الكائن
public class EmailService {
// التكوين التلقائي
}
@Service
public class OrderService {
// Spring يحقن EmailService تلقائيًا
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
المزايا:
- ✅ Spring ينشئ تلقائيًا
- ✅ Spring يدير التبعيات
- ✅ Spring يحقن حيثما كان ذلك ضروريًا
إنشاء Beans: الطريقة 1 - التعليقات التوضيحية للأنماط
التعليقات التوضيحية الأربعة الرئيسية
يوفر Spring 4 تعليقات توضيحية لإنشاء Beans:
@Component // مكون عام
@Service // طبقة الأعمال
@Repository // طبقة البيانات
@Controller // طبقة العرض (الويب)
التسلسل الهرمي
جميعها ترث من @Component!
1. @Component (عام)
الاستخدام: لأي مكون لا يندرج تحت الفئات الأخرى.
@Component
public class PriceCalculator {
public double calculateDiscount(double price) {
return price * 0.9;
}
}
متى تستخدمها؟
- الفئات المساعدة
- المكونات التي ليست Service، ولا Repository، ولا Controller
2. @Service (منطق الأعمال)
الاستخدام: لطبقة الأعمال (Service Layer).
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final EmailService emailService;
public OrderService(OrderRepository orderRepository,
EmailService emailService) {
this.orderRepository = orderRepository;
this.emailService = emailService;
}
public void processOrder(Order order) {
// منطق الأعمال
orderRepository.save(order);
emailService.sendConfirmation(order);
}
}
متى تستخدمها؟
- الفئات التي تحتوي على منطق الأعمال
- تنسيق عدة مستودعات
- المعاملات
3. @Repository (الوصول إلى البيانات)
الاستخدام: لطبقة الوصول إلى البيانات.
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
}
المزايا الخاصة:
- ✅ الترجمة التلقائية لاستثناءات SQL إلى استثناءات Spring
- ✅ دلالة واضحة على الدور (الوصول إلى البيانات)
متى تستخدمها؟
- واجهات JPA Repository
- فئات DAO (كائن الوصول إلى البيانات)
4. @Controller و@RestController (الويب)
الاستخدام: لطبقة الويب.
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping
public List<Order> getAllOrders() {
return orderService.getAllOrders();
}
}
الفرق:
@Controller→ يعيد العرض (HTML)@RestController→ يعيد JSON (API REST)
ملخص: متى تستخدم أي تعليق توضيحي؟
| التعليق التوضيحي | الطبقة | الاستخدام |
|---|---|---|
@Component | عام | الفئات المساعدة، المكونات الأخرى |
@Service | الأعمال | منطق الأعمال، التنسيق |
@Repository | البيانات | الوصول إلى قاعدة البيانات |
@Controller | الويب | صفحات الويب (HTML) |
@RestController | الويب | API REST (JSON) |
القاعدة الذهبية: استخدم التعليق التوضيحي الأكثر تحديدًا!
إنشاء Beans: الطريقة 2 - @Bean و@Configuration
لماذا طريقة أخرى؟
المشكلة: لا يمكنك إضافة @Service على الفئات التي لا تتحكم فيها.
أمثلة:
- فئات المكتبات الخارجية
- فئات Java القياسية (ObjectMapper، RestTemplate، إلخ.)
الحل: @Bean و@Configuration
@Configuration
فئة تكوين Spring:
@Configuration
public class AppConfig {
// طرق @Bean هنا
}
@Configuration تشير إلى Spring: "تحتوي هذه الفئة على تعريفات Beans"
@Bean
إنشاء Bean يدويًا:
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
return mapper;
}
}
ما يحدث:
1. Spring يفحص AppConfig
↓
2. Spring يجد الطريقة objectMapper() معلمة بـ @Bean
↓
3. Spring يستدعي objectMapper()
↓
4. Spring يحتفظ بالنتيجة كـ Bean مسمى "objectMapper"
↓
5. يمكنك حقن ObjectMapper في كل مكان!
مثال كامل
@Configuration
public class EmailConfig {
// Bean 1: تكوين البريد الإلكتروني
@Bean
public EmailSettings emailSettings() {
EmailSettings settings = new EmailSettings();
settings.setHost("smtp.gmail.com");
settings.setPort(587);
return settings;
}
// Bean 2: خدمة الإرسال (تعتمد على Bean 1)
@Bean
public EmailSender emailSender(EmailSettings settings) {
// ^^^^^^^^^^^^^^^^^^^^^^
// Spring يحقن تلقائيًا!
return new EmailSender(settings);
}
}
الاستخدام:
@Service
public class OrderService {
private final EmailSender emailSender;
// Spring يحقن الـ Bean الذي تم إنشاؤه في EmailConfig
public OrderService(EmailSender emailSender) {
this.emailSender = emailSender;
}
}
@Bean مقابل @Service
| الجانب | @Service | @Bean |
|---|---|---|
| على ماذا؟ | فئة | طريقة |
| أين؟ | في أي مكان | في @Configuration |
| التحكم | فئة لك | أي فئة |
| الاستخدام | فئات الأعمال | التكوين، الفئات الخارجية |
مثال:
// ✅ فئتك
@Service
public class MyService { }
// ✅ فئة خارجية
@Configuration
public class Config {
@Bean
public ExternalLibraryClass externalClass() {
return new ExternalLibraryClass();
}
}
تسمية Beans
الاسم الافتراضي
مع @Service, @Component، إلخ.:
@Service
public class UserService { }
// ^^^^^^^^^^^
// اسم الـ Bean: "userService" (camelCase)
مع @Bean:
@Bean
public EmailSender emailSender() {
// ^^^^^^^^^^^^
// اسم الـ Bean: "emailSender"
return new EmailSender();
}
القاعدة: الحرف الأول صغير (camelCase)
اسم مخصص
مع @Service:
@Service("myCustomName")
public class UserService { }
// Bean مسمى: "myCustomName"
مع @Bean:
@Bean(name = "gmailSender")
public EmailSender emailSender() {
return new GmailEmailSender();
}
// Bean مسمى: "gmailSender"
إدارة عدة تطبيقات
المشكلة
public interface PaymentProcessor {
void process(Payment payment);
}
@Service
public class StripePayment implements PaymentProcessor { }
@Service
public class PayPalPayment implements PaymentProcessor { }
// ❌ Spring لا يعرف أيهما يحقن!
@Service
public class OrderService {
private final PaymentProcessor processor;
public OrderService(PaymentProcessor processor) {
// خطأ عند بدء التشغيل!
}
}
الحل 1: @Primary
تحديد تطبيق كالتطبيق الافتراضي:
@Service
@Primary // ← هذا هو التطبيق الافتراضي
public class StripePayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("الدفع عبر Stripe");
}
}
@Service
public class PayPalPayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("الدفع عبر PayPal");
}
}
النتيجة:
@Service
public class OrderService {
private final PaymentProcessor processor;
// Spring يحقن تلقائيًا StripePayment (@Primary)
public OrderService(PaymentProcessor processor) {
this.processor = processor;
}
}
الحل 2: @Qualifier
اختيار تطبيق محدد:
@Service("stripePayment")
public class StripePayment implements PaymentProcessor { }
@Service("paypalPayment")
public class PayPalPayment implements PaymentProcessor { }
الحقن مع الاختيار:
@Service
public class OrderService {
private final PaymentProcessor processor;
public OrderService(@Qualifier("paypalPayment") PaymentProcessor processor) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// يختار PayPal بشكل صريح
this.processor = processor;
}
}
الحل 3: حقن جميع Beans
إذا كنت بحاجة إلى جميع التطبيقات:
@Service
public class PaymentOrchestrator {
private final List<PaymentProcessor> allProcessors;
// Spring يحقن قائمة بجميع PaymentProcessor
public PaymentOrchestrator(List<PaymentProcessor> allProcessors) {
this.allProcessors = allProcessors;
}
public void processWithAll(Payment payment) {
for (PaymentProcessor processor : allProcessors) {
processor.process(payment);
}
}
}
سياق Spring (ApplicationContext)
ما هو السياق؟
سياق Spring = الحاوية التي تحتوي على جميع Beans
الوصول إلى السياق (نادراً ما يكون ضروريًا)
عادةً، Spring يحقن تلقائيًا. ولكن إذا لزم الأمر:
@Service
public class MyService {
private final ApplicationContext context;
// حقن السياق نفسه
public MyService(ApplicationContext context) {
this.context = context;
}
public void doSomething() {
// استرجاع Bean يدويًا
UserService userService = context.getBean(UserService.class);
// استرجاع Bean بالاسم
EmailSender sender = context.getBean("emailSender", EmailSender.class);
// سرد جميع Beans من نوع معين
Map<String, PaymentProcessor> processors =
context.getBeansOfType(PaymentProcessor.class);
}
}
⚠️ تجنب فعل ذلك! إنه نمط سيء. استخدم الحقن العادي.
دورة حياة Beans
الخطوات
1. الإنشاء
↓
2. حقن التبعيات
↓
3. التهيئة (@PostConstruct)
↓
4. Bean جاهز (قابل للاستخدام)
↓
5. التدمير (@PreDestroy)
نقاط دورة الحياة
تنفيذ الكود بعد الحقن:
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Service
public class DatabaseService {
private final DataSource dataSource;
public DatabaseService(DataSource dataSource) {
this.dataSource = dataSource;
// ❌ dataSource لم يتم تهيئته بالكامل بعد
}
@PostConstruct // ← يُستدعى بعد الحقن
public void init() {
System.out.println("DatabaseService تم تهيئته");
// ✅ كل شيء جاهز، يمكنك القيام بالتهيئات
connectToDatabase();
}
@PreDestroy // ← يُستدعى قبل التدمير
public void cleanup() {
System.out.println("DatabaseService تم تدميره");
// إغلاق الاتصالات، تحرير الموارد
disconnectFromDatabase();
}
}
ترتيب التنفيذ:
1. new DatabaseService(dataSource) ← المنشئ
2. init() ← @PostConstruct
3. [Bean مستخدم أثناء التطبيق]
4. cleanup() ← @PreDestroy (عند الإيقاف)
مثال كامل
// الواجهة
public interface NotificationService {
void send(String message);
}
// التطبيق 1
@Service("emailNotification")
public class EmailNotification implements NotificationService {
@PostConstruct
public void init() {
System.out.println("EmailNotification تم تهيئته");
}
@Override
public void send(String message) {
System.out.println("البريد الإلكتروني: " + message);
}
@PreDestroy
public void cleanup() {
System.out.println("EmailNotification تم تدميره");
}
}
// التطبيق 2
@Service("smsNotification")
@Primary // ← الافتراضي
public class SmsNotification implements NotificationService {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
// التكوين
@Configuration
public class AppConfig {
@Bean
public NotificationLogger logger() {
return new NotificationLogger();
}
}
// الاستخدام
@Service
public class AlertService {
private final NotificationService notification;
private final List<NotificationService> allNotifications;
public AlertService(
NotificationService notification, // SmsNotification (@Primary)
List<NotificationService> allNotifications // كلاهما
) {
this.notification = notification;
this.allNotifications = allNotifications;
}
public void alert(String message) {
// يستخدم SMS (Primary)
notification.send(message);
}
public void alertAll(String message) {
// يستخدم الجميع
allNotifications.forEach(n -> n.send(message));
}
}
// الاستخدام مع الاختيار
@Service
public class SpecialAlertService {
private final NotificationService notification;
public SpecialAlertService(
@Qualifier("emailNotification") NotificationService notification
) {
this.notification = notification;
}
}
ملخص الفصل
ما هو Bean؟
Bean = كائن يُدار بواسطة Spring
إنشاء Beans
الطريقة 1: التعليقات التوضيحية للأنماط
@Component // عام
@Service // الأعمال
@Repository // البيانات
@Controller // الويب
الطريقة 2: @Bean في @Configuration
@Configuration
public class Config {
@Bean
public MyClass myBean() {
return new MyClass();
}
}
إدارة عدة تطبيقات
@Primary // التطبيق الافتراضي
@Qualifier("name") // اختيار محدد
List<Interface> // حقن الجميع
دورة الحياة
@PostConstruct // بعد الحقن
@PreDestroy // قبل التدمير
النقاط الرئيسية
✅ Bean = كائن يُدار بواسطة Spring
✅ @Service, @Repository, @Controller ترث من @Component
✅ @Bean للفئات الخارجية أو التكوين المعقد
✅ @Primary للتطبيق الافتراضي
✅ @Qualifier للاختيار المحدد
✅ @PostConstruct للتهيئة
✅ @PreDestroy للتنظيف
التقييم الذاتي
- ما الفرق بين Bean والكائن العادي؟
- متى تستخدم @Service مقابل @Component؟
- ما الفرق بين @Service و@Bean؟
- كيف تدير عدة تطبيقات لواجهة؟
- ما فائدة @PostConstruct؟
الخطوة التالية
أنت الآن تفهم ما هي Beans. لننتقل إلى Scopes للتحكم في دورة حياتها!