Auf dem Berg steht Spring Boot ...

... seine vier debuggen.


Inspiriert von einem Bericht von Vladimir Plizgi ( Spring Boot 2: Was sie nicht in Versionshinweisen schreiben ), beschloss ich, über meine Erfahrungen mit Spring Booth, seine Funktionen und Fallstricke zu sprechen, die ich auf meinem Weg kennengelernt habe.


Federdaten jpa


Wladimir's Bericht widmet sich der Migration zu Spring Booth 2 und den auftretenden Schwierigkeiten. Im ersten Teil beschreibt er Kompilierungsfehler, daher werde ich auch damit beginnen.


Ich denke, viele kennen das Spring Data Framework und seine Derivate für verschiedene Data Warehouses. Ich habe nur mit einem von ihnen gearbeitet - Spring Data JPA. Die in diesem Framework verwendete Hauptschnittstelle - JpaRepository - wurde in Version 2 erheblich geändert. *.


Betrachten Sie die am häufigsten verwendeten Methoden:


  // 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); } 

Tatsächlich gibt es weitere Änderungen, und nach dem Wechsel zu Version 2 * werden alle zu Kompilierungsfehlern.


Als eines unserer Projekte das Problem der Migration auf Spring Booth 2 ansprach und die ersten Schritte unternommen wurden (Ersetzen der Version in pom.xml), wurde das gesamte Projekt buchstäblich rot: Dutzende von Repositorys und Hunderte von Aufrufen ihrer Methoden führten zur Migration "(mit neuen Methoden) würde jede zweite Datei einbinden. Stellen Sie sich den einfachsten Code vor:


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

Damit das Projekt einfach zusammenkommt, müssen Sie


  • Rufen Sie eine andere Methode auf
  • Ändern Sie den Typ der Variablen in Optional<SomeEntity>
  • Ersetzen Sie die Prüfung der leeren Referenz durch die Optional::orElseGet Optional::isPresent oder Optional::orElse / Optional::orElseGet
  • Tests reparieren

Glücklicherweise gibt es einen einfacheren Weg, da Spring Date Benutzern bisher beispiellose Möglichkeiten bietet, Repositorys an ihre Bedürfnisse anzupassen. Wir müssen lediglich die Schnittstelle und Klasse definieren (sofern sie noch nicht definiert wurden), die die Grundlage aller Repositorys unseres Projekts bilden. Dies geschieht folgendermaßen:


 //    @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); } 

Alle unsere Repositorys erben von dieser Schnittstelle. Jetzt Implementierung:


 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); //      } } 

Der Rest ist im Bild und in der Ähnlichkeit. Alle Kompilierungsfehler verschwinden, der Code funktioniert wie zuvor und nur 2 Klassen werden geändert und nicht 200. Jetzt können Sie die veraltete API im Verlauf der Entwicklung langsam durch eine neue ersetzen, da die Idee alle Aufrufe von @Deprecated-Methoden sorgfältig gelb färbt.


Letzter Schlag - wir informieren Spring, dass von nun an Repositories über unserer Klasse aufgebaut werden sollten:


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

In einem ruhigen Pool befinden sich Mülleimer


Spring Booth basiert auf zwei Konzepten - Starter und automatische Konfiguration. Im Leben ist die Konsequenz daraus eine wichtige Eigenschaft: Die Bibliothek, die in den Klassenpfad der Anwendung gelangt ist, wird vom SB gescannt. Wenn darin eine Klasse gefunden wird, die eine bestimmte Einstellung enthält, wird sie ohne Ihr Wissen und ohne ausdrückliche Anweisungen aktiviert. Wir zeigen dies anhand eines einfachen Beispiels.


Viele verwenden die gson- Bibliothek, um Objekte in JSON-Zeichenfolgen umzuwandeln und umgekehrt. Sie können diesen Code oft sehen:


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

Bei häufigen Zugriffen lädt dieser Code zusätzlich den Garbage Collector, den wir nicht wollen. Besonders kluge Leute erstellen ein Objekt und verwenden es:


 @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); } } 

... ohne zu merken, dass der Spring Booth alles selbst machen kann. Standardmäßig wird der SB mit der org.springframework.boot:spring-boot-autoconfigure , die viele Klassen mit dem Namen *AutoConfiguration , z.


 @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(); } } 

Die Einstellung ist einfach wie eine Schiene: Wenn die Anwendung eine Gson Klasse hat und keine manuell definierte Bean dieses Typs vorhanden ist, wird eine Standardimplementierung erstellt. Dies ist eine enorme Leistung, die es einer Abhängigkeit und einigen Anmerkungen ermöglicht, eine vollwertige Konfiguration zu erstellen, deren Beschreibung XML-Blätter und zahlreiche handgeschriebene Fächer verwendet.


Dies ist jedoch eine große Hinterlist des Sicherheitsrates. Hinterlist liegt in der Geheimhaltung, weil nach vorgefertigten Skripten viel Arbeit unter der Haube geschieht. Dies ist gut für eine typische Anwendung, aber das Verlassen der ausgetretenen Pfade kann Probleme verursachen - der Konvoi schießt ohne Vorwarnung. Viele Bohnen werden ohne unser Wissen hergestellt, das Beispiel mit Gson zeigt dies gut. Sei vorsichtig mit der Klasse und deinen Abhängigkeiten, denn ...


Alles, was in Ihren Klassenpfad gelangt, kann gegen Sie verwendet werden.


Ein Fall aus dem Leben. Es gibt zwei Anwendungen: Eine verwendet LdapTemplate , die andere hat es einmal verwendet. Beide Anwendungen hängen vom core , in dem einige gemeinsame Klassen herausgenommen werden, und die für beide Anwendungen gemeinsamen Bibliotheken wurden sorgfältig in pom.xml gefaltet.


Nach einiger Zeit wurden ab dem zweiten Projekt alle Verwendungen von LdapTemplate als unnötig herausgeschnitten. Aber die Bibliothek org.springramework:spring-ldap blieb im core . Dann schlug der SB zu und org.springramework.ldap:ldap-core wurde zu org.springramework.boot:spring-boot-starter-data-ldap .


Aus diesem Grund wurden in den Protokollen interessante Meldungen angezeigt:


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

Abhängigkeit von der core org.springramework.boot:spring-boot-starter-data-ldap um in ein Projekt org.springramework.boot:spring-boot-starter-data-ldap passen, in dem LDAP überhaupt nicht verwendet wird. Was bedeutet es zu zögern? Schlagen Sie den Klassenpfad :). Weiter mit allen Haltestellen:


  • Spring Boot-Hinweise org.springramework.boot:spring-boot-starter-data-ldap im Klassenpfad
  • Jedes Repository wird nun überprüft, ob es für die Verwendung mit Spring Data JPA oder Spring Data LDAP geeignet ist
  • Reorganisation von Abhängigkeiten und org.springramework.boot:spring-boot-starter-data-ldap unnötiger org.springramework.boot:spring-boot-starter-data-ldap reduziert die org.springramework.boot:spring-boot-starter-data-ldap der Anwendung um durchschnittlich 20 (!) Sekunden von insgesamt 40-50 Sekunden

Ein aufmerksamer und kritischer Leser wird wahrscheinlich fragen: Warum mussten Sie org.springramework.ldap:ldap-core in org.springramework.boot:spring-boot-starter-data-ldap wenn Spring Data LDAP nicht verwendet wird, sondern nur eines verwendet wird Klasse org.springframework.ldap.LdapTempate ?


Antwort: Es war ein Fehler. Tatsache ist, dass vor Version 2.1.0.M1 die automatische Optimierung für LDAP ungefähr so ​​aussah


 @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; } } 

Wo ist das LdapTemplate ? Aber er ist nicht :). Genauer gesagt, liegt es aber woanders:


 @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); } } 

Daher wurde angenommen, dass Sie LdapTemplate in Ihrer Anwendung erhalten LdapTemplate , LdapTemplate Sie die @ConditionalOnClass({LdapContext.class, LdapRepository.class}) , die möglich ist, wenn die Abhängigkeit spring-boot-starter-data-ldap zum Klassenpfad hinzugefügt wird.


Eine andere Möglichkeit: Bestimmen Sie diese Bohne mit Ihren Händen, die Sie nicht möchten (denn warum brauchen wir dann SB)? org.springramework.boot:spring-boot-starter-data-ldap kamen darauf, nachdem sie org.springramework.boot:spring-boot-starter-data-ldap durch org.springramework.ldap:ldap-core .


Das Problem wird hier behoben: https://github.com/spring-projects/spring-boot/pull/13136 . Wesentliche Änderung: Die LdapTemplate Bean- LdapTemplate nach LdapAutoConfiguration . Jetzt können Sie LDAP verwenden, ohne an spring-boot-starter-data-ldap LDAP gebunden zu spring-boot-starter-data-ldap und den LdapTemplate Bin manuell zu definieren.


Hood Aufregung


Wenn Sie den Ruhezustand verwenden, sind Sie wahrscheinlich mit dem Open-in-View- Antimuster vertraut. Wir werden nicht auf seine Beschreibung eingehen, indem wir darauf verweisen, dass es ausreichend detailliert beschrieben wird, und in anderen Quellen werden seine schädlichen Wirkungen sehr detailliert beschrieben.


Ein spezielles Flag ist für das Ein- und Ausschalten dieses Modus verantwortlich:


 spring: jpa: open-in-view: true 

In SB Version 1. * war es standardmäßig aktiviert, während der Benutzer nicht darüber informiert wurde. In Version 2. * ist es weiterhin aktiviert, aber jetzt wird eine Warnung in das Protokoll geschrieben:


 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. 

Ursprünglich gab es eine Aufforderung, diesen Modus zu deaktivieren. Der epische Srach zum Thema enthält mehrere Dutzend (!) Detaillierte Kommentare, einschließlich von Oliver Hörke (Spring Date-Entwickler), Vlad Michalce (Hibernate-Entwickler), Phil Web und Vedran Pavic (Spring-Entwickler) mit Vor- und Nachteilen.


Sie waren sich einig, dass sich das Verhalten nicht ändern wird, aber eine Warnung angezeigt wird (die beobachtet wird). Es gibt auch einen ziemlich häufigen Tipp, um diesen Modus zu deaktivieren:


 spring: jpa: open-in-view: false 

Das ist alles, schreiben Sie über Ihren Rechen und die interessanten Merkmale des Sicherheitsrates - dies ist wirklich ein unerschöpfliches Thema.

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


All Articles