تمهيد نفسك ، الربيع قادم (الجزء 1)

يتحدث Evgeny EvgenyBorisov Borisov ( NAYA Technologies) و Kirill tolkkv Tolkachev (Cyan.Finance، Twitter ) عن أهم لحظات Spring Boot وأكثرها إثارة للاهتمام على سبيل المثال في بداية لبنك حديد وهمي.



يعتمد المقال على تقرير يوجين وسيريل من مؤتمر جوكر 2017. تحت المقطع يوجد نص الفيديو ونص التقرير.


يتم رعاية مؤتمر جوكر من قبل العديد من البنوك ، لذلك دعونا نتخيل أن التطبيق الذي سنقوم بدراسة عمل Boot Spring والمبدئ الذي نقوم بإنشائه مرتبط بالبنك.



لذا ، لنفترض أنه تم تلقي طلب للحصول على طلب من البنك الحديدي في Braavos. ببساطة يقوم البنك العادي بتحويل الأموال ذهابا وإيابا. على سبيل المثال ، مثل هذا (لدينا API لهذا):

http://localhost:8080/credit\?name\=Targarian\&amount\=100

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

على سبيل المثال ، إذا حاولنا تحويل الأموال إلى Targaryen ، فستتم الموافقة على العملية:



ولكن إذا كان ستارك ، فلا:



لا عجب: ستاركس يموت في كثير من الأحيان. لماذا تحويل الأموال إذا لم ينجو الشخص من الشتاء؟

دعونا نرى كيف تبدو في الداخل.

 @RestController @RequiredArgsConstructor public class IronBankController { private final TransferMoneyService transferMoney; private final MoneyDao moneyDao; @GetMapping("/credit") public String credit(@RequestParam String name, @RequestParam long amount) {   long resultedDeposit = transferMoney.transfer(name, amount);   if (resultedDeposit == -1) {     return "Rejected<br/>" + name + " <b>will`t</b> survive this winter";   }   return format(       "<i>Credit approved for %s</i> <br/>Current  bank balance: <b>%s</b>",       name,       resultedDeposit   ); } @GetMapping("/state") public long currentState() {   return moneyDao.findAll().get(0).getTotalAmount(); } } 

هذا هو وحدة تحكم سلسلة عادية.

من المسؤول عن منطق الاختيار ، ولمن يمنح القرض ، ولمن؟ خط بسيط: إذا كان اسمك Stark ، فإننا بالتأكيد لا نخون. في حالات أخرى - كم هو محظوظ. بنك عادي.

 @Service public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) {   return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } } 

كل شيء آخر ليس مثيرا للاهتمام. هذه بعض التعليقات التوضيحية التي تؤدي كل العمل لنا. كل شيء سريع جدا.

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

 public interface MoneyDao extends JpaRepository<Bank, String> { } 

في الخدمات - فقط خدمات الترجمة والتنبؤ التي يمكنك إصدارها لها. لا يوجد دليل Conf. في الواقع ، لدينا فقط application.yml (قائمة بأولئك الذين يسددون الديون). والأهم هو الأكثر شيوعًا:

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

فأين كل السحر مخفي؟

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

التبعيات


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

يعمل Spring Boot على حل مشكلة تعارض الإصدار.

كيف نحصل عادةً على مشروع Spring Boot (إذا لم نصل إلى مكان يوجد فيه بالفعل)؟

  • أو انتقل إلى start.spring.io ، ضع مربعات الاختيار التي علمنا جوش لونغ تعيينها ، انقر فوق تنزيل المشروع وافتح المشروع حيث يوجد كل شيء بالفعل ؛
  • أو استخدم IntelliJ ، حيث بفضل الخيار الظاهر ، يمكن تعيين خانات الاختيار في Spring Initializer مباشرة من هناك.

إذا عملنا مع Maven ، فسيكون للمشروع pom.xml ، حيث يوجد أحد الوالدين في Boot Spring يسمى Spring spring-boot-dependencies boot- spring-boot-dependencies . سيكون هناك كتلة ضخمة من إدارة التبعية.

لن أخوض في تفاصيل مخضرم الآن. كلمتين فقط.

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

لكن ما هي المشكلة؟ المشكلة هي أنه في شركتي ، على سبيل المثال ، لديّ والدتي الخاصة. إذا كنت أريد استخدام Spring ، فماذا أفعل مع والدتي؟



ليس لدينا العديد من الميراث. نحن نرغب في استخدام بوم لدينا ، والحصول على كتلة إدارة التبعية من الخارج.



يمكن القيام بذلك. يكفي تسجيل استيراد قائمة المواد لكتلة إدارة التبعية.

 <dependencyManagement> <dependencies>    <dependency>       <groupId>io.spring.platform</groupId>       <artifactId>platform-bom</artifactId>       <version>Brussels-SR2</version>       <type>pom</type>       <scope>import</scope>    </dependency> </dependencies> </dependencyManagement> 

من يريد أن يعرف المزيد عن بوم - انظر التقرير " مافن مقابل جرادل ". تم شرح كل ذلك بالتفصيل.

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

وهذه هي الطريقة التي يتم بها في Gradle (كالعادة ، نفس الشيء ، أسهل فقط):

 dependencyManagement { imports {   mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE' } } 

الآن دعونا نتحدث عن التبعيات نفسها.

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

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

على سبيل المثال يبدو pom لدينا على النحو التالي: يحتوي فقط على تلك التبعيات 3-5 التي نحتاجها:

 'org.springframework.boot:spring-boot-starter-web' 'org.springframework.boot:spring-boot-starter-data-jpa' 'com.h2database:h2' 

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

إعداد السياق


لنتحدث عن الألم التالي الذي كان لدينا دائمًا - تحديد السياق. في كل مرة نبدأ فيها في كتابة تطبيق من البداية ، يستغرق الأمر وقتًا طويلاً لتكوين البنية التحتية بالكامل. لقد سجلنا إما في xml أو java config الكثير مما يسمى بقوليات البنية التحتية. إذا عملنا مع السبات ، فنحن بحاجة إلى EntityManagerFactory bean. الكثير من حبوب البنية التحتية - مدير المعاملات ، مصدر البيانات ، إلخ. - كان من الضروري التكيف مع يديك. بطبيعة الحال ، وقعوا جميعًا في السياق.

أثناء تقرير Spring Ripper ، أنشأنا السياق بشكل رئيسي ، وإذا كان سياق XML ، كان فارغًا في البداية. إذا قمنا ببناء السياق من خلال AnnotationConfigApplicationContext ، فهناك بعض المعالجات الفاصوليا التي يمكنها تكوين الفاصوليا وفقًا للتعليقات التوضيحية ، ولكن السياق كان فارغًا أيضًا.
والآن يوجد بشكل رئيسي SpringApplication.run ولا يوجد سياق مرئي:

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

لكن في الواقع لدينا سياق. يعيد SpringApplication.run بعض السياق.


هذه حالة غير نمطية تماما. كان هناك خياران:

  • إذا كان هذا تطبيق سطح مكتب ، بشكل أساسي ، كان عليك كتابة جديد بيديك ، حدد ClassPathXmlApplicationContext ، إلخ.
  • إذا عملنا مع Tomcat ، فقد كان هناك مدير servlet ، والذي ، حسب بعض الاصطلاحات ، بحث عن XML وبنى افتراضيًا سياقًا منه.

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

لغز: ماذا يمكننا أن نمرر هناك؟


معطى:

RipperApplication.class
 public… main(String[] args) {  SpringApplication.run(?,args); } 

السؤال: ماذا يمكن أن ينقل هناك؟

الخيارات:
  1. RipperApplication.class
  2. String.class
  3. "context.xml"
  4. new ClassPathResource("context.xml")
  5. Package.getPackage("conference.spring.boot.ripper")

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



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

الشيء الوحيد الذي لم يتم ذكره في الوثائق هو في أي شكل لإرسالنا هناك. ولكن هذا بالفعل من عالم المعرفة السرية.

 SpringApplication.run(Object[] sources, String[] args) # APPLICATION SETTINGS (SpringApplication) spring.main.sources= # class name, package name, xml location spring.main.web-environment= # true/false spring.main.banner-mode=console # log/off 

SpringApplication مهم حقًا هنا - مزيد من الشرائح التي سنحصل عليها مع كارلسون.

يخلق كارلسون لدينا نوعًا من السياق بناءً على المدخلات التي نمررها إليه. أذكركم ، نمنحه ، على سبيل المثال ، خمسة خيارات رائعة يمكنك من خلالها عمل كل شيء باستخدام SpringApplication.run :

  • RipperApplication.class
  • String.class
  • "context.xml"
  • new ClassPathResource("context.xml")
  • Package.getPackage("conference.spring.boot.ripper")

ماذا يفعل SpringApplication لنا؟



عندما أنشأنا السياق من خلال new رئيسي من خلال new ، كان لدينا الكثير من الفئات المختلفة التي تنفذ واجهة ApplicationContext :



وما هي الخيارات المتاحة عندما يقوم كارلسون ببناء السياق؟

إنه يجعل نوعين فقط من السياق: إما سياق ويب ( WebApplicationContext ) أو سياق عام ( AnnotationConfigApplicationContext ).



يعتمد اختيار السياق على وجود فئتين في مسار الفصل الدراسي:



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

ومع ذلك ، هذا هو التمهيد الربيع. لم نقم بعد بإنشاء حاوية واحدة ، وليس فئة واحدة ، بل لدينا فقط رئيسي ، SpringApplication.run Carlson - SpringApplication.run . عند المدخل ، يتلقى فئة مميزة بنوع من التعليقات التوضيحية لفصل الربيع.

إذا نظرت في هذا السياق ، ماذا سيحدث هناك؟

في تطبيقنا ، بعد توصيل زوج من المبتدئين ، كان هناك 436 حاوية.



ما يقرب من 500 حبة فقط لبدء الكتابة.



بعد ذلك ، سوف نفهم من أين جاءت هذه الحبوب.

لكن بادئ ذي بدء ، نريد أن نفعل نفس الشيء.

إن سحر المبتدئين ، بالإضافة إلى حل جميع مشاكل الإدمان ، هو أننا ربطنا فقط 3-4 مبتدئين ولدينا 436 صندوقًا. سنقوم بتوصيل 10 مبتدئين ، سيكون هناك أكثر من 1000 حاوية ، لأن كل بداية ، باستثناء التبعيات ، تجلب بالفعل التكوينات التي يتم فيها تسجيل بعض الصناديق الضرورية. على سبيل المثال قلت أنك تريد بداية للويب ، لذلك تحتاج إلى مرسل servlet و InternalResourceViewResolver . قمنا بتوصيل مشغل jpa - نحن بحاجة إلى EntityManagerFactory bean. كل هذه الفاصوليا موجودة بالفعل في مكان ما في تكوينات المبتدئين ، وتأتي بشكل سحري إلى التطبيق دون أي إجراء من جانبنا.

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

قانون الحديد 1.1. أرسل غرابًا دائمًا




دعونا نلقي نظرة على متطلبات العميل. لدى بنك الحديد العديد من التطبيقات المختلفة التي تعمل في فروع مختلفة. يرغب العملاء في إرسال غراب في كل مرة يرتفع فيها التطبيق - وهي معلومات تفيد بارتفاع التطبيق.

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

نصنع مستمعًا يستمع للسياق ليتم تحديثه (الحدث الأخير) ، وبعد ذلك يرسل الغراب. سنستمع إلى ContextRefreshEvent .

 public class IronListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(" ..."); } } 


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

 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


السؤال الذي يطرح نفسه: كيفية جعل تهيئة المبدئ لدينا تتناسب تلقائيًا مع جميع التطبيقات التي تستخدم هذا المبدئ؟

لجميع المناسبات هناك "تمكين شيء".



حقًا ، إذا كنت أعتمد على 20 مبتدئًا ، فسيتعين علي وضع @Enable ؟ وإذا كان للمبتدئ عدة تكوينات؟ سيتم تعليق فئة التكوين الرئيسية باستخدام @Enable* ، كيف تكون شجرة العام الجديد؟



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



إذن ما هو spring.factories


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



وهكذا نحصل على انقلاب السيطرة ، الذي نحتاجه.



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

 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ironbank.IronConfiguration 


 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


كل ما تبقى لنا الآن هو ربط المشغل في المشروع.

 compile project(':iron-starter') 


في مخضرم ، بالمثل - تحتاج إلى تسجيل التبعية.

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

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

SpringBootApplication حول الرأس


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

 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { … } 

أولاً ، في الداخل يوجد @Configuration ، أي أنه تكوين. يمكنك كتابة @Bean ، وكالعادة ، قم بتسجيل الفول.

ثانياً ، يقف @ComponentScan فوقه. بشكل افتراضي ، يقوم بمسح جميع الحزم والعبوات الفرعية. وفقًا لذلك ، إذا بدأت في إنشاء خدمات في نفس الحزمة أو في @Service ، @RestController - يتم مسحها تلقائيًا ، نظرًا لأن التكوين الرئيسي يبدأ عملية المسح.

في الواقع ، @SpringBootApplication لا يفعل شيئًا جديدًا. لقد قام ببساطة بتجميع أفضل الممارسات التي كانت في تطبيقات Spring ، لذا فهذا الآن نوع من تكوين التعليقات التوضيحية ، بما في ذلك @ComponentScan .

بالإضافة إلى ذلك ، لا تزال هناك أشياء لم تكن موجودة من قبل @EnableAutoConfiguration . هذه هي الفئة التي وصفتها في مصانع الربيع.
@EnableAutoConfiguration ، إذا نظرتم ، يحمل @Import معه:

 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";  Class<?>[] exclude() default {};  String[] excludeName() default {}; } 

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

تحتاج إلى الانتباه إلى هذه الفئة. ينتهي بـ ImportSelector . في الربيع العادي ، نكتب Import(Some Configuration.class) لبعض التكوينات ويتم تحميلها ، مثل جميع المعالين. هذا هو ImportSelector ، هذا ليس تكوينًا. ImportSelector كل مبتدئين إلى السياق. يقوم بمعالجة التعليق التوضيحي @EnableAutoConfiguration من spring.factories ، الذي يحدد التكوينات المطلوب تحميلها ، ويضيف الفاصوليا التي حددناها في IronConfiguration إلى السياق.



كيف يفعل ذلك؟

بادئ ذي بدء ، فإنه يستخدم فئة فائدة مباشرة ، SpringFactoriesLoader ، والتي تبحث في spring.factories وتحميل كل شيء من ما يطلب. لديه طريقتان ، لكنهما ليستا مختلفتين كثيرًا.



توجد مصانع تحميل Spring Spring في Spring 3.2 ، ولكن لم يستخدمها أحد. يبدو أنه كتب كتطور محتمل للإطار. وهكذا نمت إلى Spring Boot ، حيث توجد العديد من الآليات التي تستخدم اصطلاحات spring.factories. سنوضح أيضًا أنه بالإضافة إلى التكوين ، يمكنك أيضًا الكتابة في Spring.factories - المستمعين والمعالجات غير العادية ، إلخ.

 static <T> List<T> loadFactories( Class<T> factoryClass, ClassLoader cl ) static List<String> loadFactoryNames( Class<?> factoryClass, ClassLoader cl ) 

هذه هي طريقة عمل عكس التحكم. open closed principle, - - . ( , ). , spring.factories. , . Spring Boot , , spring.factories.

. , Spring, , , , org.springframework.boot:spring-boot-autoconfigure , META-INF/spring.factories EnableAutoConfiguration ، ولديها الكثير من التكوينات (في المرة الأخيرة التي نظرت فيها ، كان هناك حوالي 80 تكوينًا تلقائيًا غير متصل).

 spring-boot-autoconfigure.jar/spring.factories</b> org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.\ ... 

هذا ، بغض النظر عما إذا قمت بتوصيل المبتدئ أو عدم الاتصال ، عندما أعمل مع Spring Boot ، سيكون هناك دائمًا واحد من jar-s (jar of Spring Boot نفسه) ، حيث توجد مصانع Spring.Factories الخاصة بها ، حيث يتم كتابة 90 تكوينًا. يمكن أن تحتوي كل من هذه التكوينات على العديد من التكوينات الأخرى ، على سبيل المثال CacheAutoConfiguration، تحتوي على شيء من هذا القبيل - شيء أردنا الابتعاد عنه:

 for (int i = 0; i < types.length; i++) { Imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; 

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

 private static final Map<CacheType, Class<?>> MAPPINGS; static { Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>(); mappings.put(CacheType.GENERIC,    GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE,    EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST,  HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE,     JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE,  CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS,      RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE,   CaffeineCacheConfiguration.class); addGuavaMapping(mappings); mappings.put(CacheType.SIMPLE,     SimpleCacheConfiguration.class); mappings.put(CacheType.NONE,       NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); } 

, .



. :



. — , , , open closed principle — spring.factories, . , - .

, Spring Boot, — 90 . 30 , Spring Boot.

, . 2013 حول ما هو جديد في Spring 4 ، حيث قيل أن التعليق التوضيحي ظهر @Conditional، مما يجعل من الممكن كتابة الشروط في التعليقات التوضيحية التي تشير إلى الفصول التي تعود trueأو false. اعتمادًا على ذلك ، يتم إنشاء الفاصوليا أم لا. نظرًا لأن تكوين جافا في Spring هو أيضًا فول ، يمكنك أيضًا تعيين شرطي مختلف هناك. وبالتالي ، يتم النظر في التكوينات ، ولكن إذا تم إرجاعها المشروطة false، فسيتم تجاهلها.

ولكن هناك فروق دقيقة. بادئ ذي بدء ، يؤدي هذا إلى موقف قد تكون فيه الحاوية أو لا تكون ، اعتمادًا على بعض إعدادات البيئة.



اعتبر هذا كمثال.

قانون الحديد 1.2. الغراب فقط في الإنتاج


العميل لديه متطلبات جديدة. الغراب شيء باهظ الثمن ، ليس هناك الكثير منهم. لذلك ، لا يجب إطلاقها إلا إذا علمنا أن الإنتاج قد ارتفع.



وبناءً على ذلك ، يجب إنشاء المستمع الذي يطلق الغراب فقط إذا كان الإنتاج. دعونا نحاول القيام بذلك.

ندخل في التكوين ونكتب:

 @Configuration <b>@ConditionalOnProduction</b> public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 

كيف نقرر ما إذا كان الإنتاج أم لا؟ كان لدي شركة غريبة قالت: "إذا كان Windows على الجهاز ، فهذا يعني عدم الإنتاج ، ولكن إذا لم يكن Windows ، فحينئذٍ الإنتاج". لكل فرد مشروط خاص به.

على وجه التحديد ، قال البنك الحديدي إنه يريد إدارة ذلك يدويًا: عندما ترتفع الخدمة ، يجب أن تنبثق نافذة منبثقة: "الإنتاج أم لا". لا يتم توفير مثل هذا الشرط في Spring Boot.

 @Retention(RUNTIME) @Conditional(OnProductionCondition.class) public @interface ConditionalOnProduction { } 

نحن نصنع بوبابًا قديمًا جيدًا:

 public class OnProductionCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {   return JOptionPane.showConfirmDialog(parentComponent: null, " ?") == 0; } } 

لنجربها.



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

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

Pazzler


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

سؤال: المنطق الذي يكتشف ما إذا كان الإنتاج أم لا ، كم مرة يجب أن يعمل؟
ما الفرق يعمل؟ حسنًا ، ربما هذا المنطق مكلف ، ويستغرق الوقت ، والوقت هو المال.

للتوضيح ، توصلنا إلى مثال:

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

: , . - — , .

. — , , .



, ( OnProductionCondition.class , — ). . , , , - . 5 ?

— 300 400. - . , , . — .

. ( @Component , @Configuration @Service ), . , .

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } 

, .

 @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

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

السؤال الذي يطرح نفسه: كيف يعمل هذا ولماذا هو هكذا؟ وإجابتي هي فقط:



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

قانون الحديد 1.3. الغراب في


نواصل تطوير كاتبنا. يجب علينا بطريقة أو بأخرى تحديد رحلة الغراب.



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

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

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

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

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

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

في الواقع ، هناك أشياء هنا ستساعدنا. بادئ ذي بدء @ConditionalOnProperty. هذا شرط يعمل إذا كانت هناك خاصية أو خاصية معينة مع بعض القيمة المحددة في application.yml. وبالمثل ، علينا @ConfigurationalPropertyأن نجعل الإكمال التلقائي.

الإكمال التلقائي


يجب أن نتأكد من أن جميع الخصائص تبدأ في الإكمال التلقائي. سيكون من الجيد إذا كان هذا سيتم الإكمال التلقائي ليس فقط بين الأشخاص الذين سيقومون بتسجيلهم في تطبيقهم .yml ، ولكن أيضًا في بدايتنا.

دعونا نسمي ممتلكاتنا "الغراب". يجب أن يعرف إلى أين يطير.

 @ConfigurationProperties("") public class RavenProperties { List<String> ; } 

تخبرنا IDEA أن هناك خطأ ما هنا:



تقول الوثائق أننا لم نقم بإضافة تبعية (في Maven لن تكون هناك إشارة إلى الوثائق ، ولكن زر "إضافة تبعية"). فقط أضفه إلى مشروعك.

 subproject { dependencies { compileOnly 'org.springframework.boot:spring-boot-configuration-processor' compile 'org.springframework.boot: spring-boot-starter' } } 

الآن ، وفقًا لـ IDEA ، لدينا كل شيء.

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

من أين تأتي الإكمال التلقائي على الخاصية الموجودة في خصائص التطبيق؟ هناك ملف JSON يمكن لـ IDEA العمل معه. يصف هذا الملف جميع الخصائص التي يجب أن تكون IDEA قادرة على التحويل البرمجي لها تلقائيًا. إذا كنت تريد الخاصية التي توصلت إليها في بادئ الأمر ، يمكن لـ IDEA أيضًا الترجمة التلقائية ، فلديك طريقتان:

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

عليك أيضًا أن تتذكر @EnableConfigurationPropertiesأن هذه الفئة تظهر داخل السياق الخاص بك كحبة فول.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction public RavenListener ravenListener() { return new RavenListener(); } } 

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

ونتيجة لذلك ، كان من الضروري وضع تعليقين:

  • @EnableConfigurationProperties من خلال تحديد خصائص من ؛
  • @ConfigurationalProperties نقول ما البادئة.

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

ونتيجة لذلك ، لدينا ملف يمكن كتابته يدويًا من حيث المبدأ. لكن لا أحد يحب الكتابة يدويًا.

 { "hints": [], "groups": [ { "sourceType": "com.ironbank.RavenProperties", "name": "", "type": "com.ironbankRavenProperties" } ], "properties": [ { "sourceType": "com.ironbank.RavenProperties", "name": ".", "type": "java.util.List<java.lang.String>" } ] } 

عنوان الغراب


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

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener() { return new RavenListener(); } } 


أضفنا شرطًا آخر - يجب إنشاء الغراب فقط بشرط أن يخبر شخص ما في مكان ما بالطائرة.
الآن سنكتب إلى أين تطير في application.yml.

 spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---:   -  : -: ,   : true 

يبقى أن يصف المنطق أنه يطير إلى حيث قيل له.

للقيام بذلك ، يمكننا إنشاء مُنشئ. يحتوي الربيع الجديد على حقن منشئ - هذه هي الطريقة الموصى بها. يحب يوجين أن @Autowiredيظهر كل شيء في التطبيق من خلال التفكير. أحب اتباع الاتفاقيات التي يقدمها الربيع:

 @RequiredArgsConstructor public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

لكنها ليست مجانية. من ناحية تحصل على سلوك يمكن التحقق منه ، ومن ناحية أخرى تحصل على بعض البواسير.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

لا يوجد أي مكان @Aurowired، مع Spring 4.3 لا يمكنك تثبيته. إذا كان هناك منشئ واحد ، فهو كذلك @Aurowired. في هذه الحالة ، يتم استخدام تعليق توضيحي @RequiredArgsConstructor، والذي يولد منشئ واحد. هذا يعادل هذا السلوك:

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

Spring Lombok. Jurgen Holler, 2002 80% Spring, @Aurowired , ( ).

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Aurowired public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

? RavenProperties Java-. @Aurowired , .

, . , , , .

1.4.




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

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

لقد سجلت شيئًا بنفسي ، وأحضر لي المبتدئ مصدر البيانات الخاص بي. هل لدي اثنان الآن؟ أم سوف يسحق واحد (وأي واحد؟)

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

هناك عدد كبير من الشروط التي تم توفيرها لنا بالفعل:

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

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

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnMissingBean</b> public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

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

عند محاولة إطلاق الغراب لم يذهب ، لكن الحدث ظهر ، والذي كتبناه في مستمعنا الجديد - MyRavenListener.



هناك نقطتان مهمتان هنا.
النقطة الأولى هي أننا تم ربطنا من مستمعنا الحالي ، ولم نكتب أي مستمع هناك:

 @Component public class MyRavenListener implements ApplicationListener { public MyRavenListener(RavenProperties ravenProperties) { super(ravenProperties); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println("event = " + event); }); } } 

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

إذا قمت بتنظيف extendsومستمع للتطبيق ، @ConditionalOnMissingBeanفلن تعمل. لكن منذ ذلك الحينيُدعى الفصل أيضًا ، عندما نحاول إنشاءه ، يمكننا الكتابة ravenListener- تمامًا كما فعلنا في تكويننا. أعلاه ، ركزنا على حقيقة أن اسم الفول في تكوين جافا سيكون باسم الطريقة. وفي هذه الحالة ، نقوم بإنشاء صندوق يسمى ravenListener.

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

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

شرطي OnPuzzler


لدينا @ConditionalOnClass، @ConditionalOnMissingBeanقد يكون هناك كتابة فصول. كمثال ، خذ بعين الاعتبار تكوين التنفيذ.

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

 @Configuration public class  { @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new ("..."); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  c() { return new (" "); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new (" "); } } 

كيف سننفذ؟

سؤال: كيف @ConditionalOnMissingClassتعمل كتابة التعليقات التوضيحية بشكل عام ؟

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

خيارات الإجابة:
  • ClassDefNotFound? , . - , ClassDefNotFound , reflection- , conditional as long as;
  • , . reflection . , .
  • ;
  • .

: , . reflection . exception, , — . reflection? , , , , — ClassDefNotFound .

ASM. , reflection — , Spring -, , . , , , @Conditional , . . ASM — , , . , , .

Juergen Hoeller , , , OnMissingClass , (String). , , ASM . , , .

1.5.




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

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

لذلك ، سنفعل كل شيء من خلال @ConditionalOnProperty(".").

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

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

لدينا طرق لهذا التعليق التوضيحي ، هناك سلسلة ، وهذا صفيف - يمكنك تحديد العديد من الخصائص هناك.

 @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; boolean relaxedNames() default true; } 

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

أي أنه لا يمكنك القيام بذلك:

 @ConditionalOnProduction @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".",  havingValue="true") @ConditionalOnProperty(name = ".",havingValue="false") public IronBankApplicationListener applicationListener() { ... } 

 @ConditionalOnProduction @ConditionalOnProperty( name = {    ".",    ".",    "." }, havingValue = "true" ) public IronBankApplicationListener applicationListener() { ... } 

المظلل هنا ليس مصفوفة.

هناك انحراف يسمح لك بالعمل مع العديد من الخصائص ، ولكن بقيمة واحدة فقط لها: AllNestedConditionsو AnyNestedCondition.



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

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnRaven public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

نضع تعليقًا توضيحيًا @Conditional()وعلينا هنا تسجيل فئة.

 @Retention(RUNTIME) @Conditional({OnRavenCondional.class}) public @interface CondionalOnRaven { } 


نحن نصنعه.

 public class OnRavenCondional implements Condition { } 

علاوة على ذلك ، كان علينا تنفيذ نوع من التكييف ، ولكن قد لا نقوم بذلك لأن لدينا التعليق التوضيحي التالي:

 public class CompositeCondition extends AllNestedConditions { @ConditionalOnProperty(  name = ".",  havingValue = "false") public static class OnRavenProperty { } @ConditionalOnProperty(  name = ".enabled",  havingValue = "true",  matchIfMissing = true) public static class OnRavenEnabled { } ... } 

لدينا فئة مركبة Conditional، والتي سيتم توريثها أيضًا من فئة أخرى - إما AllNestedConditions، أو AnyNestedCondition- وستحتوي على فئات أخرى تحتوي على التعليقات التوضيحية المعتادة مع التوابل. على سبيل المثالبدلاً من ذلك @Conditionيجب أن نحدد:

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } } 

في هذه الحالة ، تحتاج إلى إنشاء مُنشئ بالداخل.

الآن علينا إنشاء فصول ثابتة هنا. نحن نصنع نوعًا من الفصل (لنطلق عليه R).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } public static class R {} } 

نحن نجعل قيمتنا (يجب أن تكون بالضبط true).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(".") public static class R {} @ConditionalOnProperty(value= ".", havingValue = "true") public static class C {} } 

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

وضعنا لنا @ConditionalOnRaven. من حيث المبدأ ، يمكنك لف كلاهما @ConditionalOnProduction، و @ConditionalOnMissingBean، ولكن الآن لن نقوم بذلك. فقط انظر ماذا حدث.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnRaven @ConditionalOnMissingBean public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

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

 @Data @ConfigurationalProperties("") public class RavenProperties { List<String> ; boolean ; } 

هذا كل شيء. false , true
application.yml:

 jpa.hibernate.ddl-auto: validate ironbank: ---: -  : : ,   : true 

, .

, repeatable. Java. .

, , .




دقيقة من الدعاية. 19-20 Joker 2018, « [Joker Edition]» , «Micronaut vs Spring Boot, ?» . بشكل عام ، سيكون هناك العديد من التقارير الأكثر إثارة للاهتمام والجديرة بالاهتمام في Joker. يمكن شراء التذاكر على الموقع الرسمي للمؤتمر.

ولدينا أيضًا استطلاع صغير لك!

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


All Articles