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

استيراد التهيئة وتقسيمها إلى وحدات

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

استيراد التهيئة وتقسيمها إلى وحدات

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

لماذا نقسّم التهيئة إلى وحدات؟

تعاني فئة التهيئة المتضخّمة عادةً من عدة مشاكل:

  • صعوبة التنقّل. تتشابك حبوب الأمان وحبوب الاستمرارية والرسائل والويب كلها في ملف واحد.
  • تعارضات الدمج. حين يعمل مطوّرون متعددون على ميزات مختلفة فإنهم جميعًا يُعدّلون الملف ذاته.
  • صعوبة الاختبار المعزول. تحميل السياق بالكامل لاختبار شريحة واحدة أمر بطيء وهشّ.
  • ضعف إعادة الاستخدام. لا يمكنك نقل تهيئة متخصصة ومكتفية بذاتها إلى مشروع آخر بسهولة.

التقسيم إلى وحدات يحلّ كل هذه المشاكل: كل فئة تهيئة تملك اهتمامًا واحدًا ويُركَّب الكل بشكل صريح.

@Import — تركيب فئات التهيئة

يُخبر @Import Spring بمعالجة فئة أو أكثر من فئات @Configuration الإضافية كأنها مُعلَن عنها مضمَّنةً. تصبح جميع الحبوب المُعرَّفة في الفئة المستوردة جزءًا من سياق التطبيق.

import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @Import({ PersistenceConfig.class, SecurityConfig.class, MessagingConfig.class }) public class AppConfig { // التهيئة الجذر — تُركّب الوحدات المتخصصة }

كل فئة مستوردة هي فئة @Configuration عادية بحبوبها الخاصة:

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; @Configuration public class PersistenceConfig { @Bean public DataSource dataSource() { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:postgresql://localhost/mydb"); ds.setUsername("app"); ds.setPassword("secret"); return ds; } }
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } }
@Import مقابل مسح المكوّنات: يلتقط مسح المكوّنات (الدرس 3) الفئات تلقائيًا بناءً على الحزمة الأساسية. أما @Import فهو صريح — تُسمّي بالضبط الفئة التي تريد تضمينها. افضّل @Import لتهيئة المكتبات والأطر، ولأي فئة تقع خارج الحزمة الأساسية لمسح المكوّنات.

رؤية الحبوب عبر التهيئات المستوردة

بمجرد استيراد فئة — سواء مباشرةً بـ @Import أو جُلبت عبر تعدية — تصبح حبوبها متاحةً لكل فئة تهيئة أخرى في السياق ذاته. يمكنك حقنها بـ @Autowired كمعاملات لباني فئة @Configuration، أو دع Spring يحلّها كمعاملات لأساليب @Bean.

@Configuration @Import({ PersistenceConfig.class, SecurityConfig.class }) public class ServiceConfig { // تحقن Spring حبة DataSource المُعرَّفة في PersistenceConfig @Bean public UserRepository userRepository(DataSource dataSource) { return new JdbcUserRepository(dataSource); } // تحقن Spring حبة PasswordEncoder المُعرَّفة في SecurityConfig @Bean public AuthService authService(UserRepository userRepository, PasswordEncoder passwordEncoder) { return new AuthServiceImpl(userRepository, passwordEncoder); } }

@Import الهرمي — التركيب المتعدي

يمكن للفئات المستوردة أن تستخدم @Import بدورها مما يُنشئ شجرة من التهيئات. هذا هو الأسلوب الذي تعمل به كثير من فئات الضبط التلقائي في Spring Boot داخليًا.

// PersistenceConfig تستورد بالفعل تهيئة JPA ذات مستوى أدنى @Configuration @Import(JpaConfig.class) public class PersistenceConfig { // حبوب DataSource ومدير المعاملات وما إلى ذلك } @Configuration public class JpaConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource ds) { LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(ds); factory.setPackagesToScan("com.example.domain"); return factory; } }

حين تستورد AppConfig كائن PersistenceConfig، تُعالج Spring تلقائيًا JpaConfig أيضًا. لا تحتاج إلى سردها مجددًا في AppConfig.

@ImportResource — دمج XML مع تهيئة Java

تحتوي المشاريع القديمة في الغالب على تعريفات حبوب XML موجودة. يُتيح لك @ImportResource سحبها إلى تهيئة قائمة على Java دون إعادة كتابتها:

@Configuration @ImportResource("classpath:legacy/messaging-beans.xml") public class LegacyBridgeConfig { // يمكن للحبوب الحديثة في Java الإشارة إلى الحبوب المُعرَّفة في ملف XML }
استراتيجية الترحيل: استخدم @ImportResource كجسر أثناء عملية الترحيل. حوّل حبوب XML إلى أساليب @Bean وحدةً وحدةً، ثم احذف ملف XML بعد اكتمال ترحيل كل وحدة. لا تحاول إعادة كتابة كل شيء دفعةً واحدة.

@ImportSelector — استيراد ديناميكي برمجي

حين تحتاج إلى تحديد عند بدء التشغيل أي فئات تهيئة تستورد بناءً على ظروف وقت التشغيل، نفّذ الواجهة ImportSelector:

import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; public class StorageConfigSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata metadata) { String storageType = System.getProperty("storage.type", "local"); if ("s3".equals(storageType)) { return new String[]{ "com.example.config.S3StorageConfig" }; } return new String[]{ "com.example.config.LocalStorageConfig" }; } } // يُستخدم عبر @Import: @Configuration @Import(StorageConfigSelector.class) public class AppConfig { }

هذه هي الآلية التي تقوم عليها @EnableAutoConfiguration في Spring Boot — تستخدم ImportSelector متطوّرًا لاختيار فئات الضبط التلقائي الصحيحة بناءً على ما هو موجود في مسار الفئات.

حدود الوحدات الموصى بها

اتفاقية عملية تتوسّع جيدًا في التطبيقات الحقيقية:

  • PersistenceConfig — DataSource وEntityManagerFactory ومدير المعاملات
  • SecurityConfig — PasswordEncoder وAuthenticationManager وسلسلة مرشّح الأمان
  • WebConfig — تهيئة MVC والمحوّلات وCORS ومحلّلات العرض
  • MessagingConfig — مصانع JMS/RabbitMQ/Kafka والمستمعون
  • CacheConfig — CacheManager ومولّدات المفاتيح
  • AppConfig (الجذر) — يستورد كل ما سبق؛ يُعرّف فقط الحبوب العابرة للاهتمامات حقًا
تجنّب @Import الدائري. إذا استوردت ConfigA كائن ConfigB واستورد ConfigB بدوره ConfigA، ستُطلق Spring استثناء BeanDefinitionParsingException أو تتصرّف بشكل غير متوقّع. حلّ التبعيات الدائرية باستخراج الحبوب المشتركة إلى فئة تهيئة ثالثة ذات مستوى أدنى يمكن لكلتيهما استيرادها.

اختبار وحدة تهيئة واحدة

من أكبر مكاسب التقسيم إلى وحدات سهولة الاختبار. يمكنك تحميل فئة تهيئة واحدة بالضبط في اختبار دون تكلفة السياق الكامل:

import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @SpringJUnitConfig(PersistenceConfig.class) // تحمّل هذه الوحدة فقط class PersistenceConfigTest { @Autowired DataSource dataSource; @Test void dataSourceIsConfigured() { assertThat(dataSource).isNotNull(); } }

الخلاصة

يُعدّ @Import الأداة الأساسية لتركيب فئات @Configuration المتخصصة في سياق تطبيق متماسك. رأيت كيف تستخدمه مباشرةً، وكيف تبني الاستيرادات المتعدية شجرة تهيئة، وكيف يُجسّر @ImportResource XML القديم، وكيف تُتيح ImportSelector استيرادات مشروطة في وقت التشغيل. تقسيم التهيئة حسب الاهتمام — الاستمرارية والأمان والرسائل — يجعل كل جزء قابلًا للتنقّل والاختبار وإعادة الاستخدام بشكل مستقل. يختتم الدرس التالي البرنامج التعليمي بمشروع عملي يجمع تهيئة البيئات المتعددة بالكامل.