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

@Value ولغة تعبيرات Spring (SpEL)

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

@Value ولغة تعبيرات Spring (SpEL)

تخزّن ملفات الخصائص ومتغيرات البيئة إعدادات تطبيقك الخارجية: عناوين URL لقواعد البيانات وقيم المهلة الزمنية وأعلام الميزات وعشرات الإعدادات الأخرى التي تختلف بين بيئات التطوير والاختبار والإنتاج. تُعدّ @Value التعليقَ الأساسي الذي يستخدمه Spring لحقن هذه القيم الخارجية مباشرةً في الـ beans. علاوةً على ذلك، يأتي Spring مزوّدًا بمحرك تعبيرات صغير لكن قوي — يُعرف بـ لغة تعبيرات Spring (SpEL) — يُتيح لك حساب القيم وقت الحقن بدلًا من نسخ السلسلة النصية الخام فحسب.

حقن @Value الأساسي

أبسط صور @Value تسحب خاصية من application.properties (أو application.yml) باستخدام عنصر نائب للخاصية محاط بـ ${...}.

# application.properties app.name=OrderService app.max-retries=3 app.timeout-ms=5000 app.debug-mode=false
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service public class OrderService { @Value("${app.name}") private String appName; @Value("${app.max-retries}") private int maxRetries; @Value("${app.timeout-ms}") private long timeoutMs; @Value("${app.debug-mode}") private boolean debugMode; public void processOrder() { System.out.printf("Service: %s | retries: %d | timeout: %d ms%n", appName, maxRetries, timeoutMs); } }

يُجري Spring تحويل الأنواع تلقائيًا: تصبح القيمة النصية "3" في ملف الخصائص عددًا صحيحًا int، و"5000" تصبح long، و"false" تصبح boolean. يعمل هذا مع جميع الأنواع الأولية وأنواعها المغلّفة وعدد من أنواع القيم الشائعة.

القيم الافتراضية

إذا كانت الخاصية مفقودة ولم يُقدَّم أي افتراضي، يرمي Spring استثناء BeanCreationException عند بدء التشغيل — إخفاق سريع لا قيمة null صامتة. يمكنك تقديم قيمة احتياطية باستخدام صيغة النقطتين داخل العنصر النائب:

@Value("${app.feature-flag:false}") private boolean featureEnabled; // false إذا كانت الخاصية غائبة @Value("${app.greeting:Hello, World!}") private String greeting;
قدّم دائمًا قيمًا افتراضية للإعدادات الاختيارية. إذا كانت الخاصية مطلوبة فعلًا — ككلمة مرور قاعدة البيانات — اتركها بدون افتراضي عمدًا. إخفاق بدء التشغيل يعمل بوصفه حاجزًا أمام النشر: لن يبدأ تطبيقك دون قيمة عرّفتها بوضوح على أنها إلزامية.

حقن القوائم والمصفوفات

يمكن حقن قيمة خاصية مفصولة بفواصل مباشرةً في مصفوفة أو List:

# application.properties app.allowed-origins=https://example.com,https://app.example.com,https://admin.example.com
@Value("${app.allowed-origins}") private String[] allowedOrigins; // تُقسَّم على الفاصلة تلقائيًا @Value("${app.allowed-origins}") private List<String> originList; // يعمل بالطريقة ذاتها
يتم تقسيم الفاصلة بواسطة ConversionService المدمج في Spring. ينطبق هذا على المصفوفات وList<T> حيث يمكن لـ Spring تحويل سلسلة نصية مفردة إلى النوع T. للهياكل المعقدة (الخرائط والكائنات المتداخلة) فضّل @ConfigurationProperties على @Value.

مقدمة إلى SpEL — #{...}

عندما تحتاج إلى أكثر من قيمة خاصية خام، يمكنك تضمين تعبير SpEL داخل #{...}. يُقيَّم SpEL في وقت التشغيل بواسطة حاوية Spring ويستطيع الإشارة إلى beans أخرى واستدعاء الدوال وإجراء العمليات الحسابية والوصول إلى خصائص النظام.

// تعبيرات حرفية @Value("#{1 + 2}") private int three; // 3 @Value("#{'Hello ' + 'World'}") private String greeting; // "Hello World" // المنطق البولياني @Value("#{10 > 5 and 3 < 7}") private boolean result; // true

الإشارة إلى beans أخرى في SpEL

يستطيع SpEL الوصول إلى beans أخرى مُدارة بواسطة Spring باسمها، مستدعيًا الدوال أو قارئًا الخصائص:

@Component("appConfig") public class AppConfig { public int getMaxConnections() { return 20; } public String getRegion() { return "eu-west-1"; } }
@Service public class ReportService { // استدعاء دالة على bean آخر @Value("#{appConfig.maxConnections}") private int maxConnections; // دمج استدعاء bean مع تسلسل نصي @Value("#{appConfig.region + '-reports'}") private String bucketName; // "eu-west-1-reports" }

يعتمد اسم الـ bean في SpEL افتراضيًا على اسم الفئة البسيطة بحرف أول صغير ما لم تُحدد اسمًا صريحًا باستخدام @Component("appConfig").

مزج ${...} و#{...}

يمكنك تداخل عناصر نائبة للخصائص داخل تعبيرات SpEL لدمج الإعدادات الخارجية مع الحساب في وقت التشغيل:

# application.properties pool.base-size=5 pool.multiplier=2
// احسب حجم التجمّع عند بدء التشغيل: 5 * 2 = 10 @Value("#{${pool.base-size} * ${pool.multiplier}}") private int computedPoolSize; // شرطي: استخدم قيمة آمنة في وضع الاختبار @Value("#{environment['app.env'] == 'test' ? 1 : ${pool.base-size}}") private int activePoolSize;
لا تُفرط في استخدام SpEL لمنطق الأعمال. إذا كان تعبير @Value أطول من سطر واحد أو يتطلب شروطًا متعددة، فهذا المنطق ينتمي إلى دالة @PostConstruct أو فئة إعداد مخصصة، لا إلى تعليق توضيحي. تعبيرات SpEL المعقدة يصعب اختبارها، ومستحيل التحقق منها وقت الترجمة، وعسيرة التصحيح.

خصائص النظام ومتغيرات البيئة

يُعرّض SpEL كائنَين ضمنيَّين: systemProperties (خصائص نظام JVM من أعلام -D) وsystemEnvironment (متغيرات بيئة نظام التشغيل). يمكنك الوصول إلى أيٍّ منهما مباشرةً:

// قراءة خاصية نظام JVM (-Djava.io.tmpdir=...) @Value("#{systemProperties['java.io.tmpdir']}") private String tmpDir; // قراءة متغير بيئة نظام التشغيل @Value("#{systemEnvironment['HOME']}") private String homeDir;

في Spring Boot يكون استخدام ${MY_ENV_VAR} (عنصر نائب للخاصية) أبسط دائمًا تقريبًا، لأن Boot يُعيّن متغيرات البيئة بالفعل إلى تجريد Environment الخاص به. احتفظ بصيغة systemEnvironment في SpEL للحالات التي تحتاج فيها إلى دمجها مع عمليات SpEL أخرى.

حقن معاملات المُنشئ والدالة

لا تقتصر @Value على الحقول. يمكنك تعليق معاملات المُنشئ ومعاملات الدالة، مما يجعل الـ bean أسهل في اختبار الوحدة دون سياق Spring:

@Service public class PaymentService { private final String apiKey; private final int timeoutMs; public PaymentService( @Value("${payment.api-key}") String apiKey, @Value("${payment.timeout-ms:3000}") int timeoutMs) { this.apiKey = apiKey; this.timeoutMs = timeoutMs; } }

مع حقن المُنشئ يمكنك ببساطة كتابة new PaymentService("test-key", 500) في اختبار وحدة — لا حاجة لسياق تطبيق، ولا سحر انعكاس، ولا إطار محاكاة مطلوب للإعداد نفسه.

@Value مقابل @ConfigurationProperties

معرفة متى تستخدم كلًّا منهما يتجنب رائحة معمارية شائعة:

  • @Value — خاصية أو خاصيتان متفرقتان تنتميان إلى بادئات مختلفة؛ وصول سريع إلى قيم محسوبة أو بيئية عبر SpEL؛ الحالات التي تكون فيها فئة ربط قوية النوع مبالغةً في الأمر.
  • @ConfigurationProperties — مجموعة متماسكة من الخصائص ذات الصلة التي تشترك في بادئة مشتركة (مثل app.mail.*). تمنحك POJO مكتوبة بأنواع محددة وإكمال تلقائي في IDE والتحقق من JSR-303 عند بدء التشغيل وقابلية اختبار أفضل.
عامل @Value باعتبارها الاستثناء لا القاعدة. لأي ميزة تحتوي على أكثر من عنصرين أو ثلاثة من مفاتيح الضبط، أنشئ سجل أو فئة @ConfigurationProperties. احتفظ بـ @Value لحقن الخاصية الواحدة وبـ SpEL للحالات التي تحتاج فيها إلى حساب وقت التشغيل.

الخلاصة

تحقن @Value("${property}") الإعدادات الخارجية في الـ beans مع تحويل تلقائي للأنواع وقيم افتراضية اختيارية. تضمّن @Value("#{expression}") تعبير SpEL يُقيَّم عند بدء تشغيل الحاوية، ممنوحًا إياك الوصول إلى beans أخرى والعمليات الحسابية والشرطية وخصائص النظام. يتكامل الصيغتان: يمكنك تداخل عناصر نائبة ${} داخل تعبيرات #{}. فضّل حقن معاملات المُنشئ على حقن الحقول لتحسين قابلية الاختبار، وانتقل إلى @ConfigurationProperties حين تنمو مجموعة الخصائص ذات الصلة إلى ما هو أكثر من حفنة من القيم.