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

إخراج الإعدادات باستخدام ملفات الخصائص

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

إخراج الإعدادات باستخدام ملفات الخصائص

يُعدّ ترميز قيم الإعدادات — عناوين URL لقواعد البيانات، ومفاتيح API، والمهلات الزمنية، وأعلام الميزات — مباشرةً داخل فئات Java من أكثر الأخطاء شيوعًا في مشاريع Spring المبتدئة. في اللحظة التي تحتاج فيها إلى تشغيل التطبيق ذاته مقابل قاعدة بيانات staging أو تدوير سر، ستُجبر على إعادة التصريف أو إعادة النشر. يُزيل نظام الإعدادات الخارجية في Spring هذه المشكلة بإبقاء تلك القيم في ملفات .properties خارج الكود المُصرَّف، ثم حقنها في الحبوب عند بدء التشغيل.

يتناول هذا الدرس آليتين رئيسيتين: @PropertySource لتحميل الملفات بشكل صريح، وواجهة برمجية Environment للوصول برمجيًا. يبني الدرس الخامس على هذا بتقديم @Value ولغة Spring Expression Language لحقن القيم مباشرةً في الحقول ومعاملات المُنشئ.

لماذا نُخرج الإعدادات خارج الكود؟

تُلزم منهجية تطبيق الاثني عشر عاملًا (twelve-factor app) بفصل صارم بين الكود والإعداد. عمليًا، يعني ذلك:

  • تشغيل نفس ملف JAR المُصرَّف في كل بيئة — التطوير، والتكامل المستمر، والاختبار، والإنتاج.
  • عدم ظهور الأسرار في نظام التحكم بالإصدارات.
  • تغيير قيمة ما (كحجم مجمّع الاتصالات) دون إعادة تصريف ودون أثر بناء جديد.
  • قدرة فرق التشغيل على ضبط السلوك دون الاقتراب من الكود المصدري.

التعليق @PropertySource

يُخبر @PropertySource Spring بتحميل ملف .properties وتسجيل أزواج مفتاح/قيمة الخاصة به في Environment الخاص بالتطبيق. توضعه على فئة @Configuration.

افترض وجود هذا الملف في src/main/resources/app.properties:

# app.properties datasource.url=jdbc:postgresql://localhost:5432/orders datasource.username=orders_user datasource.password=changeme datasource.pool.size=10 mail.host=smtp.example.com mail.port=587 mail.from=noreply@example.com

حمّله في فئة الإعداد:

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; @Configuration @PropertySource("classpath:app.properties") public class AppConfig { private final Environment env; public AppConfig(Environment env) { this.env = env; } @Bean public DataSourceProperties dataSourceProperties() { DataSourceProperties props = new DataSourceProperties(); props.setUrl(env.getRequiredProperty("datasource.url")); props.setUsername(env.getRequiredProperty("datasource.username")); props.setPassword(env.getRequiredProperty("datasource.password")); props.setPoolSize(env.getProperty("datasource.pool.size", Integer.class, 5)); return props; } }
classpath: مقابل file: مقابل https: سلسلة الموقع في @PropertySource هي مسار مورد Spring. يُحلّ classpath: من مسار فئات JVM (ملف JAR أو مجلد resources/). يقرأ file:/etc/myapp/secrets.properties من نظام الملفات في وقت التشغيل — مفيد للأسرار التي يُدخلها منسّق الحاويات. كذلك https:// مدعوم لكن نادرًا ما يُستخدم للخصائص.

استخدام واجهة Environment البرمجية

org.springframework.core.env.Environment هو تجريد Spring الموحَّد فوق جميع مصادر الخصائص. بمجرد تحميل ملف عبر @PropertySource، تصبح مفاتيحه متاحة من خلال هذه الواجهة في أي مكان في سياق التطبيق.

الأساليب الرئيسية التي تحتاج معرفتها:

  • env.getProperty("key") — تُعيد القيمة أو null إن لم يكن المفتاح موجودًا.
  • env.getProperty("key", "defaultValue") — تُعيد القيمة أو قيمة افتراضية.
  • env.getProperty("key", Integer.class) — تُعيد القيمة مُحوَّلة إلى النوع المطلوب، أو null.
  • env.getProperty("key", Integer.class, 10) — بحث بنوع مع قيمة افتراضية.
  • env.getRequiredProperty("key") — يرمي IllegalStateException إن كان المفتاح غائبًا؛ هو الخيار المُفضَّل للقيم الإلزامية.
  • env.containsProperty("key") — فحص منطقي، مفيد لأعلام الميزات الاختيارية.

ملفات متعددة والتراكم

يمكنك تحميل عدة ملفات على فئة واحدة باستخدام التعليق القابل للتكرار في Java 8:

@Configuration @PropertySource("classpath:defaults.properties") @PropertySource("classpath:overrides.properties") // قابل للتكرار في Java 8 public class MultiSourceConfig { // تفوز مفاتيح overrides.properties على defaults.properties // عند ظهور المفتاح ذاته في الاثنين }

عندما يوجد المفتاح ذاته في مصادر متعددة، يفوز المصدر الأخير المُسجَّل. هذا يجعل من السهل شحن قيم افتراضية معقولة والسماح لملف خاص بالبيئة بتجاوز ما يختلف فحسب.

اتفاقية التسمية: احتفظ بالملف الأساسي (مثلًا application.properties) في نظام التحكم بالإصدارات مع الإعدادات الافتراضية غير الحساسة وتعليقات الاستبدال. ملف مرافق كـ application-local.properties (مُدرَج في git-ignore) يحمل التجاوزات الخاصة بالمطوّر. في Spring Boot هذا مدمج؛ في Spring العادي تصله يدويًا عبر @PropertySource.

تجاهل الملفات الغائبة باستخدام ignoreResourceNotFound

بشكل افتراضي، إن لم يكن الملف المُحدَّد في @PropertySource موجودًا، يرمي Spring استثناءً عند بدء تشغيل السياق. يمكنك تخفيف هذا للملفات الاختيارية:

@PropertySource( value = "classpath:local-overrides.properties", ignoreResourceNotFound = true )

استخدم هذا للملفات الاختيارية — تجاوزات المطوّر، والأسرار المحلية، وتركيبات الاختبار — حتى يبدأ التطبيق بنظافة حتى في غياب الملف.

العناصر النائبة للخصائص داخل القيم

يُحلّ Spring بنية ${placeholder} داخل قيم الخصائص، مما يسمح لك بتأليف قيم من خصائص أخرى:

# base.properties app.env=production app.name=OrderService app.full.name=${app.name}-${app.env} # يُحلَّل إلى: OrderService-production log.dir=/var/log log.file=${log.dir}/app.log # يُحلَّل إلى: /var/log/app.log

يعمل هذا تلقائيًا بمجرد تسجيل حبة PropertySourcesPlaceholderConfigurer (في Spring العادي) أو الاعتماد على الإعداد التلقائي لـ Spring Boot الذي يُسجّله من أجلك.

يتطلب Spring العادي تسجيلًا صريحًا. إن لم تستخدم Spring Boot، لن تُحلَّل العناصر النائبة ${...} في تعليقات @Value وإعدادات XML إلا بوجود حبة PropertySourcesPlaceholderConfigurer. صرّح عنها كحبة static حتى تُعالَج قبل الحبوب الأخرى:
@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }
في Spring Boot تُسجَّل هذه الحبة تلقائيًا — لا تحتاج إضافتها بنفسك أبدًا.

@PropertySource مع مصنع مخصص: الترميز والتنسيقات المخصصة

بشكل افتراضي، يقرأ Spring ملفات .properties بترميز ISO-8859-1. إن احتوت قيمك على محارف UTF-8 (نصوص عربية، حروف مُنقَّطة، محارف CJK) فيجب أن تُصرّح بالترميز صراحةً:

@PropertySource(value = "classpath:i18n-config.properties", encoding = "UTF-8")

للتنسيقات غير المعيارية (YAML، TOML، ملفات الخصائص المشفّرة)، يتيح Spring مصنع PropertySourceFactory مخصصًا:

import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PropertySourceFactory; import java.io.IOException; import java.util.Properties; public class YamlPropertySourceFactory implements PropertySourceFactory { @Override public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { Properties props = loadYaml(resource); return new org.springframework.core.env.PropertiesPropertySource( resource.getResource().getFilename(), props); } private Properties loadYaml(EncodedResource resource) throws IOException { // استخدم YamlPropertiesFactoryBean أو SnakeYAML هنا return new Properties(); } }

ثم أشر إليه في التعليق:

@PropertySource( value = "classpath:config.yml", factory = YamlPropertySourceFactory.class )
Spring Boot يتولى هذا عنك. إن كنت على Spring Boot، يُحمَّل application.yml تلقائيًا بواسطة YamlPropertySourceLoader — لا حاجة لمصنع مخصص. نمط المصنع المخصص مفيد أساسًا في Spring العادي أو عند تحميل ملفات إضافية بتنسيقات غير معيارية إلى جانب الإعداد الرئيسي.

أولوية مصادر الخصائص

يحتفظ Spring بـقائمة ذات أولويات من مصادر الخصائص. في Spring Boot يكون الترتيب (الأعلى أولوية أولًا) تقريبًا:

  1. معطيات سطر الأوامر (--server.port=9090)
  2. متغيرات بيئة نظام التشغيل (SPRING_DATASOURCE_URL)
  3. خصائص نظام JVM (-Dspring.profiles.active=prod)
  4. خصائص خاصة بالمرحلة (application-prod.properties)
  5. الخصائص الافتراضية (application.properties)
  6. الملفات المضافة عبر @PropertySource

تقع الملفات التي تحمّلها بـ @PropertySource في قاع هذه الرزمة. هذا مقصود: يعني ذلك أن متغيرات بيئة النظام وسطر الأوامر تتجاوز دائمًا ما في ملفات الخصائص المُدرجة في الكود، مما يمنح فرق التشغيل السيطرة الكاملة في وقت التشغيل دون أن تمسّ الكود المصدري.

الخلاصة

@PropertySource هو الطريقة الصريحة المدفوعة بالتعليقات لتحميل ملف .properties في Environment الخاص بـ Spring. بمجرد تحميله، تصبح المفاتيح متاحة عبر Environment.getProperty() أو getRequiredProperty(). يمكنك تراكم ملفات متعددة، وتجاهل الملفات الاختيارية الغائبة، وتأليف القيم بعناصر ${} النائبة، بل وإضافة مصانع مخصصة للتنسيقات غير المعيارية. في الدرس التالي ستتعلم كيف تتخطى استدعاءات Environment API كليًا وتحقن القيم مباشرةً في حقول الحبة ومعاملات المُنشئ باستخدام @Value.