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

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

مقدمة​

في الفصول السابقة، استخدمت @Service، @Repository، @Controller دون أن تفهم حقًا ما هي. يكشف لك هذا الفصل ما هو Bean وكيف يديرها Spring.

ما ستتعلمه​

  1. ما هو Bean في Spring؟
  2. التعليقات التوضيحية المختلفة للأنماط
  3. كيفية إنشاء Beans باستخدام @Bean و@Configuration
  4. كيفية تسمية Beans
  5. كيفية الاختيار بين عدة 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 للتنظيف


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

  1. ما الفرق بين Bean والكائن العادي؟
  2. متى تستخدم @Service مقابل @Component؟
  3. ما الفرق بين @Service و@Bean؟
  4. كيف تدير عدة تطبيقات لواجهة؟
  5. ما فائدة @PostConstruct؟

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

أنت الآن تفهم ما هي Beans. لننتقل إلى Scopes للتحكم في دورة حياتها!

➡️ الفصل 4: Scopes والتكوين

⬅️ الفصل السابق: حقن التبعيات

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