Spring Spring 2: ما لا يكتبونه في ملاحظات الإصدار



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

في مؤتمر Joker 2018 ، تحدثت عن المشكلات التي واجهتها بنفسي عند التبديل إلى Spring Boot 2 وكيف تم حلها. والآن خصيصا للحبر - النسخة النصية من هذا التقرير. للراحة ، يحتوي المنشور على تسجيل فيديو وجدول محتويات: لا يمكنك قراءة كل شيء ، لكن يمكنك الانتقال مباشرة إلى المشكلة التي تقلقك.

جدول المحتويات





يوم جيد! أريد أن أخبرك عن بعض الميزات (دعنا نسميها مكابس) التي قد تصادفها عند تحديث إطار Spring Boot إلى الإصدار الثاني وتشغيله اللاحق.

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

باختصار عن المنتج التجريبي


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

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

تقرير المخطط




هناك الكثير من المناطق التي ظهرت فيها ميزات Spring Boot 2 ، وسنحاول الانتقال إلى كل شيء. للقيام بذلك بسرعة ، نحتاج إلى محقق أو محقق ذي خبرة - شخص سيكشف كل هذا كما لو كان بالنسبة لنا. نظرًا لأن Holmes و Watson قدما بالفعل عرضًا تقديميًا في Joker ، فسوف يتم مساعدتنا من قِبل متخصص آخر هو الملازم أول كولومبو. المضي قدما!

ربيع الحذاء / 2


أولاً ، بضع كلمات حول Spring Boot بشكل عام والإصدار الثاني بشكل خاص. أولاً ، تم إصدار هذا الإصدار ، بعبارة ملطفة ، وليس بالأمس: في 1 مارس (آذار) 2018 ، كان بالفعل في حالة التوفر العام. كان أحد الأهداف الرئيسية التي اتبعها المطورون دعم Java 8 بشكل كامل على مستوى المصدر. وهذا يعني أنه لا يمكن تجميعها على إصدار أصغر ، على الرغم من أن وقت التشغيل متوافق. تم اعتماد إطار Spring Spring الخاص بالإصدار الخامس ، والذي تم إصداره قبل الإصدار Spring Boot 2 بقليل ، وهذا ليس هو التبعية الوحيدة. لديه أيضًا مفهوم مثل BOM (Bill Of Materials) - وهو XML كبير ، والذي يسرد جميع التبعيات (متعدية بالنسبة لنا) على جميع أنواع مكتبات الطرف الثالث ، والأطر الإضافية ، والأدوات ، وأكثر من ذلك.

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

تجميع الوقت. أمثلة تغيير API


لنبدأ مع أشعل النار أكثر بسيطة وأقل وضوحا: هذه هي التي تنشأ في وقت الترجمة. أي أنه لن يسمح لك حتى بترجمة المشروع إذا قمت ببساطة بتغيير الرقم 1 في البرنامج النصي Boot إلى 2.

إن المصدر الرئيسي للتغييرات ، الذي أصبح أساسًا لهذه التعديلات في Spring Boot ، هو ، بطبيعة الحال ، انتقال Spring إلى Java 8. بالإضافة إلى ذلك ، تم تقسيم رصة Spring 5 و Spring Boot 2 على شبكة الإنترنت إلى اثنين. الآن هو servlet ، التقليدية بالنسبة لنا ، ورد الفعل. بالإضافة إلى ذلك ، كان من الضروري مراعاة عدد من أوجه القصور في الإصدارات السابقة. بدأت مكتبات الطرف الثالث (من خارج Spring). إذا نظرت إلى Release Notes ، فلن ترى أي عيوب أثناء الطيران ، وبصراحة ، عندما قرأت ملاحظات الإصدار لأول مرة ، بدا لي أن كل شيء على ما يرام هناك. وبالنسبة لي بدا الأمر مثل هذا:


ولكن ، كما تعتقد ، ربما ليس كل شيء جيدًا.

ماذا ستنفذه المجموعة (مثال 1):

  • لماذا : فئة WebMvcConfigurerAdapter لم يعد ؛
  • لماذا : لدعم رقائق Java 8 (الأساليب الافتراضية في الواجهات) ؛
  • ما يجب فعله : استخدم واجهة WebMvcConfigurer .

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

ما التصنيف الذي سينفجر (مثال 2):

  • لماذا : بدأت طريقة PropertySourceLoader#load بإرجاع قائمة بالمصادر بدلاً من واحدة ؛
  • لماذا : لدعم موارد المستندات المتعددة ، على سبيل المثال ، YAML ؛
  • ما يجب القيام به : قم بتغطية الاستجابة في singletonList() (عند تجاوزها).

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

ما التصنيف الذي سينفجر (مثال 3):

  • لماذا : org.springframework.boot.autoconfigure.web بعض الفئات من حزمة org.springframework.boot.autoconfigure.web حزم .servlet - .servlet و .reactive ؛
  • لماذا : لدعم المكدس النفاث على قدم المساواة مع التقليدية ؛
  • ما يجب القيام به : تحديث الواردات.

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

ما التصنيف الذي سينفجر (مثال 4):

  • لماذا : تم تغيير توقيع أساليب فئة ErrorAttributes : بدلاً من RequestAttributes ، تم استخدام WebRequest(servlet) و ServerRequest(reactive) ؛
  • لماذا : لدعم المكدس النفاث على قدم المساواة مع التقليدية ؛
  • ما يجب فعله : استبدل أسماء الفئات بالتوقيعات.

على سبيل المثال ، في فئة ErrorAttributes ، الآن ، بدلاً من RequestAttributes ، بدأ استخدام فئتين أخريين في الأساليب: هاتان WebRequest و ServerRequest. السبب هو نفسه. وماذا تفعل حيال ذلك؟ إذا كنت تقوم بالتبديل من الأول إلى الحذاء الربيعي الثاني ، فأنت بحاجة إلى تغيير RequestAttributes إلى WebRequest. حسنًا ، إذا كنت بالفعل في المرحلة الثانية ، فاستخدم ServerRequest. من الواضح ، أليس كذلك؟

كيف تكون


يوجد عدد قليل من هذه الأمثلة ؛ لن نقوم بتصنيفها بالكامل. ماذا تفعل حيال ذلك؟ بادئ ذي بدء ، من المفيد إلقاء نظرة على دليل ترحيل Spring Boot 2.0 من أجل ملاحظة التغيير الذي يتعلق بك في الوقت المناسب. على سبيل المثال ، يذكر إعادة تسمية الطبقات غير الواضحة تمامًا. ومع ذلك ، إذا انفصل شيء ما وكسر ، فإنه يجدر النظر إلى أن مفهوم "الويب" ينقسم إلى 2: "servlet" و "reactive". مع الاتجاه في جميع الفئات والحزم ، وهذا يمكن أن يساعد. بالإضافة إلى ذلك ، ينبغي ألا يغيب عن الأذهان أنه لم تتم إعادة تسمية الفئات والحزم نفسها فحسب ، ولكن أيضًا التبعيات والتحف الكاملة. لأن هذا ، على سبيل المثال ، حدث مع Spring Cloud.



نوع المحتوى. تحديد نوع استجابة HTTP


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



ليس سراً أن Spring يمكنه كتابة تطبيقات الويب ، واجهات برمجة التطبيقات للصفحة و REST ، ويمكنه تقديم محتوى بمجموعة واسعة من الأنواع ، سواء أكانت XML أو JSON أو أي شيء آخر. وأحد السحر التي يحبها Spring هو أنك لست مضطرًا لأن تهتم بتعريف النوع الوارد في الكود. يمكنك أن تأمل في السحر. يعمل هذا السحر ، نسبياً ، بثلاث طرق مختلفة: إما أن يعتمد على رأس Accept الذي جاء من العميل ، أو على امتداد الملف المطلوب ، أو على معلمة خاصة في عنوان URL ، والتي ، بالطبع ، يمكن توجيهها أيضًا.

النظر في مثال بسيط ( شفرة المصدر الكامل ). فيما يلي ، سأستخدم الترميز من Gradle ، لكن حتى لو كنت من محبي Maven ، فلن يكون من الصعب عليك فهم ما هو مكتوب هنا: نحن نبني تطبيقًا صغيرًا على أول Spring Boot ونستخدم شبكة بداية واحدة فقط.

مثال (v1.x):

 dependencies { ext { springBootVersion = '1.5.14.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") } 

كرمز قابل للتنفيذ ، لدينا فئة واحدة يتم فيها الإعلان عن طريقة التحكم فورًا.

 @GetMapping(value = "/download/{fileName: .+}", produces = {TEXT_HTML_VALUE, APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE}) public ResponseEntity<Resource> download(@PathVariable String fileName) { //   ,  Content-Type } 

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



بشكل عام ، يمكنك حتى محاولة القيام بذلك. في الواقع ، إذا طلبنا المستند نفسه بملحقات مختلفة ، فسيتم إرجاعه بنوع المحتوى الصحيح وفقًا لما نعود إليه: إذا كنت تريد - json ، إذا كنت تريد - txt ، إذا كنت تريد - html. وهي تعمل مثل خرافة.

تحديث إلى v2.x


 dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") } 

لقد حان الوقت للترقية إلى الحذاء الربيعي الثاني. نحن فقط نغير الرقم 1 إلى 2.



الربيع MVC مسار مطابقة تغيير السلوك الافتراضي

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



لذلك ، نحن يسجل ، والتحقق من الانفجار! - فجأة لا يعمل. لسبب ما ، يبدأ فقط تقديم النص / html في كل مكان ، وإذا قمت بحفره ، فهو ليس مجرد نص / html ، ولكن فقط من الأنواع التي حددتها في سمة "إنتاج" على تعليق توضيحيGetMapping. لماذا هذا يبدو ، بعبارة ملطفة ، غير مفهومة.



وهنا ، لن تساعد ملاحظات الإصدار ، عليك قراءة المصدر.

ContentNegotiationManagerFactoryBean


 public ContentNegotiationManagerFactoryBean build() { List<ContentNegotiationStrategy> strategies = new ArrayList<>(); if (this.strategies != null) { strategies.addAll(this.strategies); } else { if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy; // … 

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



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



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

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

الشيء الوحيد الذي يستحق القيام به في الحال ، لتوضيح ضميرك ، هو النظر في وثائق Spring Boot فقط لمعرفة أي إشارة إلى هذه العلامة. وهناك ذكره حقًا ، ولكن فقط في سياق غريب:
إذا فهمت التحذيرات وكنت لا تزال ترغب في استخدام التطبيق الخاص بك لمطابقة النقش اللاحق ، فإن التكوين التالي مطلوب:
spring.mvc.contentnegotiation.favor-path-extension = true
...
يقولون أنه مكتوب إذا فهمت كل الحيل وما زلت ترغب في استخدام مطابقة المسار اللاحق ، ثم حدد هذا المربع. تشعر التناقض؟ يبدو أننا نتحدث عن تعريف نوع المحتوى في سياق هذه العلامة ، ولكن هنا نتحدث عن مطابقة أساليب Java وعناوين URL. يبدو بطريقة ما غير مفهومة.

لدينا لمزيد من الحفر. يوجد طلب سحب على جيثب:



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

بالطبع ، يمكنك العثور عليها على الفور في GitHub إذا كنت تعرف فقط المكان الذي تبحث فيه.


مباراة لاحقة

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

تلخيص


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

  • تقليل المخاطر الأمنية (تلك التي سأوضحها) ؛
  • محاذاة سلوك WebFlux و WebMvc ، فقد اختلفا في هذا الجانب ؛
  • محاذاة البيان في الوثائق مع رمز الإطار.

كيف تكون


أولاً ، كلما كان ذلك ممكنًا ، يجب ألا تعتمد على تعريف نوع المحتوى حسب الامتداد. المثال الذي أظهرته مثال مضاد ، لا حاجة للقيام بذلك! بالإضافة إلى أنه ليس من الضروري الاعتماد على حقيقة أن طلبات النموذج "GET something.json" ، على سبيل المثال ، سوف "تحصل على شيء" ببساطة. كان هذا هو الحال في Spring Framework 4 وفي Spring Boot 1. هذا لا يعمل بعد الآن. إذا كنت بحاجة إلى تعيين ملف به الامتداد ، فعليك القيام بذلك بشكل صريح. بدلاً من ذلك ، من الأفضل الاعتماد على رأس قبول أو على معلمة URL ، والتي يمكنك توجيه اسمها. حسنًا ، إذا لم تتمكن من القيام بذلك بأي شكل من الأشكال ، فلنفترض أن لديك بعض عملاء الأجهزة المحمولة القديمة الذين توقفوا عن التحديث في القرن الماضي ، وعليك إعادة هذا العلم وتعيينه إلى "صواب" ، وسيعمل كل شيء كما كان من قبل.

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

الجدولة. المهام المجدولة أو الدورية


دعنا نغير النطاق قليلاً ونتحدث عن إكمال المهام في جدول أو بشكل دوري.

مثال على المهمة. سجل رسالة كل 3 ثوان


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



يمكن القيام بذلك ، من الواضح ، بطرق متنوعة ، فهناك بالفعل شيء ما في فصل الربيع. وتجده - الكثير من الطرق.

الخيار 1: ابحث عن مثال في مشروعك


 /** *A very helpful service */ @Service public class ReallyBusinessService { // … a bunch of methods … @Scheduled(fixedDelay = 3000L) public void runRepeatedlyWithFixedDelay() { assert Runtime.getRuntime().availableProcessors() >= 4; } // … another bunch of methods … } 

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

الخيار 2: البحث عن التعليق التوضيحي المطلوب




يمكنك البحث عن التعليق التوضيحي مباشرةً بالاسم ، وسيكون من الواضح أيضًا من الوثائق التي تعلقها - ويعمل كل شيء.

الخيار 3: غوغلينغ


إذا لم يكن لديك ثقة في نفسك ، يمكنك حينئذٍ استخدام google ، ومن ما وجدته ، سيكون من الواضح أيضًا أن كل شيء سيبدأ بتعليق توضيحي واحد.

 @Component public class EventCreator { private static final Logger LOG = LoggerFactory.getLogger(EventCreator.class); private final EventRepository eventRepository; public EventCreator(final EventRepository eventRepository) { this.eventRepository = eventRepository; } @Scheduled(fixedRate = 1000) public void create() { final LocalDateTime start = LocalDateTime.now(); eventRepository.save( new Event(new EventKey("An event type", start, UUID.randomUUID()), Math.random() * 1000)); LOG.debug("Event created!"); } } 

من يرى الفائدة في هذا؟ نحن مهندسون بعد كل شيء ، لنفحص كيف يعمل هذا في الواقع.

أرني الرمز!


النظر في مهمة محددة (المهمة نفسها والرمز في مستودع بلدي ).

من لا يرغب في القراءة ، يمكنك مشاهدة جزء الفيديو هذا مع عرض توضيحي (حتى الدقيقة 22):


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

 dependencies { ext { springBootVersion = '1.5.14.RELEASE' // springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") compile("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") // +100500      } 

وسوف يكون رمزنا القابل للتنفيذ أكثر بساطة.

 package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3     …”); } } 

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

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



حان الوقت فقط للترقية إلى الحذاء الربيعي الثاني. لن نزعج ، فقط قم بالتبديل من واحد إلى آخر:

 dependencies { ext { // springBootVersion = '1.5.14.RELEASE' springBootVersion = '2.0.4.RELEASE' } 

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

نبدأ. بادئ ذي بدء ، نحن مقتنعون بأننا نعمل على برنامج Spring Spring الثاني ، وإلا لن يكون هناك أي انحرافات.



ومع ذلك ، 3 ثوان تمر ، 6 ، 9 ، ولكن لا يوجد حتى الآن هيرمان - لا يوجد استنتاج ، لا شيء يعمل.
كما يحدث في كثير من الأحيان ، فإن التوقع يتعارض مع الواقع. غالبًا ما يكتبون إلينا في الوثائق التي ، في الواقع ، كل شيء يعمل خارج الصندوق في Spring Boot ، وبصفة عامة يمكننا ببساطة أن نبدأ كما هو مع الحد الأدنى من المشاكل ، وليس هناك حاجة للتهيئة. ولكن بمجرد أن يصل الأمر إلى الواقع ، غالبًا ما يتبين أنه لا يزال يتعين على المرء قراءة الوثائق. على وجه الخصوص ، إذا حفرت بعمق ، يمكنك أن تجد هنا الخطوط التالية:
7.3.1. تمكين جدولة التعليقات التوضيحية
لتمكين دعم التعليقات التوضيحيةScheduled و Async ، يمكنك إضافةEnableScheduling وEnableAsync إلى أحد فصولConfiguration الخاصة بك.
لكي يعمل التعليق التوضيحي المجدول ، تحتاج إلى تعليق تعليق توضيحي آخر على الفصل مع تعليق توضيحي آخر. حسنا ، كالعادة في الربيع. ولكن لماذا كان يعمل من قبل؟ لم نفعل أي شيء من هذا القبيل. من الواضح أن هذا التعليق التوضيحي معلق في مكان ما في وقت مبكر من التمهيد الأول في الربيع ، لكن الآن لسبب ما لم يكن في الثانية.



نبدأ في البحث عن طريق أكواد مصدر أول Spring Spring. نجد أن هناك بعض الصفات التي من المفترض أن يتم تعليقها. نحن ننظر عن كثب ، ويطلق عليه "MetricExportAutoConfiguration" ، وعلى ما يبدو ، هو المسؤول عن تقديم هذه المقاييس الأداء في الخارج ، لبعض المجمعات المركزية ، ولها حقا هذا التعليق التوضيحي.



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



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

من لا يريد القراءة ، راجع عرضًا تجريبيًا قصيرًا لمدة 30 ثانية:


ويترتب على ذلك أنه إذا أخذنا الآن التعليق التوضيحي المفقود في فلسفتنا الأصلية وعلقناه يدويًا ، فمن الناحية النظرية ، يجب أن يصبح السلوك صحيحًا.

 package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication @EnableScheduling public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3     …”); } } 

هل تعتقد أنها ستعمل؟ دعونا تحقق. نبدأ.



يمكن ملاحظة أنه بعد 3 ثوانٍ ، بعد 6 ثوانٍ وبعد 9 ، لا تزال الرسالة التي نتوقعها معروضة في السجل.

كيف تكون


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

ثانياً ، تذكر أنه في برنامج Spring Boot ، على الرغم من أن العديد من الميزات غير متوفرة (الجدولة ، المزامنة ، التخزين المؤقت ، ...) ، فهي غير مضمنة دائمًا ، يجب تمكينها بشكل صريح.

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

على سبيل المثال ، تحتويEnableAsync و Enable Caching على سمات تتحكم ، على وجه الخصوص ، في أي وضع سيتم تحويل الفاصوليا من أجل تنفيذ الوظيفة المقابلة. لذلك ، يمكنك عن طريق الخطأ تعيين هذه التعليقات التوضيحية في مكانين بقيم سمات مختلفة. ماذا سيحدث في هذه الحالة؟ جزئيًا ، يجيب javadoc على هذا السؤال لأحد الفئات التي تشارك فقط في هذه الوظيفة. يقول أن هذا المسجل يعمل من خلال البحث عن أقرب تعليق توضيحي. إنه يعلم أن هناك العديد من ممكن تمكين *, , . ? .

Spring Cloud & Co.




Spring Boot 2 , Spring Cloud — Service Discovery ( ). JavaMelody. - . , , JDBC, H2.



, , JavaMelody — , , . dev-, test, - , Prometheus.

Gradle :

 dependencies { ext { springBootVersion = '2.0.4.RELEASE' springCloudVersion = '2.0.1.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") runtime("org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion") runtime group: "org.springframework.cloud", name: "spring-clooud-starter-netflix-eureka-client", version: springCloudVersion runtime("net.bull.javamelody:javamelody-spring-boot-starter:1.72.0") //… } 

( )

Spring Boot — web jdbc, Spring Cloud eureka (, , Service Discovery), JavaMelody. .

 @SpringBootApplication public class HikariJavamelodyDemoApplication { public static void main(String[] args) { SpringApplication.run(HikariJavamelodyDemoApplication.class, args); } } 

نبدأ.



. , , - com.sun.proxy Hikari, HikariDataSource. , Hikari — , Tomcat, C3P0 .


? .


Spring Cloud dataSource


, Spring Cloud , dataSource ( ), . , AutoRefresh RefreshScope — . . CGLIB.

, , Spring Boot Spring : JDK ( , ) CGLIB ( ). BeanPostProcessor' BeanDefinition , .

JavaMelody dataSource


— JavaMelody. DataSource , , . JavaMelody JDK-, , . — BeanPostProcessor.

, , DataSource JDK-, CGLIB-. :



. , .

Spring Boot dataSource.unwrap()


Spring Boot, DataSource#unwrap(), JMX. JDK- ( ), CGLIB-, Spring Cloud, Spring Context. , , JDK-, CGLIB API .

, :


https://jira.spring.io/browse/SPR-17381

, , , . , , , , - .



. Hikari?

, Hikari - , Spring Cloud . : Hikari Spring Boot 2. ? - - . , Spring Cloud? , - , ? . , .


 org.springframework.cloud.autoconfigure.RefreshAutoConfiguration .RefreshScopeBeanDefinitionEnhancer: /** * Class names for beans to post process into refresh scope. Useful when you * don't control the bean definition (eg it came from auto-configuration). */ private Set<String> refreshables = new HashSet<>( Arrays.asList("com.zaxxer.hikari.HikariDataSource")); 

Spring Cloud autoconfiguration, Enhancer BeanDefinition', , Hikari. Spring Cloud . .

? Spring Cloud , CGLIB-. , , , , - . (jira.spring.io/browse/SPR-17381). BeanPostProcessor, . BeanDefinition , BeanPostProcessor'. Stack Overflow , - , , proxyTargetClass true false , . , . .

, - , .

:

  • (, Tomcat JDBC Pool)
    spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource

    runtime 'org.apache.tomcat:tomcat-jdbc:8.5.29'
    Hikari , , , , , Tomcat, Spring Boot.
  • JavaMelody, JDBC-, .
    javamelody.excluded-datasources=scopedTarget.dataSource
  • Spring Cloud.
    spring.cloud.refresh.enabled=false
    , , , Service Discovery, .


. , .

( *)


* Spring Cloud ( JavaMelody)

 @Component @ManagedResource @EnableAsync public class MyJmxResource { @ManagedOperation @Async public void launchLongLastingJob() { // -   } } 

: github.com/toparvion/joker-2018-samples/tree/master/jmx-resource .

. , Spring Cloud. JavaMelody , Spring-, . , , , JMX . - , Async, JMX- . JMX, @ManagedOperation, , ( Spring — , OK).

, , , , , myJMXResource JMX, . , — , CGLIB JDK.



JDK CGLIB-. , - BeanPostProcessor.

, BeanPostProcessor':
AsyncAnnotationBeanPostProcessor

  • : Async
  • : org.springframework.scheduling
  • : @EnableAsync ( Import )

2. DefaultAdvisorAutoProxyCreator
  • : AOP-,
  • : org.springframework.aop.framework.autoproxy
  • : @Configuration- PointcutAdvisorConfig ( )

DefaultAdvisorAutoProxyCreator @Configuration-. , , JavaMelody, configuration-. , PointcutAdvisorConfig, .



, . PointcutAdvisorConfig, AdvisorConfig, , configuration-, , , , , .

, , , , -.



BeanPostProcessor'. , , , BeanPostProcessor . , Advised ( BeanPostProcessor'), , , , , , JDK-, . .

. , :



JMX . BeanPostProcessor. BeanPostProcessor', , , , , JAR, , .

كيف تكون


-, , Spring AOP, , . « »? , - Advice Advisor, , .

-, best practices. , JMX- , . - , , , . , autowire' () . . , , - . Order , . , , , .. proxyTargetClass, .

: , . -, «Keep calm and YAGNI». , . « », - , - , , , . , , : -, , — , . . , Spring , , . tolkkv , , 436- , . , .

Relax Binding. ()


, .


https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-external-config-relaxed-binding

, Relax Binding Spring Boot. - . , - firstName , acme.my-project.person, Spring Boot . : camel case, , , - — firstName. Relax Binding.

Spring Boot' , , — . , , , :


, , . - . , .

على سبيل المثال:

 dependencies { ext { springBootVersion = '1.5.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") } 

( )

- , web, Spring Boot, - .

 @SpringBootApplication public class RelaxBindingApplication implements ApplicationRunner { private static final Logger log = LoggerFactory.getLogger(RelaxBindingDemoApplication.class); @Autowired private SecurityProperties securityProperties; public static void main(String[] args) { SpringApplication.run(RelaxBindingDemoApplication.class, args); } @Override public void main run(ApplicationArguments args) { log.info("KEYSTORE TYPE IS: {}", securityProperties.getKeyStoreType()); } } 

, POJO- ( ) , KEYSTORE TYPE. POJO , applications.properties application.yaml, .

keystoreType, private String keystoreType, applications.properties: security.keystoreType=jks.

 @Component @ConfigurationProperties(prefix = "security") public class SecurityProperties { private String keystorePath; private String keystoreType; public String getKeystorePath() { return keystorePath; } public void setKeystorePath(String keystorePath) { this.keystorePath = keystorePath; } public String getKeyStoreType() { return keystoreType; } public void setKeystoreType(String keystoreType) { this.keystoreType = keystoreType; } } 

Spring Boot .



, , , . , .



, . , , , - , , - key-store-type. , , , .

. , .



. 2 , , . Java properties — , . , , , , , . — Java bean . , , . , «keystore» , : «Key» «Store». …



, , ? .

, , Relax Binding ( getStoreType()). , . , . , keyStoreType, . , Relax Binding, , , .

, - , - , . , . :



, - , , , , -, . .

كيف تكون


: - . -, , dev- , , YAML properties, — , , . -, c , Relax Binding, . , , , Spring Boot .

Unit Testing. Mockito 2


, - , Mockito.

Mockito , Spring Boot Starter, Spring, Mockito.

 $gradle -q dependencyInsight --configuration testCompile --dependency mockito org.mockito:mockito-core:2.15.0 variant "runtime" /--- org.springframework.boot:spring-boot-starter-test:2.0.2.RELEASE /---testCompile 

? . Spring Boot Mockito , 1.5.2 Spring Boot Mockito 2, . - . Mockito 2.

Mockito , 2016- Mockito 2.0 Mockito.2.1— : Java 8 , Hamcrest - . , , .

, , ( ) .



, , JButton Swing, null, , - . , string' null, , null instanceof string. , Mockito 1 , Mockito 2 , anyString null , , . : null, . , , , Mockito 1.

, , .

 public class MyService { public void setTarget(Object target) { //… } } <hr/> @Test public void testAnyStringMatcher() { MyService myServiceMock = mock(MyService.class); myServiceMock.setTarget(new JButton()); verify(myServiceMock).setTarget(anyString()); } 

, , . JButton. anyString. , : , — . , . - 10- , . Mockito 1 , :



Mockito 2 , , , anyString :



. , . , , , , SocketTimeoutException, - . , SocketTimeoutException , . Mockito 1.



Mockito 2 , :



, Mockito 1 , , . new SocketTimeoutException new, constructor, Mockito 1 .

ماذا تفعل حيال ذلك؟ , , RuntimeException, , Mockito .

. , . compile-time. - , Hamcrest. Spring Boot, Mockito 1 @MockBean @SpyBean. , Spring Integration, review.
(: https://docs.spring.io/spring-integration/docs/5.0.0.RELEASE/reference/htmlsingle/#testing )

كيف تكون


, , Mockito 1, Mockito 2 ( dzone.com/refcardz/mockito ).

-, , , Spring Boot 1.5.2 Mockito 2.

-, , , Mockito 2, : , .

Gradle Plugin. Spring Boot


, , — Spring Boot- Gradle.

Migration Guide , Spring Boot Gradle . , . : Gradle 4 (, settings.gradle ). dependency management plugin, . bootRepackage, , : bootWar bootJar. bootJar .

bootJar:

  • , org.springframework.boot java;
  • jar;
  • mainClassName ( ) ( , - ).

, , — , , Gradle, Spring Boot.

? - Spring Boot 2, , , Gradle 4 Spring Boot-. , , : , , ( , ).



, . app1 , app2, app3 . . app1 lib.

«Show me the code!»




 subprojects { repositories { mavenCentral() } apply plugin: 'java' apply plugin: 'org.springframework.boot' } 

— : java Spring Boot .

, , , . , , , . .

app1:

 dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") compile project(':lib') } 

lib , Spring Boot-.

app1:

 @SpringBootApplication public class GradlePluginDemoApplication implements ApplicationRunner { //… @Override public void run(ApplicationArguments args) { String appVersion = Util.getAppVersion(getClass()); log.info("Current application version: {}", appVersion); } } 

Util, .

lib:

 public abstract class Util { public static String getAppVersion(Class<?> appClass) { return appClass.getPackage().getImplementationVersion(); } } 

Util getAppVersion , , ImplementationVersion . .



IDE, , , . gradle build IDE , . Util. , , , , .


:

  • bootJar jar;
  • Gradle jar.

:
  • ;
  • , jar ( ImplementationVersion), .

? : .



Spring Boot- , , lib . .

2: SB Gradle Plugin Spring Boot-

 bootJar { enabled = false } 

, - , , , , , bootJar . , jar , .

أخرى


, , , Spring Boot. .



Spring Boot : web-, , . - , , Spring Boot properties migrator, , , . , , -, .

Actuator. , () , Spring Security. .



Spring Cloud . , . , Netflix Feign.



Spring Integration, , Spring Framework, . — , Java DSL , , . , , , , handle handleWithAdapter.

.


, , :



, , , Web, .

(Properties Binding), , , Relax Binding.

— , : , AOP , , Spring Boot 2 .

, , , — , Mockito 1 Mockito 2. - , ?


-, , , , YAGNI. , - , . , , .

-, - - , , , . , , , , . Migration Guide. , , , , , .

, Spring Boot. , Spring Boot, , … .
, : 5-6 JPoint , Spring Boot: Spring Boot- Java 8 Java 11. — .

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


All Articles