تنفيذ واجهة برمجة تطبيقات Spring Framework من الصفر. تجول للمبتدئين. الجزء الأول



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

أود أن أقدم لكم مقاربة جديدة بشكل أساسي لدراسة الربيع. وهو يتألف من حقيقة أن الشخص يمر بسلسلة من البرامج التعليمية المعدة بشكل خاص وينفذ بشكل مستقل وظيفة الربيع. تكمن خصوصية هذا النهج في أنه بالإضافة إلى فهم 100٪ للجوانب المدروسة للربيع ، فإنه يعطي أيضًا زيادة كبيرة في Java Core (التعليقات التوضيحية ، التأمل ، الملفات ، الوراثة).

ستمنحك المقالة تجربة لا تنسى وتجعلك تشعر بأنك مطور محوري. خطوة بخطوة ، سوف تجعل الفصول الدراسية الخاصة بك وتنظم دورة حياتها (مثل الربيع الحقيقي). الفئات التي ستنفذها هي BeanFactory و Component و Service و BeanPostProcessor و BeanNameAware و BeanFactoryAware و InitializingBean و PostConstruct و PreDestroy و DisposableBean و ApplicationContext و ApplicationListener و ContextClosedEvent .

القليل عن نفسك


اسمي ياروسلاف وأنا مطور جافا مع 4 سنوات من الخبرة. في الوقت الحالي أعمل لدى EPAM Systems (SPB) ، وأتعمق كثيرًا في التقنيات التي نستخدمها. في كثير من الأحيان يجب أن أتعامل مع الربيع ، وأرى فيه بعض الوسط الذي يمكنك فيه النمو (جافا يعرف الجميع جيدًا ، ويمكن للأدوات والتقنيات المحددة أن تأتي وتذهب).

قبل شهرين ، حصلت على شهادة Spring Professional v5.0 (بدون أخذ دورات). بعد ذلك ، فكرت في كيفية تعليم الآخرين ينبوع. لسوء الحظ ، لا توجد في الوقت الحالي منهجية تدريس فعالة. معظم المطورين لديهم فكرة سطحية للغاية عن الإطار وميزاته. يعد تصحيح مصادر الربيع أمرًا صعبًا للغاية وغير فعال على الإطلاق من وجهة نظر التدريب (كنت مولعًا بذلك بطريقة أو بأخرى). هل 10 مشاريع؟ نعم ، في مكان ما يمكنك تعميق معرفتك واكتساب الكثير من الخبرة العملية ، ولكن الكثير مما هو "تحت غطاء المحرك" لن يفتح أمامك أبدًا. قراءة الربيع في العمل؟ رائع ، ولكنه مكلف في الجهد. لقد عملت 40 ٪ (أثناء التحضير للشهادة) ، لكن ذلك لم يكن سهلاً.

الطريقة الوحيدة لفهم شيء حتى النهاية هي تطويره بنفسك. في الآونة الأخيرة ، كانت لدي فكرة أنه يمكنك قيادة شخص من خلال برنامج تعليمي مثير للاهتمام سيشرف على تطوير إطار عمل DI. وستكون ميزته الرئيسية أن API تتزامن مع API قيد الدراسة. إن روعة هذا النهج هو أنه بالإضافة إلى فهم عميق (بدون مسافات) للربيع ، سيحصل الشخص على قدر كبير من الخبرة في Java Core. بصراحة ، لقد تعلمت بنفسي الكثير من الأشياء الجديدة أثناء إعداد المقالة ، في كل من Spring و Java Core. دعنا نبدأ تطوير!

المشروع من الصفر


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

في مشروع نظيف ، أنشئ حزمتين رئيسيتين. الحزمة الأولى هي تطبيقك ( com.kciray ) ، وفئة Main.java بداخله. الحزمة الثانية هي org.springframework. نعم ، سنقوم بتكرار بنية الحزمة للربيع الأصلي ، واسم فئاتها وطرقها. هناك مثل هذا التأثير المثير للاهتمام - عندما تنشئ شيئًا خاصًا بك ، يبدأ تأثيره الخاص في أن يبدو بسيطًا ومفهومًا. بعد ذلك ، عندما تعمل في مشاريع كبيرة ، يبدو لك أن كل شيء يتم إنشاؤه هناك بناءً على قطعة عملك. يمكن أن يكون لهذا النهج تأثير إيجابي للغاية على فهم النظام ككل ، وتحسينه ، وإصلاح الأخطاء ، وحل المشكلات ، وما إلى ذلك.

إذا كان لديك أي مشاكل ، يمكنك أن تأخذ مشروع عمل هنا .

قم بإنشاء حاوية


للبدء ، قم بتعيين المهمة. لنفترض أن لدينا فئتين - ProductFacade و PromotionService . تخيل الآن أنك تريد ربط هذه الفئات ببعضها البعض ، ولكن حتى لا تعرف الفئات نفسها عن بعضها البعض (نمط DI). نحن بحاجة إلى فصل منفصل يدير كل هذه الفئات ويحدد التبعيات بينها. دعونا نسميها حاوية. لنقم بإنشاء فئة Container ... على الرغم من لا ، انتظر! لا يحتوي الربيع على فئة حاوية واحدة. لدينا العديد من تطبيقات الحاويات ، ويمكن تقسيم جميع هذه التطبيقات إلى نوعين - مصانع الحاويات والسياقات. يقوم مصنع بن بإنشاء الفاصوليا وربطها معًا (حقن التبعية ، DI) ، والسياق يفعل نفس الشيء ، بالإضافة إلى أنه يضيف بعض الميزات الإضافية (على سبيل المثال ، تدويل الرسائل). لكننا لسنا بحاجة إلى هذه الوظائف الإضافية الآن ، لذلك سنعمل مع مصنع بن.

أنشئ فئة BeanFactory جديدة وضعها في مجموعة org.springframework.beans.factory . اسمح بتخزين Map<String, Object> singletons داخل هذه الفئة ، حيث يتم تعيين id الحاوية إلى الحاوية نفسها. أضف إليها طريقة Object getBean(String beanName) ، والتي تسحب الفاصوليا حسب المعرف.

 public class BeanFactory { private Map<String, Object> singletons = new HashMap(); public Object getBean(String beanName){ return singletons.get(beanName); } } 

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

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

 public class PromotionsService { } public class ProductService { private PromotionsService promotionsService; public PromotionsService getPromotionsService() { return promotionsService; } public void setPromotionsService(PromotionsService promotionsService) { this.promotionsService = promotionsService; } } 

الآن نحن بحاجة إلى جعل الحاوية الخاصة بنا ( BeanFactory ) تكتشف BeanFactory لنا وحقن واحدة في الأخرى. يجب أن تكون العمليات مثل new ProductService() داخل الحاوية ويتم إجراؤها للمطور. دعونا نستخدم أحدث الأساليب (المسح الطبقي والتعليقات التوضيحية). لإجراء ذلك ، نحتاج إلى إنشاء تعليق توضيحي @Component باستخدام @Component ( org.springframework.beans.factory.stereotype ).

 @Retention(RetentionPolicy.RUNTIME) public @interface Component { } 

بشكل افتراضي ، لا يتم تحميل التعليقات التوضيحية في الذاكرة أثناء تشغيل البرنامج ( RetentionPolicy.CLASS ). قمنا بتغيير هذا السلوك من خلال سياسة احتفاظ جديدة ( RetentionPolicy.RUNTIME ).

أضف الآن @Component قبل فئات ProductService وقبل PromotionService .

 @Component public class ProductService { //... } @Component public class PromotionService { //... } 


نحتاج BeanFactory لفحص الحزمة ( com.kciray ) والعثور على فئات فيها مشروحة بواسطة @Component . هذه المهمة بعيدة عن أن تكون تافهة. لا يوجد حل جاهز في Java Core ، وعلينا أن نصنع عكازًا بأنفسنا. تستخدم الآلاف من تطبيقات الربيع المسح الضوئي للمكونات من خلال هذا العكاز. لقد تعلمت الحقيقة الرهيبة. سيتعين عليك استخراج أسماء ClassLoader من ClassLoader والتحقق مما ClassLoader كانت تنتهي بـ ".class" أم لا ، ثم بناء اسمها الكامل وسحب كائنات الفئة منه!

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

 //BeanFactory.java public class BeanFactory{ public void instantiate(String basePackage) { } } //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); 

بعد ذلك ، نحتاج إلى الحصول على ClassLoader . وهي مسؤولة عن تحميل الفئات ، ويتم استخلاصها بكل بساطة:

 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 

ربما لاحظت بالفعل أن الحزم مفصولة بنقطة ، والملفات بشرطة مائلة للأمام. نحتاج إلى تحويل مسار الدُفعات إلى مسار المجلد ، والحصول على شيء مثل List<URL> (المسارات في نظام الملفات الخاص بك حيث يمكنك البحث عن ملفات الفئة).

 String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray" Enumeration<URL> resources = classLoader.getResources(path); 

لذا انتظر لحظة! Enumeration<URL> ليس List<URL> . ما هو كل هذا؟ أوه ، رعب ، هذا السلف القديم لـ Iterator ، متاح منذ Java 1.0. هذا هو الإرث الذي علينا أن نتعامل معه. إذا كان من الممكن السير عبر Iterable باستخدام (جميع المجموعات تنفذها) ، في حالة Enumeration سيتعين عليك إجراء تجاوز جانبي ، حتى while(resources.hasMoreElements()) و nextElement() . ومع ذلك ، لا توجد طريقة لإزالة العناصر من المجموعة. فقط عام 1996 ، المتشددين فقط. أوه نعم ، في Java 9 أضافوا طريقة Enumeration.asIterator() ، حتى تتمكن من العمل من خلالها.

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

 while (resources.hasMoreElements()) { URL resource = resources.nextElement(); File file = new File(resource.toURI()); for(File classFile : file.listFiles()){ String fileName = classFile.getName();//ProductService.class } } 

بعد ذلك ، نحتاج إلى الحصول على اسم الملف بدون الامتداد. في الفناء في عام 2018 ، طورت Java الملف I / O (NIO 2) لسنوات عديدة ، ولكن لا يزال يتعذر فصل الامتداد عن اسم الملف. لا بد لي من إنشاء دراجتي الخاصة ، لأن قررنا عدم استخدام مكتبات خارجية مثل Apache Commons. دعونا نستخدم طريقة الجد القديمة lastIndexOf(".") :

 if(fileName.endsWith(".class")){ String className = fileName.substring(0, fileName.lastIndexOf(".")); } 

بعد ذلك ، يمكننا الحصول على كائن الفئة باستخدام الاسم الكامل للفئة (لهذا نسمي فئة فئة Class ):

 Class classObject = Class.forName(basePackage + "." + className); 

حسنا ، الآن فصولنا في أيدينا. علاوة على ذلك ، يبقى فقط أن نبرز من بينها تلك التي تحتوي على تعليق توضيحي @Component :

 if(classObject.isAnnotationPresent(Component.class)){ System.out.println("Component: " + classObject); } 

اركض وافحص. يجب أن تكون وحدة التحكم على النحو التالي:

 Component: class com.kciray.ProductService Component: class com.kciray.PromotionsService 

الآن نحن بحاجة إلى إنشاء الفول لدينا. تحتاج إلى القيام بشيء مثل new ProductService() ، ولكن لكل حبة لدينا فئة خاصة بنا. يزودنا التأمل في Java بحل عالمي (يسمى المُنشئ الافتراضي):

 Object instance = classObject.newInstance();//=new CustomClass() 

بعد ذلك ، نحتاج إلى وضع هذه الحبة في Map<String, Object> singletons . للقيام بذلك ، حدد اسم الفول (معرفه). في Java ، نسمي متغيرات مثل الفئات (الحرف الأول فقط هو أحرف صغيرة). يمكن تطبيق هذا النهج على الفاصوليا أيضًا ، لأن Spring إطار Java! قم بتحويل اسم الحاوية بحيث يكون الحرف الأول صغيرًا ، وأضفه إلى الخريطة:

 String beanName = className.substring(0, 1).toLowerCase() + className.substring(1); singletons.put(beanName, instance); 

الآن تأكد من أن كل شيء يعمل. يجب أن تخلق الحاوية الفاصوليا ، ويجب استرجاعها بالاسم. يرجى ملاحظة أن اسم طريقة classObject.newInstance(); الخاصة بك واسم أسلوب classObject.newInstance(); لها جذر مشترك. علاوة على ذلك ، فإن instantiate() هو جزء من دورة حياة الفول. في جافا ، كل شيء مترابط!

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); ProductService productService = (ProductService) beanFactory.getBean("productService"); System.out.println(productService);//ProductService@612 


جرب أيضًا تنفيذ org.springframework.beans.factory.stereotype.Service شرح الخدمة. إنه يؤدي نفس الوظيفة التي @Component ، ولكن يطلق عليه بشكل مختلف. النقطة كلها في الاسم - أنت تثبت أن الصف خدمة وليس مجرد مكون. هذا شيء مثل الكتابة المفاهيمية. في شهادة الربيع كان هناك سؤال "ما هي التعليقات التوضيحية النمطية؟" (من تلك المذكورة). " لذا ، فإن التعليقات التوضيحية النمطية هي تلك الموجودة في حزمة stereotype .

املأ الخصائص


انظر إلى الرسم البياني أدناه ، فهو يوضح بداية دورة حياة الفول. ما فعلناه قبل ذلك هو Instantiate (إنشاء الفاصوليا من خلال newInstance() ). والخطوة التالية هي الحقن المتقاطع للفاصوليا (حقن التبعية ، وهي أيضًا انعكاس التحكم (IoC)). تحتاج إلى مراجعة خصائص الفاصوليا وفهم الخصائص التي تحتاج إلى حقنها. إذا اتصلت بـ productService.getPromotionsService() ، ستحصل على قيمة null ، لأن التبعية لم تضاف بعد.



أولاً ، أنشئ حزمة org.springframework.beans.factory.annotation وأضف التعليق التوضيحي @Autowired . والفكرة هي وضع علامة على الحقول التابعة مع هذا التعليق التوضيحي.

 @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } 

بعد ذلك ، قم بإضافته إلى العقار:

 @Component public class ProductService { @Autowired PromotionsService promotionsService; //... } 

الآن نحن بحاجة إلى تعليم BeanFactory لدينا للعثور على هذه التعليقات التوضيحية وحقن التبعيات عليها. أضف طريقة منفصلة لهذا ، واسميها من Main :

 public class BeanFactory { //... public void populateProperties(){ System.out.println("==populateProperties=="); } } 

بعد ذلك ، نحتاج فقط إلى object.getClass().getDeclaredFields() جميع object.getClass().getDeclaredFields() ، ولكل صندوق يمر عبر جميع حقوله ( object.getClass().getDeclaredFields() جميع الحقول ، بما في ذلك الحقول الخاصة). وتحقق مما إذا كان الحقل يحتوي على تعليق توضيحي @Autowired :

 for (Object object : singletons.values()) { for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { } } } 

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

 for (Object dependency : singletons.values()) { if (dependency.getClass().equals(field.getType())) { } } 

علاوة على ذلك ، عندما وجدنا الإدمان ، نحتاج إلى حقنه. أول شيء قد تفكر فيه هو كتابة حقل خدمة promotionsService باستخدام التفكير المباشر. لكن الربيع لا يعمل بهذه الطريقة. بعد كل شيء ، إذا كان الحقل يحتوي على مُعدِّل private ، فسنضطر أولاً إلى تعيينه على أنه عام ، ثم كتابة قيمتنا ، ثم تعيينه على private مرة أخرى (للحفاظ على التكامل). يبدو مثل عكاز كبير. بدلاً من عكاز كبير ، دعنا نصنع عكازًا صغيرًا (سنقوم بتشكيل اسم العداد ونطلق عليه):

 String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService System.out.println("Setter name = " + setterName); Method setter = object.getClass().getMethod(setterName, dependency.getClass()); setter.invoke(object, dependency); 

الآن قم بتشغيل مشروعك وتأكد من أنه عند استدعاء productService.getPromotionsService() بدلاً من null ، يتم إرجاع الفول لدينا.

ما قمنا بتطبيقه هو الحقن حسب النوع. هناك أيضًا حقنة بالاسم ( javax.annotation.Resource شرح). يختلف في أنه بدلاً من نوع الحقل ، سيتم استخراج اسمه ، ووفقًا له - الاعتماد من الخريطة. كل شيء مشابه هنا ، حتى في شيء أبسط. أوصي بتجربة وإنشاء الفول الخاص بك ، ثم حقنه باستخدام @Resource وتوسيع طريقة populateProperties() .

نحن ندعم الفاصوليا التي تعرف باسمها




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

كيف نعرف الفول الذي يريد أن يعرف ما هو اسمه وما لا يريده؟ أول ما يتبادر إلى الذهن هو عمل تعليق توضيحي جديد من النوع @InjectName في حقول من النوع String. لكن هذا الحل سيكون عامًا جدًا ويسمح لك بتصوير نفسك في القدم عدة مرات (ضع هذا التعليق التوضيحي على حقول الأنواع غير المناسبة (وليس سلسلة) ، أو حاول إدخال اسم في عدة حقول في نفس الفصل). هناك حل آخر ، أكثر دقة - لإنشاء واجهة خاصة باستخدام طريقة ضبط واحدة. جميع الصناديق التي تنفذها لها اسمها. أنشئ فئة BeanNameAware في حزمة org.springframework.beans.factory :

 public interface BeanNameAware { void setBeanName(String name); } 

بعد ذلك ، دع خدمة PromotionsService تنفذها:

 @Component public class PromotionsService implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } } 

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

 public void injectBeanNames(){ for (String name : singletons.keySet()) { Object bean = singletons.get(name); if(bean instanceof BeanNameAware){ ((BeanNameAware) bean).setBeanName(name); } } } 

اركض وتأكد من أن كل شيء يعمل:

 BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); beanFactory.populateProperties(); beanFactory.injectBeanNames(); //... System.out.println("Bean name = " + promotionsService.getBeanName()); 

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

تهيئة الفاصوليا




تخيل أن لديك حالة تحتاج فيها إلى تنفيذ بعض التعليمات البرمجية بعد حقن التبعيات (يتم تعيين خصائص الحاوية). بعبارات بسيطة ، نحن بحاجة إلى إعطاء الحاوية القدرة على تهيئة نفسها. بدلاً من ذلك ، يمكننا إنشاء واجهة InitializingBean ، ووضع توقيع طريقة void afterPropertiesSet() فيه. إن تنفيذ هذه الآلية هو نفسه تمامًا مثل ذلك الذي تم تقديمه لواجهة BeanNameAware ، لذا فإن الحل موجود تحت المفسد. تدرب وافعل ذلك بنفسك في دقيقة:

حل تهيئة الفاصوليا
 //InitializingBean.java package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet(); } //BeanFactory.java public void initializeBeans(){ for (Object bean : singletons.values()) { if(bean instanceof InitializingBean){ ((InitializingBean) bean).afterPropertiesSet(); } } } //Main.java beanFactory.initializeBeans(); 



إضافة معالجات ما بعد


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

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

يمكننا تمكين كلا الخيارين دفعة واحدة. دع معالج ما بعد واحد يحمل منطقين وطريقتين. يتم تنفيذ أحدهما قبل التهيئة (قبل الطريقة afterPropertiesSet() ) ، والآخر بعد. الآن دعونا نفكر في الأساليب نفسها - ما هي المعلمات التي يجب أن تكون لديهم؟ من الواضح أن Object bean نفسه يجب أن يكون هناك. للراحة ، بالإضافة إلى الفاصوليا ، يمكنك تمرير اسم هذه الحبة. تتذكر أن السلة نفسها لا تعرف اسمها. ولا نريد إجبار جميع الفاصوليا على تنفيذ واجهة BeanNameAware. ولكن ، على مستوى ما بعد المعالج ، يمكن أن يكون اسم الفول مفيدًا جدًا. لذلك ، نضيفه كمعلمة ثانية.

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

 package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); } 

بعد ذلك ، نحتاج إلى إضافة قائمة بالمعالجات البسيطة إلى مصنع الفاصوليا والقدرة على إضافة معالجات جديدة. نعم ، هذه قائمة ArrayList عادية.

 //BeanFactory.java private List<BeanPostProcessor> postProcessors = new ArrayList<>(); public void addPostProcessor(BeanPostProcessor postProcessor){ postProcessors.add(postProcessor); } 

الآن قم بتغيير طريقة initializeBeans بحيث تأخذ في الاعتبار المعالجات اللاحقة:

 public void initializeBeans() { for (String name : singletons.keySet()) { Object bean = singletons.get(name); for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeforeInitialization(bean, name); } if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessAfterInitialization(bean, name); } } } 

لنقم بإنشاء معالج بريد صغير يتتبع ببساطة المكالمات إلى وحدة التحكم ويضيفها إلى مصنع الفول لدينا:

 public class CustomPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor Before " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor After " + beanName); return bean; } } 

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.addPostProcessor(new CustomPostProcessor()); 


اركض الآن وتأكد من عمل كل شيء. كمهمة تدريب ، قم بإنشاء معالج بريد يوفر التعليق التوضيحي @PostConstruct (javax.annotation.PostConstruct) . يوفر طريقة بديلة للتهيئة (متجذرة في Java ، وليس في الربيع). جوهرها هو أنك تضع التعليق التوضيحي على بعض الطرق ، وستسمى هذه الطريقة قبل التهيئة القياسية الربيعية (InitializingBean).

تأكد من إنشاء جميع التعليقات التوضيحية والحزم (حتى javax.annotation) يدويًا ، لا تقم بربط التبعيات! هذا سيساعدك على رؤية الفرق بين قلب الربيع وامتداداته (دعم javax) ، وتذكره. .

, @PostConstruct , - CommonAnnotationBeanPostProcessor. , .

, void close() BeanFactory . — @PreDestroy (javax.annotation.PreDestroy) , , . — org.springframework.beans.factory.DisposableBean , void destroy() . , , ( , ).

@PreDestroy + DisposableBean
 //DisposableBean.java package org.springframework.beans.factory; public interface DisposableBean { void destroy(); } //PreDestroy.java package javax.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface PreDestroy { } //DisposableBean.java public void close() { for (Object bean : singletons.values()) { for (Method method : bean.getClass().getMethods()) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.invoke(bean); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (bean instanceof DisposableBean) { ((DisposableBean) bean).destroy(); } } } 




, . , .


, , . -. , — , BeanFactory . , (DI), . — .

, .

. org.springframework.context , ApplicationContext . BeanFactory . , close() .

 public class ApplicationContext { private BeanFactory beanFactory = new BeanFactory(); public ApplicationContext(String basePackage) throws ReflectiveOperationException{ System.out.println("******Context is under construction******"); beanFactory.instantiate(basePackage); beanFactory.populateProperties(); beanFactory.injectBeanNames(); beanFactory.initializeBeans(); } public void close(){ beanFactory.close(); } } 


Main , , :

 ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); 

, . close() , « » - . , :

 package org.springframework.context.event; public class ContextClosedEvent { } 

ApplicationListener , . , ( ApplicationListener<E> ). , Java-, . , , :

 package org.springframework.context; public interface ApplicationListener<E>{ void onApplicationEvent(E event); } 

ApplicationContext . close() , , . ApplicationListener<ContextClosedEvent> , onApplicationEvent(ContextClosedEvent) . , ?

 public void close(){ beanFactory.close(); for(Object bean : beanFactory.getSingletons().values()) { if (bean instanceof ApplicationListener) { } } } 

. . bean instanceof ApplicationListener<ContextClosedEvent> . Java. (type erasure) , <T> <Object>. , ? , ApplicationListener<ContextClosedEvent> , ?

, , . , , , , :

 for (Type type: bean.getClass().getGenericInterfaces()){ if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; } } 

, , , — . , :

 Type firstParameter = parameterizedType.getActualTypeArguments()[0]; if(firstParameter.equals(ContextClosedEvent.class)){ Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class); method.invoke(bean, new ContextClosedEvent()); } 

ApplicationListener:

 @Service public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> { //... @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println(">> ContextClosed EVENT"); } } 

, Main , , :

 //Main.java void testContext() throws ReflectiveOperationException{ ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); } 


الخلاصة


Baeldung , , . , . 30, . , Spring Core, , Core Spring 5.0 Certification Study Guide . , Java-.

Update 10/05/2018


« , ». , . , - , -. , .

:
Spring Container — [ ]
Spring AOP — [ ]
Spring Web — [ ]
Spring Cloud — [ ]

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


All Articles