إعداد Spring والملفّات الشخصية

الإعداد القائم على Java

18 دقيقة الدرس 2 من 13

الإعداد القائم على Java

دعم Spring طرقًا متعددة لتعريف الـ beans عبر تاريخه. جاء أسلوب XML أولًا، ثم التعليقات التوضيحية كـ @Component و@Autowired، وأخيرًا — مع Spring 3.0 ومع تحسينات كبيرة في Spring 4 وما بعده — الإعداد القائم على Java: فئات Java عادية مُزيَّنة بـ @Configuration تحتوي على توابع مُزيَّنة بـ @Bean تُعرِّف الـ beans وتربطها في الكود. في تطبيق Spring Boot 3 اليوم، هذا هو الأسلوب السائد الذي تحتاج إلى إتقانه بالكامل.

فئة @Configuration

فئة مُزيَّنة بـ @Configuration تُخبر Spring: "عامل هذه الفئة كمصدر لتعريفات الـ beans." يُنشئ Spring وكيل CGLIB لتلك الفئة حتى يستطيع اعتراض استدعاءات توابع @Bean وتطبيق عقد Singleton (موضَّح أدناه). في أبسط صورته:

import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean; import jakarta.persistence.EntityManagerFactory; @Configuration public class AppConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } }

يستدعي Spring التابع passwordEncoder() مرةً واحدةً، يحفظ النتيجة في سياق التطبيق تحت اسم الـ bean الافتراضي passwordEncoder (اسم التابع)، ويُعيد نفس الكائن لأي مكوّن يحتاج إلى حقن PasswordEncoder.

الاسم الافتراضي للـ bean: الاسم الافتراضي هو اسم التابع. يمكنك تجاوزه: @Bean("encoder") أو @Bean(name = {"encoder", "pwdEncoder"}) (أسماء بديلة). الاسم الأول هو الأساسي وما بعده مجرد أسماء مستعارة.

تعريف التبعيات بين الـ Beans

الطريقة الأكثر وضوحًا للتعبير عن أن bean أ يعتمد على bean ب هي تعريف bean ب كمعامل في تابع @Bean الخاص بـ أ. يحلّ Spring ذلك حسب النوع من السياق، تمامًا كما يفعل مع حقن المُنشئ:

@Configuration public class ServiceConfig { @Bean public UserRepository userRepository(DataSource dataSource) { return new JdbcUserRepository(dataSource); } @Bean public UserService userService(UserRepository userRepository, PasswordEncoder passwordEncoder) { return new UserServiceImpl(userRepository, passwordEncoder); } }

هذا الأسلوب يجعل رسم التبعيات صريحًا ومرئيًا وقت الترجمة. يمكنك رؤية ما يحتاجه كل bean دفعةً واحدة دون الغوص في مُنشئ الفئة.

وكيل CGLIB وضمان Singleton

تأمّل ما يحدث عندما يستدعي تابع @Bean تابعًا آخر مباشرةً:

@Configuration public class AppConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } @Bean public UserService userService() { // استدعاء تابع @Bean آخر مباشرةً: return new UserServiceImpl(passwordEncoder()); // آمن — Spring يعترضه } }

بما أن Spring يستبدل الفئة بفئة فرعية من CGLIB، فإن استدعاء passwordEncoder() داخل userService() لا يُنشئ BCryptPasswordEncoder ثانيًا. يعترض الوكيل الاستدعاء، يتحقق مما إذا كان bean بذلك الاسم موجودًا بالفعل في السياق، ويُعيد الكائن الموجود. هذا يحافظ على عقد Singleton.

@Configuration مقابل @Component: إذا زيَّنت فئة الإعداد بـ @Component العادية بدلًا من @Configuration، فلن يُنشئ Spring وكيل CGLIB. تصبح استدعاءات @Bean البينية استدعاءات Java عادية وتُنشئ كائنًا جديدًا في كل مرة. هذه خطأ دقيق شائع — استخدم دائمًا @Configuration للفئات التي تستدعي توابع @Bean بعضها بعضًا.

نطاق الـ Bean

افتراضيًا كل @Bean هو Singleton — كائن واحد لكل سياق تطبيق. تُغيِّر النطاق بـ @Scope:

import org.springframework.context.annotation.Scope; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public ReportGenerator reportGenerator() { return new ReportGenerator(); }

النطاق Prototype يعني أن Spring يُنشئ كائنًا جديدًا تمامًا في كل مرة يُطلب فيها الـ bean. النطاقات الأخرى المُدمجة (request وsession) تُستخدم في تطبيقات الويب ويدعمها سياق Servlet.

دورة الحياة: initMethod و destroyMethod

يمكنك الاشتراك في دورة حياة الـ bean دون أن تعرف فئة الـ bean شيئًا عن Spring:

@Bean(initMethod = "connect", destroyMethod = "disconnect") public MessageBrokerClient brokerClient() { MessageBrokerClient client = new MessageBrokerClient(); client.setHost("localhost"); client.setPort(5672); return client; }

يستدعي Spring التابع connect() بعد بناء الـ bean كاملًا وتعيين خصائصه، ويستدعي disconnect() عند إغلاق السياق. يُبقي ذلك الفئات الخارجية خالية من تبعيات Spring مع ربطها بدورة الحياة بشكل صحيح.

فضّل خاصيتَي دورة الحياة في @Bean للفئات الخارجية. لفئاتك الخاصة، @PostConstruct و@PreDestroy (من jakarta.annotation) مباشرةً على فئة الـ bean أوضح — القصد مرئي هناك في الكود، وتعملان بغض النظر عن طريقة تعريف الـ bean (مسح مكوّنات أو @Bean صريح).

@Primary و @Qualifier

عندما يوجد عدة beans من نفس النوع في السياق، يحتاج Spring إلى توجيه. استخدم @Primary لتعليم الخيار الافتراضي، أو استند إلى @Qualifier عند نقطة الحقن للتحديد الصريح:

@Bean @Primary public DataSource primaryDataSource() { // تجمّع HikariCP لقاعدة البيانات الرئيسية return buildPool("jdbc:postgresql://primary:5432/app"); } @Bean public DataSource reportingDataSource() { // تجمّع منفصل لنسخة القراءة return buildPool("jdbc:postgresql://replica:5432/app"); }

عند حقن DataSource دون تخصيص إضافي، يختار Spring الـ primaryDataSource. للحقن الصريح للنسخة: @Qualifier("reportingDataSource") عند نقطة الحقن.

تنظيم الإعداد: فئات @Configuration متعددة

التطبيق الحقيقي يحتوي على عشرات الـ beans. تقسيم الإعداد حسب الاهتمام يُبقي الملفات مركّزة وقابلة للصيانة:

// DatabaseConfig.java — كل شيء متعلق بـ DataSource وـ JPA @Configuration public class DatabaseConfig { /* توابع @Bean ... */ } // SecurityConfig.java — beans الأمان @Configuration public class SecurityConfig { /* توابع @Bean ... */ } // MessagingConfig.java — beans Kafka / RabbitMQ / SQS @Configuration public class MessagingConfig { /* توابع @Bean ... */ }

يكتشف Spring Boot جميع فئات @Configuration التي يمكن الوصول إليها من حزمة فئة @SpringBootApplication عبر مسح المكوّنات. لا توجد قائمة رئيسية للإدارة — فقط ضعها في الحزمة الصحيحة.

Java Config مقابل XML مقابل مسح @Component — متى تستخدم كلًا منها

  • @Configuration القائم على Java: الأفضل لـ beans البنية التحتية (مصادر البيانات وعملاء HTTP والذاكرة المؤقتة وسماسرة الرسائل وـ beans الأمان) وللكائنات الخارجية التي لا تملك كودها. منطق المصنع كود Java مرئي وقابل للاختبار.
  • @Component / @Service / @Repository: الأفضل لفئات التطبيق الخاصة بك — الخدمات والـ repositories والمتحكمات. تشريفات أقل؛ الفئة تُعلن عن نفسها كـ bean.
  • XML: تراث قديم؛ تجنّبه في الكود الجديد. قد تصطدم به عند التكامل مع وحدات Spring قديمة أو مكتبات مؤسسية تأتي بفضاءات أسماء XML.

في الممارسة تمزج الأساليب الثلاثة. الإعداد التلقائي لـ Spring Boot (فئات @AutoConfiguration في كل JAR مُبدئ) مكتوب تقريبًا بالكامل كإعداد قائم على Java، ولهذا فإن فهم @Configuration و@Bean بعمق يُتيح لك قراءة أي شيء يُعدّه إطار العمل وتجاوزه.

الخلاصة

يمنحك الإعداد القائم على Java تعريفات beans آمنة النوع وقابلة للتنقل عبر بيئة التطوير في Java عادية. زيِّن الفئة بـ @Configuration ليُنشئ Spring وكيلًا لها، أعلن كل bean كتابع @Bean، وعبِّر عن التبعيات كمعاملات للتابع. استخدم @Scope لتغيير عمر الـ bean، وinitMethod/destroyMethod لربط أحداث دورة الحياة للفئات الخارجية، و@Primary/@Qualifier لحل التعارضات. قسِّم الإعدادات الكبيرة عبر فئات متعددة مركّزة — مسح المكوّنات يجدها جميعًا. في الدرس التالي نتعمق في كيفية عمل مسح المكوّنات نفسه لتفهم بالضبط أي الفئات يلتقطها Spring ومتى.