على الجبل تقف الحذاء الربيع ...

... له أربعة تصحيح الأخطاء.


مستوحاة من تقرير أعده فلاديمير بلزي ( Spring Boot 2: ما لم يكتبوه في ملاحظات الإصدار ) ، قررت الحديث عن تجربتي مع Spring Booth ، ميزاته ومخاطره التي التقيت بها في طريقي.


بيانات الربيع jpa


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


أعتقد أن الكثير منهم على دراية بإطار Spring Data ومشتقاته لمستودعات البيانات المختلفة. عملت مع واحد منهم فقط - Spring Data JPA. شهدت الواجهة الرئيسية المستخدمة في هذا الإطار - JpaRepository ، تغييرات كبيرة في الإصدارات 2. *.


النظر في الأساليب الأكثر استخداما:


  // interface JpaRepository<T, ID> { T findOne(ID id); List<T> findAll(Iterable<ID> ids); <S extends T> Iterable<S> save(Iterable<S> entities); } // interface JpaRepository<T, ID> { Optional<T> findById(ID id); List<T> findAllById(Iterable<ID> ids); <S extends T> Iterable<S> saveAll(Iterable<S> entities); } 

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


عندما أثار أحد مشاريعنا مسألة الترحيل إلى Spring Booth 2 واتخذت الخطوات الأولى (استبدال الإصدار في pom.xml) ، تحول المشروع بالكامل حرفيًا إلى اللون الأحمر: عشرات المستودعات ومئات المكالمات إلى أساليبها أدت إلى الترحيل "(باستخدام طرق جديدة) ربط كل ملف ثاني. تخيل أبسط الكود:


 SomeEntity something = someRepository.findOne(someId); if (something == null) { something = someRepository.save(new SomeEntity()); } useSomething(something); 

لكي يجتمع المشروع معًا ، تحتاج إلى:


  • استدعاء طريقة أخرى
  • تغيير نوع المتغير something إلى Optional<SomeEntity>
  • Optional::isPresent التحقق من المرجع الفارغ Optional::isPresent أو Optional::orElse / Optional::orElseGet
  • إصلاح الاختبارات

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


 //    @NoRepositoryBean public interface BaseJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { //   findOne,     1.* @Deprecated T findOne(ID id); //    findAll @Deprecated //         List<T> findAll(Iterable<ID> ids); } 

سوف ترث جميع مستودعاتنا من هذه الواجهة. الآن التنفيذ:


 public class BaseJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> { private final JpaEntityInformation<T, ?> entityInfo; private final EntityManager entityManager; public BaseJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInfo, EntityManager entityManager) { super(entityInfo, entityManager); this.entityInfo = entityInfo; this.entityManager = entityManager; } @Override public T findOne(ID id) { return findById(id).orElse(null); //    1.* } @Override public List<T> findAll(Iterable<ID> ids) { return findAllById(ids); //      } } 

الباقي في الصورة وشبهها. تختفي جميع أخطاء الترجمة ، ويعمل الرمز كما كان من قبل ، ولا يتم تغيير فئتين فقط ، وليس 200. يمكنك الآن استبدال واجهة برمجة التطبيقات القديمة بأخرى جديدة ببطء مع تقدم التطوير ، لأن Idea ستلون بعناية جميع المكالمات بالطرق الصفراءDeprecated.


السكتة الدماغية الأخيرة - نعلم الربيع أنه من الآن فصاعدًا يجب أن تكون المستودعات مبنية على أعلى مستوى من فئتنا:


 @Configuration @EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class) public class SomeConfig{ } 

في بركة هادئة ، توجد صناديق


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


يستخدم الكثيرون مكتبة gson لتحويل الكائنات إلى سلاسل JSON والعكس. يمكنك رؤية هذا الرمز غالبًا:


 @Component public class Serializer { public <T> String serialize(T obj) { return new Gson().toJson(obj); } } 

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


 @Configuration public class SomeConfig { @Bean public Gson gson() { return new Gson(); } } @Component @RequiredArgsConstructor public class Serializer { private final Gson gson; public String serialize(T obj) { return gson.toJson(obj); } } 

... دون أن يدرك أن Spring Booth يمكنه فعل كل شيء بنفسه. بشكل افتراضي ، يأتي Sat مع org.springframework.boot:spring-boot-autoconfigure التبعية ، والذي يتضمن العديد من الفئات المسماة *AutoConfiguration ، على سبيل المثال ، هذا:


 @Configuration @ConditionalOnClass(Gson.class) @EnableConfigurationProperties(GsonProperties.class) public class GsonAutoConfiguration { @Bean @ConditionalOnMissingBean public GsonBuilder gsonBuilder(List<GsonBuilderCustomizer> customizers) { GsonBuilder builder = new GsonBuilder(); customizers.forEach((c) -> c.customize(builder)); return builder; } @Bean @ConditionalOnMissingBean public Gson gson(GsonBuilder gsonBuilder) { return gsonBuilder.create(); } } 

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


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


أي شيء يدخل في صفك يمكن أن يستخدم ضدك.


قضية من الحياة. هناك LdapTemplate : أحدهما يستخدم LdapTemplate ، بينما استخدمه الآخر مرة واحدة. يعتمد كلا التطبيقين على المشروع core ، حيث يتم إخراج بعض الفئات الشائعة ، وتم طي المكتبات المشتركة لكلا التطبيقين بعناية في pom.xml.


بعد مرور بعض الوقت ، من المشروع الثاني ، تم LdapTemplate جميع استخدامات LdapTemplate أنها غير ضرورية. ولكن org.springramework:spring-ldap ظلت مكتبة org.springramework:spring-ldap في core . ثم قام SB بضرب و org.springramework.ldap:ldap-core تحولت org.springramework.ldap:ldap-core إلى org.springramework.boot:spring-boot-starter-data-ldap .


وبسبب هذا ، ظهرت رسائل مثيرة في السجلات:


 Multiple Spring Data modules found, entering strict repository configuration mode! Spring Data LDAP - Could not safely identify store assignment for repository candidate interface .... 

الاعتماد على org.springramework.boot:spring-boot-starter-data-ldap led org.springramework.boot:spring-boot-starter-data-ldap core org.springramework.boot:spring-boot-starter-data-ldap ليناسب المشروع الذي لا يستخدم LDAP على الإطلاق. ماذا يعني أن يتردد؟ ضرب classpath :). كذلك مع كل توقف:


  • إشعارات Spring Boot org.springramework.boot:spring-boot-starter-data-ldap classpath
  • يتم فحص كل مستودع الآن لمعرفة ما إذا كان مناسبًا للاستخدام مع Spring Data JPA أو Spring Data LDAP
  • إعادة تنظيم التبعيات org.springramework.boot:spring-boot-starter-data-ldap غير الضرورية org.springramework.boot:spring-boot-starter-data-ldap يقلل org.springramework.boot:spring-boot-starter-data-ldap من وقت بدء تشغيل التطبيق بمعدل 20 (!) ثانية من إجمالي 40-50

من المحتمل أن يسأل القارئ org.springramework.ldap:ldap-core : لماذا احتجت إلى تغيير org.springramework.ldap:ldap-core إلى org.springramework.boot:spring-boot-starter-data-ldap إذا لم يتم استخدام Spring Data LDAP الصف org.springframework.ldap.LdapTempate ؟


الجواب: لقد كان خطأ. الحقيقة هي أنه قبل الإصدار 2.1.0.M1 ، كان الضبط التلقائي لـ LDAP يبدو كهذا


 @Configuration @ConditionalOnClass({ContextSource.class}) @EnableConfigurationProperties({LdapProperties.class}) public class LdapAutoConfiguration { private final LdapProperties properties; private final Environment environment; public LdapAutoConfiguration(LdapProperties properties, Environment environment) { this.properties = properties; this.environment = environment; } @Bean @ConditionalOnMissingBean public ContextSource ldapContextSource() { LdapContextSource source = new LdapContextSource(); source.setUserDn(this.properties.getUsername()); source.setPassword(this.properties.getPassword()); source.setBase(this.properties.getBase()); source.setUrls(this.properties.determineUrls(this.environment)); source.setBaseEnvironmentProperties(Collections.unmodifiableMap(this.properties.getBaseEnvironment())); return source; } } 

أين هو LdapTemplate ؟ لكنه ليس :). بتعبير أدق ، هو ، لكنه يكمن في مكان آخر:


 @Configuration @ConditionalOnClass({LdapContext.class, LdapRepository.class}) @AutoConfigureAfter({LdapAutoConfiguration.class}) public class LdapDataAutoConfiguration { @Bean @ConditionalOnMissingBean({LdapOperations.class}) public LdapTemplate ldapTemplate(ContextSource contextSource) { return new LdapTemplate(contextSource); } } 

وبالتالي ، تم افتراض أنه يمكنك الحصول على LdapTemplate في التطبيق الخاص بك عن طريق تلبية الشرط @ConditionalOnClass({LdapContext.class, LdapRepository.class}) ، وهو أمر ممكن عندما تتم إضافة تبعية spring-boot-starter-data-ldap إلى classpath.


هناك احتمال آخر: تحديد هذا الحبة بيديك ، وهو ما لا تريده (لأننا نحتاج إلى SB بعد ذلك). org.springramework.boot:spring-boot-starter-data-ldap توصلوا إلى هذا بعد استبدال org.springramework.boot:spring-boot-starter-data-ldap with org.springramework.ldap:ldap-core .


تم حل المشكلة هنا: https://github.com/spring-projects/spring-boot/pull/13136 . تغيير كبير: نقل LdapTemplate bean إلى LdapAutoConfiguration . يمكنك الآن استخدام LDAP دون الربط بـ spring-boot-starter-data-ldap وتحديد حاوية LdapTemplate يدويًا.


ضجة هود


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


العلم الخاص هو المسؤول عن تشغيل / إيقاف هذا الوضع:


 spring: jpa: open-in-view: true 

في الإصدار 1. من SB ، تم تمكينه افتراضيًا ، في حين لم يتم إبلاغ المستخدم بهذا. في الإصدارات 2. * لا يزال ممكّنًا ، ولكن الآن يتم كتابة تحذير إلى السجل:


 WARN spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning. 

في البداية ، كان هناك طلب لتعطيل هذا الوضع ، تحتوي السلسلة الملحمية على الموضوع على عشرات (!) من التعليقات التفصيلية ، بما في ذلك بقلم أوليفر جورك (مطور Spring Spring) ، و Vlad Michalce (مطور Hibernate) ، و Phil Web و Vedran Pavic (مطورو Spring) مع إيجابيات وسلبيات.


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


 spring: jpa: open-in-view: false 

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

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


All Articles