الحصول على Spring Bean من سياق التطبيق لجهة خارجية بشكل صحيح

عمت مساءً يا خبروفيت!

في هذه المقالة ، أقترح مناقشة إحدى المشكلات التي تصادفها غالبًا في المشروعات التي تستخدم إطار Spring. تنشأ المشكلة الموضحة في هذه المقالة بسبب أحد الأخطاء النموذجية في تكوينات الربيع. لا حاجة لمحاولة ارتكاب مثل هذا الخطأ في التكوين ، وبالتالي هذا الخطأ شائع للغاية.

بيان المشكلة


تتعلق المشكلة المقدمة في هذه المقالة بالتكوين غير الصحيح للفاصوليا في سياق التطبيق الحالي ، والتي يتم أخذها من سياقات التطبيق الأخرى. يمكن أن تنشأ مثل هذه المشكلة في تطبيق صناعي كبير ، يتكون من العديد من الأواني ، ولكل منها سياق تطبيق خاص به يحتوي على حبوب الربيع.

نتيجة للتكوين غير الصحيح ، نحصل على عدة نسخ من الفاصوليا في حالة غير متوقعة ، حتى لو كانت ذات نطاق مفرد. علاوة على ذلك ، يمكن أن تؤدي النسخ غير المدروسة للفاصوليا إلى حقيقة أنه سيتم إنشاء أكثر من اثني عشر نسخة من جميع الفاصوليا من أي جرة في التطبيق ، وهو محفوف بمشاكل أداء التطبيق وزيادة وقت بدء تشغيل التطبيق.

مثال على استخدام وحدة برامج من سياق تطبيق خارجي في التيار


تخيل أننا نقوم بتطويره في إحدى وحدات التطبيق ، حيث يوجد العديد من الوحدات النمطية الأخرى ، وأن كل وحدة من الوحدات لديها سياق التطبيق الخاص بها. يجب أن يحتوي هذا التطبيق على وحدة نمطية يتم فيها إنشاء مثيلات سياق التطبيق لجميع وحدات التطبيق.



لنفترض ، في سياق التطبيق الخاص بإحدى الوحدات الخارجية ، أن يتم إنشاء مثيل لفول الفئة NumberGenerator ، والذي نريد الحصول عليه في الوحدة النمطية الخاصة بنا. افترض أيضًا أن فئة NumberGenerator موجودة في الحزمة org.example.kruchon.generators ، التي تخزن بعض الفئات التي تنشئ قيمًا.



هذه الفاصوليا لديها حالة - مجال العد int.

package org.example.kruchon.calculators public class NumberGenerator { private int count = 0; public synchronized int next() { return count++; } } 

يتم إنشاء مثيل لهذه الحبة في التكوين الفرعي GeneratorsConfiguration.

 @Configuration public class GeneratorsConfiguration { @Bean public NumberGenerator numberGenerator() { return new NumberGenerator(); } ... } 

أيضًا في سياق التطبيق الخارجي ، يوجد تكوين رئيسي يتم فيه استيراد كافة التكوينات الفرعية للوحدة الخارجية.

 @Configuration @Import({GeneratorsConfiguration.class, ...}) public class ExternalContextConfiguration { ... } 

الآن سأقدم بعض الأمثلة التي تم فيها تكوين الفول المفرد لفئة NumberGenerator بشكل غير صحيح في تكوين سياق التطبيق الحالي.

التكوين غير صحيح 1. استيراد التكوين الرئيسي لسياق التطبيق الخارجي


أسوأ قرار يمكن أن يكون.

 @Configuration @Import(ExternalContextConfiguration.class) public class CurrentContextConfiguration { ... } 

  • يعيد التطبيق إنشاء كل مثيلات الفاصوليا من سياق التطبيق الخارجي. بمعنى آخر ، يتم إنشاء نسخة من الوحدة الخارجية بأكملها ، مما يؤثر على استهلاك الذاكرة والأداء ووقت بدء تشغيل التطبيق.
  • الحصول على نسخة من NumberGenerator في سياق التطبيق الحالي. تحتوي نسخة NumberGenerator على قيمتها الخاصة بحقل العدد ، بما يتعارض مع المثيل الأول من NumberGenerator. يؤدي هذا التضارب إلى حدوث أخطاء يصعب تصحيحها في التطبيق.

التكوين غير صحيح 2. استيراد التكوين الفرعي لسياق التطبيق الخارجي


الخيار الثاني غير صحيح وكثيرا ما واجه في الممارسة العملية.

 @Configuration @Import(GeneratorsConfiguration.class) public class CurrentContextConfiguration { ... } 

في هذا النموذج ، لم يعد يتم إنشاء نسخة كاملة من الوحدة الخارجية ، ومع ذلك ، نحصل مرة أخرى على الحبة الثانية من فئة NumberGenerator.

التكوين غير صحيح 3. ابحث عن الحقن مباشرةً في الحبة ، حيث نريد استخدام NumberGenerator


 public class OrderFactory { private final NumberGenerator numberGenerator; public OrderFactory() { ApplicationContext externalApplicationContext = getExternalContext(); numberGenerator = externalApplicationContext.getBean(NumberGenerator.class); } public Order create() { Order order = new Order(); int id = numberGenerator.next(); order.setId(id); order.setCreatedDate(new Date()); return order; } private ApplicationContext getExternalContext(){ ... } } 

في هذه الطريقة ، يمكن اعتبار ازدواجية حبة لها نطاق مفرد حلها. في الواقع ، نحن الآن نعيد استخدام الحبة من سياق تطبيق آخر ولا نعيد إنشائها!

لكن بهذه الطريقة:

  1. يعقد الصف المتقدمة واختبار وحدتها.
  2. يستبعد التنفيذ التلقائي للفاصوليا من فئة NumberGenerator في حبوب الوحدة النمطية الحالية.
  3. ليس من المعتاد استخدام lookUp لحقن حبة مفردة في الحالات العامة.

لذلك ، يشبه هذا الحل كحل بديل أكثر من الحل الرشيد لمشكلة ما.

فكر في كيفية تكوين فاصوليا بشكل صحيح من سياق تطبيق خارجي.

الحل 1. الحصول على الفول من سياق التطبيق الخارجي في التكوين


تشبه هذه الطريقة المثال الثالث لتكوين غير صحيح بفارق واحد: نحصل على حبة عن طريق إجراء lookUp من السياق الخارجي في التكوين ، وليس مباشرة في الحبة.

 @Configuration public class CurrentContextConfiguration { @Bean public NumberGenerator numberGenerator() { ApplicationContext externalApplicationContext = getExternalContext(); return externalApplicationContext.getBean(NumberGenerator.class); } private ApplicationContext getExternalContext(){ ... } } 

الآن يمكننا تضمين هذه الحبة تلقائيًا في الفاصوليا من وحدتنا الخاصة.

الحل 2. اجعل سياق التطبيق الخارجي الأصل


من المحتمل أن تعمل وظيفة الوحدة الحالية على توسيع وظيفة الجهاز الخارجي. قد تكون هناك حالة عندما يتم تطوير فاصوليا مساعدة شائعة في التطبيق في الوحدات الخارجية ، ويتم استخدام هذه الفاصوليا في الوحدات الأخرى. في هذه الحالة ، من المنطقي الإشارة إلى أن الوحدة الخارجية هي الأصل بالنسبة للوحدة السابقة. في هذه الحالة ، يمكن استخدام كل الحبة من الوحدة الرئيسية في الوحدة النمطية الحالية ، ومن ثم لا يلزم تكوين وحدة برامج الوحدة الرئيسية في توصيف سياق التطبيق الحالي.

من الممكن تحديد علاقة أصل عند إنشاء نسخة سياق باستخدام المُنشئ ذي المعلمة الأصل:

 public AbstractApplicationContext(ApplicationContext parent) { ... } 

أو استخدم المضبط:

 public void setParent(ApplicationContext parent) { ... } 

إذا تم الإعلان عن سياق التطبيق بلغة xml ، فيمكننا استخدام المنشئ:

 public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { ... } 

استنتاج


لذا ، كن حذرًا عند تكوين حبوب الربيع ، اتبع التوصيات الواردة في المقالة وحاول عدم نسخ الحبوب ذات النطاق المفرد. سأكون سعيدًا للإجابة على أسئلتك!

Source: https://habr.com/ru/post/ar472716/


All Articles