Spring Boot 2: Was sie nicht in Versionshinweisen schreiben



Wenn ein Großprojekt einem umfangreichen Update unterzogen wird, ist nie alles einfach: Es gibt unweigerlich nicht offensichtliche Nuancen (mit anderen Worten einen Rechen). Und dann, egal wie gut die Dokumentation ist, hilft nur die Erfahrung - Ihre eigene oder die einer anderen - bei etwas.

Auf der Joker 2018-Konferenz habe ich darüber gesprochen, auf welche Probleme ich beim Wechsel zu Spring Boot 2 selbst gestoßen bin und wie sie gelöst werden. Und jetzt speziell für Habr - die Textversion dieses Berichts. Der Einfachheit halber enthält der Beitrag sowohl eine Videoaufzeichnung als auch ein Inhaltsverzeichnis: Sie können nicht das Ganze lesen, sondern gehen direkt zu dem Problem, das Sie beunruhigt.

Inhaltsverzeichnis





Guten Tag! Ich möchte Ihnen einige Funktionen erläutern (nennen wir sie Rakes), die beim Aktualisieren des Spring Boot-Frameworks auf die zweite Version und des nachfolgenden Vorgangs auftreten können.

Mein Name ist Vladimir Plizgá ( GitHub ), ich arbeite für CFT, einen der größten und ältesten Softwareentwickler in Russland. In den letzten Jahren habe ich dort Backend-Entwicklungen durchgeführt, die für die technische Entwicklung der Online-Bank für Prepaid-Karten verantwortlich sind. Bei diesem Projekt wurde ich der Initiator und Ausführender des Übergangs von einer monolithischen Architektur zu einer Mikroservice-Architektur (die noch andauert). Nun, da das meiste Wissen, das ich mit Ihnen teilen wollte, am Beispiel dieses speziellen Projekts gesammelt wurde, werde ich Ihnen etwas mehr darüber erzählen.

Kurz zum experimentellen Produkt


Dies ist eine Internetbank, die im Alleingang mehr als zwei Dutzend Partnerunternehmen in ganz Russland bedient: Sie bietet Endkunden die Möglichkeit, ihr Geld über Remote-Banking-Dienste (mobile Anwendungen, Websites) zu verwalten. Einer der Partner ist Beeline und seine Zahlungskarte. Es stellte sich als gutes Internet-Banking heraus, gemessen am Rating des Markswebb Mobile Banking Rank , bei dem unser Produkt eine gute Position für Anfänger einnahm.

"Guts" befinden sich noch im Übergang, daher haben wir einen Monolithen, den sogenannten Kern, um den 23 Mikrodienste aufgebaut sind. Im Inneren die Spring Cloud Netflix-Microservices, Spring Integration und mehr. Und auf Spring Boot 2 fliegt das Ganze seit ungefähr dem Monat Juli. Und genau an dieser Stelle werden wir genauer darauf eingehen. Bei der Übersetzung dieses Projekts in die zweite Version bin ich auf einige Funktionen gestoßen, über die ich Ihnen erzählen möchte.

Berichtsübersicht




Es gibt viele Bereiche, in denen Funktionen von Spring Boot 2 erschienen sind. Wir werden versuchen, alles durchzugehen. Um dies schnell zu erledigen, brauchen wir einen erfahrenen Detektiv oder Ermittler - jemanden, der all dies wie für uns ans Licht bringt. Da Holmes und Watson bereits eine Präsentation bei Joker gehalten haben, werden wir von einem anderen Spezialisten unterstützt, Lieutenant Colombo. Mach weiter!

Federstiefel / 2


Zunächst ein paar Worte zu Spring Boot im Allgemeinen und zur zweiten Version im Besonderen. Erstens wurde diese Version, gelinde gesagt, nicht gestern veröffentlicht: Am 1. März 2018 befand sie sich bereits in der allgemeinen Verfügbarkeit. Eines der Hauptziele der Entwickler war die vollständige Unterstützung von Java 8 auf Quellenebene. Das heißt, es kann nicht auf einer kleineren Version kompiliert werden, obwohl die Laufzeit kompatibel ist. Das Spring Framework der fünften Version, das etwas früher als Spring Boot 2 veröffentlicht wurde, wurde als Grundlage genommen. Dies ist nicht die einzige Abhängigkeit. Er hat auch ein Konzept wie BOM (Bill Of Materials) - dies ist ein riesiges XML, das alle (für uns transitiven) Abhängigkeiten von allen Arten von Bibliotheken von Drittanbietern, zusätzlichen Frameworks, Tools und mehr auflistet.

Dementsprechend stammen nicht alle Spezialeffekte, die der zweite Spring Boot einbringt, von sich selbst oder vom Spring-Ökosystem. Für diese gesamte Farm wurden zwei hervorragende Dokumente geschrieben: Versionshinweise und das Migrationshandbuch . Dokumente sind cool, Frühling in diesem Sinne ist im Allgemeinen gut gemacht. Aus offensichtlichen Gründen ist es jedoch bei weitem nicht möglich, alles dort abzudecken: Es gibt einige Einzelheiten, Abweichungen usw., die dort entweder nicht enthalten sein können oder sollten. Wir werden über solche Funktionen sprechen.

Kompilierzeit. Beispiele für API-Änderungen


Beginnen wir mit dem mehr oder weniger einfachen und offensichtlichen Rechen: Dies sind diejenigen, die in der Kompilierungszeit entstehen. Das heißt, etwas, mit dem Sie das Projekt nicht einmal kompilieren können, wenn Sie einfach die Nummer 1 im Boot-Skript in 2 ändern.

Die Hauptquelle für Änderungen, die die Grundlage für solche Änderungen in Spring Boot wurden, ist natürlich der Übergang von Spring zu Java 8. Außerdem wurde der Webstack von Spring 5 und Spring Boot 2 relativ gesehen in zwei Teile geteilt. Jetzt ist es Servlet, traditionell für uns und reaktiv. Darüber hinaus mussten einige Mängel früherer Versionen berücksichtigt werden. Bibliotheken von Drittanbietern wurden gestartet (von außerhalb des Frühlings). Wenn Sie sich die Versionshinweise ansehen, werden Sie keine Fallstricke im laufenden Betrieb sehen, und ehrlich gesagt, als ich die Versionshinweise zum ersten Mal las, schien mir, dass dort alles in Ordnung war. Und für mich sah es ungefähr so ​​aus:


Aber wie Sie wahrscheinlich vermuten, ist nicht alles so gut.

Worauf die Zusammenstellung abzielt (Beispiel 1):

  • Warum : Die WebMvcConfigurerAdapter Klasse ist nicht mehr vorhanden.
  • Warum : Unterstützung von Java 8-Chips (Standardmethoden in Schnittstellen);
  • Was zu tun ist : Verwenden Sie die WebMvcConfigurer Oberfläche.

Ein Projekt wird möglicherweise nicht kompiliert, zumindest weil einige Klassen einfach nicht mehr existieren. Warum? Ja, da sie in Java 8 nicht benötigt werden. Wenn dies Adapter mit primitiver Implementierung von Methoden wären, dann gibt es nichts Besonderes zu erklären, Standardmethoden lösen all dies perfekt. Hier ist ein Beispiel für diese Klasse. Es ist klar, dass es ausreicht, die Schnittstelle selbst zu verwenden, und dass keine Adapter benötigt werden.

Welche Kompilierung wird unterbrochen (Beispiel 2):

  • Warum : Die PropertySourceLoader#load begonnen, eine Liste von Quellen anstelle einer zurückzugeben.
  • Warum : Unterstützung von Ressourcen mit mehreren Dokumenten, z. B. YAML;
  • Was zu tun ist : Umschließen Sie die Antwort in singletonList() (wenn überschrieben).

Ein Beispiel aus einem ganz anderen Bereich. Einige Methoden haben sogar die Signaturen geändert. Wenn Sie jemals die Methode loadSourceSourceLoader verwendet haben, wird jetzt eine Auflistung zurückgegeben. Dementsprechend ermöglichte dies die Unterstützung von Ressourcen mit mehreren Dokumenten. In YAML können Sie beispielsweise durch drei Striche eine Reihe von Dokumenten in einer Datei angeben. Wenn Sie jetzt von Java aus damit arbeiten müssen, denken Sie daran, dass dies über die Sammlung erfolgen muss.

Welche Kompilierung wird unterbrochen (Beispiel 3):

  • Warum : Einige Klassen aus dem Paket org.springframework.boot.autoconfigure.web von den Paketen .servlet - .servlet und .reactive ;
  • Warum : den Jet-Stack auf dem Niveau des Traditionellen zu unterstützen;
  • Was zu tun ist : Importe aktualisieren.

Es wurden noch mehr Änderungen eingeführt, wodurch gestapelt wurde. Zum Beispiel wurde das, was sich früher im selben Webpaket befand, jetzt in zwei Pakete mit einer Reihe von Klassen aufgeteilt. Dies sind .servlet und .reactive . Warum wird es gemacht? Weil der Jet-Stack keine riesige Krücke auf dem Servlet sein sollte. Dies war notwendig, damit sie ihren eigenen Lebenszyklus aufrechterhalten, sich in ihre eigenen Richtungen entwickeln und sich nicht gegenseitig stören konnten. Was tun? Genug, um die Importe zu ändern: Die meisten dieser Klassen blieben auf API-Ebene kompatibel. Die meisten, aber nicht alle.

Welche Kompilierung wird unterbrochen (Beispiel 4):

  • Warum : Die Signatur der Methoden der ErrorAttributes Klasse hat sich ErrorAttributes : Anstelle von RequestAttributes wurden WebRequest(servlet) und ServerRequest(reactive) verwendet.
  • Warum : den Jet-Stack auf dem Niveau des Traditionellen zu unterstützen;
  • Was zu tun ist : Ersetzen Sie Klassennamen in Signaturen.

In der ErrorAttributes-Klasse wurden jetzt anstelle von RequestAttributes zwei weitere Klassen in Methoden verwendet: WebRequest und ServerRequest. Der Grund ist der gleiche. Und was tun? Wenn Sie vom ersten zum zweiten Spring Boot wechseln, müssen Sie RequestAttributes in WebRequest ändern. Wenn Sie bereits auf der zweiten Seite sind, verwenden Sie ServerRequest. Offensichtlich nicht wahr?

Wie man ist


Es gibt einige solcher Beispiele, wir werden sie nicht alle klären. Was tun? Es lohnt sich zunächst, einen Blick in das Spring Boot 2.0-Migrationshandbuch zu werfen, um die Änderung, die Sie betrifft, rechtzeitig zu bemerken. Beispielsweise wird das Umbenennen völlig nicht offensichtlicher Klassen erwähnt. Wenn sich jedoch etwas getrennt und zerbrochen hat, sollte berücksichtigt werden, dass das Konzept von „Web“ in zwei Bereiche unterteilt ist: „Servlet“ und „Reaktiv“. Bei der Orientierung in allen Klassen und Paketen kann dies helfen. Darüber hinaus sollte berücksichtigt werden, dass nicht nur die Klassen und Pakete selbst umbenannt wurden, sondern auch ganze Abhängigkeiten und Artefakte. So geschah dies beispielsweise mit Spring Cloud.



Inhaltstyp. Bestimmen des Typs der HTTP-Antwort


Genug von diesen einfachen Dingen aus der Kompilierungszeit, dort ist alles klar und einfach. Lassen Sie uns darüber sprechen, was zur Laufzeit passieren und entsprechend schießen kann, auch wenn Spring Boot 2 schon lange für Sie arbeitet. Lassen Sie uns über die Definition des Inhaltstyps sprechen.



Es ist kein Geheimnis, dass Spring Webanwendungen schreiben kann, sowohl Seiten- als auch REST-APIs, und sie können Inhalte mit einer Vielzahl von Typen rendern, sei es XML, JSON oder etwas anderes. Und einer der Reize, die Spring so mag, ist, dass Sie sich nicht um die Definition des Typs kümmern müssen, der in Ihrem Code angegeben ist. Sie können auf Magie hoffen. Diese Magie funktioniert relativ gesehen auf drei verschiedene Arten: Entweder basiert sie auf dem vom Client stammenden Accept-Header oder auf der Erweiterung der angeforderten Datei oder auf einem speziellen Parameter in der URL, der natürlich auch gesteuert werden kann.

Betrachten Sie ein einfaches Beispiel ( vollständiger Quellcode ). Im Folgenden werde ich die Notation von Gradle verwenden, aber selbst wenn Sie ein Maven-Fan sind, wird es für Sie nicht schwierig sein zu verstehen, was hier geschrieben steht: Wir erstellen eine winzige Anwendung auf dem ersten Spring Boot und verwenden nur ein Starter-Web.

Beispiel (v1.x):

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

Als ausführbaren Code haben wir eine einzige Klasse, in der die Controller-Methode sofort deklariert wird.

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

Es nimmt einen bestimmten Dateinamen als Eingabe, den es angeblich bilden und geben wird. Es bildet seinen Inhalt wirklich in einem der drei angegebenen Typen (dies wird durch den Dateinamen bestimmt), gibt jedoch den Inhaltstyp in keiner Weise an - wir haben Spring, er wird alles selbst tun.



Im Allgemeinen können Sie dies sogar versuchen. Wenn wir dasselbe Dokument mit unterschiedlichen Erweiterungen anfordern, wird es je nach Rückgabe mit dem richtigen Inhaltstyp zurückgegeben: wenn Sie möchten - json, wenn Sie möchten - txt, wenn Sie möchten - html. Es funktioniert wie ein Märchen.

Aktualisierung auf v2.x.


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

Es ist Zeit, auf den zweiten Spring Boot zu aktualisieren. Wir ändern einfach die Nummer 1 in 2.



Spring MVC Path Matching Standardverhaltensänderung

Aber wir sind Ingenieure, wir werfen einen Blick auf den Migrationsleitfaden und plötzlich wird etwas darüber gesagt. Es wird jedoch eine Art „Suffix-Pfadabgleich“ erwähnt. Es geht darum, wie Methoden in Java mit einer URL korrekt zugeordnet werden. Dies ist jedoch nicht unser Fall, wenn auch ein wenig ähnlich.



Deshalb punkten, prüfen und schlagen wir! - funktioniert plötzlich nicht mehr. Aus irgendeinem Grund wird überall nur Text / HTML angegeben. Wenn Sie es ausgraben, ist es nicht nur Text / HTML, sondern nur der erste der Typen, die Sie im Attribut "produziert "in der Annotation" @GetMapping "angegeben haben. Warum so? Es sieht, gelinde gesagt, unverständlich aus.



Und hier helfen keine Versionshinweise, Sie müssen die Quelle lesen.

ContentNegotiationManagerFactoryBean


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

Dort finden Sie einen Klassiker mit einem sehr verständlichen lakonischen Kurznamen, der eine bestimmte Flagge mit dem Namen "Erweiterung auf dem Weg berücksichtigen" (favorPathExtension) erwähnt. Der Wert dieses Flags "true" entspricht der Anwendung einer bestimmten Strategie mit einem anderen verständlichen kurzen, kurzen Namen, aus dem hervorgeht, dass es nur für die Bestimmung des Inhaltstyps anhand der Dateierweiterung verantwortlich ist. Wie Sie sehen können, gilt die Strategie nicht, wenn das Flag falsch ist.



Ja, wahrscheinlich haben viele bemerkt, dass es im Frühjahr anscheinend eine Art Richtlinie gibt, so dass der Name gut sein muss, mindestens zwanzig Zeichen lang.



Wenn Sie etwas tiefer eintauchen, können Sie genau ein solches Fragment graben. Im Spring-Framework selbst und nicht wie zu erwarten in der fünften Version, aber seit jeher wurde dieses Flag standardmäßig auf "true" gesetzt. Während des Spring Boot und in der zweiten Version wurde es von einem anderen blockiert, der jetzt über die Einstellungen gesteuert werden kann. Das heißt, jetzt können wir sie vom Umweltmanagement ablenken, und dies ist nur in der zweiten Version. Fühlst du Dort nahm er bereits die Bedeutung von „falsch“ an. Das heißt, sie wollten irgendwie das Beste tun, dieses Flag in die Einstellungen setzen (und das ist großartig), aber der Standardwert wurde auf einen anderen umgeschaltet (das ist nicht sehr).

Die Entwickler des Frameworks sind ebenfalls Menschen, sie neigen auch dazu, Fehler zu machen. Was tun? Es ist klar, dass Sie den Parameter in Ihrem Projekt ändern müssen, und alles wird gut.

Das Einzige, was Sie für den Fall tun sollten, um Ihr Gewissen zu klären, ist, in der Spring Boot-Dokumentation nachzuschlagen, ob diese Flagge erwähnt wird. Und dort wird er wirklich erwähnt , aber nur in einem seltsamen Kontext:
Wenn Sie die Einschränkungen verstehen und dennoch möchten, dass Ihre Anwendung den Suffix-Pattern-Matching verwendet, ist die folgende Konfiguration erforderlich:
spring.mvc.contentnegotiation.favor-path-extension = true
...
Es heißt, wenn Sie alle Tricks verstehen und trotzdem den Suffix-Pfadabgleich verwenden möchten, aktivieren Sie dieses Kontrollkästchen. Fühlen Sie die Diskrepanz? Es scheint, als würden wir im Kontext dieses Flags über die Definition des Inhaltstyps sprechen, aber hier sprechen wir über den Abgleich von Java-Methoden und URLs. Es sieht irgendwie unverständlich aus.

Wir müssen weiter graben. Es gibt eine solche Pull-Anfrage auf GitHub:



Im Rahmen dieser Pull-Anfrage wurden diese Änderungen vorgenommen - der Standardwert wurde geändert - und dort sagt einer der Autoren des Frameworks, dass dieses Problem zwei Aspekte hat: Einer ist nur die Pfadübereinstimmung und der zweite ist die Definition des Inhaltstyps . Das heißt, mit anderen Worten, das Flag gilt für beide und sie sind untrennbar miteinander verbunden.

Sie könnten es natürlich sofort auf GitHub finden, wenn Sie nur wüssten, wo Sie suchen müssen.


Suffix-Match

Darüber hinaus heißt es in der Dokumentation zum Spring Framework selbst, dass die Verwendung von Dateierweiterungen früher erforderlich war, jetzt jedoch nicht mehr als Notwendigkeit angesehen wird. Darüber hinaus erwies es sich in mehreren Fällen als problematisch.

Fassen Sie zusammen


Das Ändern des Standardflag-Werts ist überhaupt kein Fehler, sondern eine Funktion. Es ist untrennbar mit der Definition der Pfadübereinstimmung verbunden und soll drei Dinge tun:

  • Sicherheitsrisiken reduzieren (welche werde ich klären);
  • Richten Sie das Verhalten von WebFlux und WebMvc aus. Sie unterschieden sich in diesem Aspekt.
  • Richten Sie die Anweisung in der Dokumentation am Framework-Code aus.

Wie man ist


Erstens sollten Sie sich nach Möglichkeit nicht auf die Definition des Inhaltstyps durch Erweiterung verlassen. Das Beispiel, das ich gezeigt habe, ist ein Gegenbeispiel, das ist nicht nötig! Es ist auch nicht notwendig, sich darauf zu verlassen, dass Anfragen der Form "GET Something.json" beispielsweise einfach "GET Something" sind. Dies war in Spring Framework 4 und in Spring Boot 1 der Fall. Dies funktioniert nicht mehr. Wenn Sie einer Datei mit der Erweiterung zuordnen müssen, müssen Sie dies explizit tun. Stattdessen ist es besser, sich auf den Accept-Header oder den URL-Parameter zu verlassen, dessen Namen Sie steuern können. Wenn Sie dies in keiner Weise tun können, nehmen wir an, Sie haben einige alte mobile Clients, die im letzten Jahrhundert nicht mehr aktualisiert wurden. Dann müssen Sie dieses Flag zurückgeben, es auf "true" setzen und alles funktioniert wie zuvor.

Zum allgemeinen Verständnis können Sie außerdem das Kapitel „Suffix-Übereinstimmung“ in der Dokumentation zum Spring Framework lesen, das von den Entwicklern als eine Art Best-Practice-Sammlung in diesem Bereich betrachtet wird, und sich mit dem Angriff auf Reflected File Download vertraut machen, der nur mithilfe von Manipulationen mit implementiert wird Dateierweiterung.

Planung. Geplante oder regelmäßige Aufgaben


Lassen Sie uns den Umfang ein wenig ändern und über das Erledigen von Aufgaben nach Zeitplan oder in regelmäßigen Abständen sprechen.

Ein Beispiel für eine Aufgabe. Protokollnachricht alle 3 Sekunden


Was gesagt wird, denke ich, ist verständlich. Wir haben einige geschäftliche Anforderungen, um etwas mit einer Art Wiederholung zu tun, also werden wir sofort mit dem Beispiel fortfahren. Nehmen wir an, wir haben eine mega-komplexe Aufgabe: alle 3 Sekunden etwas Dreck in das Protokoll auszugeben.



Dies kann natürlich auf verschiedene Arten geschehen, denn für sie gibt es im Frühling schon etwas. Und finde es - viele Möglichkeiten.

Option 1: Suchen Sie in Ihrem Projekt nach einem Beispiel


 /** *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 … } 

Wir können in unser eigenes Projekt schauen und werden wahrscheinlich so etwas finden. Eine Anmerkung hängt an der öffentlichen Methode, und es wird deutlich, dass, sobald Sie sie auflegen, alles wie in einem Märchen funktioniert.

Option 2: Suchen Sie nach der gewünschten Anmerkung




Sie können die Anmerkung direkt anhand des Namens suchen. Aus der Dokumentation geht auch hervor, dass Sie sie aufhängen - und alles funktioniert.

Option 3: Googeln


Wenn Sie nicht an sich glauben, können Sie es googeln. Nach dem, was Sie gefunden haben, wird auch klar, dass alles mit einer Anmerkung beginnt.

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

Wer sieht den Haken darin? Wir sind schließlich Ingenieure. Lassen Sie uns überprüfen, wie dies in der Realität funktioniert.

Zeig mir den Code!


Betrachten Sie eine bestimmte Aufgabe (die Aufgabe selbst und der Code befinden sich in meinem Repository ).

Wer nicht lesen möchte, kann dieses Fragment des Videos mit einer Demonstration (bis zur 22. Minute) ansehen:


Als Abhängigkeit verwenden wir den ersten Spring Boot mit zwei Startern. Eine ist für das Web, es ist, als würden wir einen Webserver entwickeln, und die zweite ist der Federstarter-Aktuator, damit wir produktionsbereite Funktionen haben, so dass wir zumindest ein bisschen wie etwas Reales sind.

 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      } 

Und unser ausführbarer Code wird noch einfacher.

 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     …”); } } 

Im Allgemeinen fast nichts Bemerkenswertes, außer der einzigen Methode, an der wir die Anmerkung aufgehängt haben. Wir haben es irgendwo kopiert und erwarten, dass es funktioniert.

Lassen Sie uns überprüfen, wir sind Ingenieure. Wir fangen an. Wir gehen davon aus, dass alle drei Sekunden eine solche Nachricht protokolliert wird. Alles sollte sofort funktionieren, wir stellen sicher, dass alles beim ersten Spring Boot gestartet wird, und wir erwarten die Ausgabe der gewünschten Zeile. Drei Sekunden vergehen - eine Zeile wird ausgegeben, sechs Durchgänge - eine Zeile wird angezeigt. Optimisten haben gewonnen, alles funktioniert.



Es ist erst an der Zeit, auf den zweiten Spring Boot zu aktualisieren. Wir werden uns nicht darum kümmern, einfach von einem zum anderen wechseln:

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

Theoretisch hat uns der Migrationsleitfaden vor nichts gewarnt, und wir erwarten, dass alles ohne Abweichungen funktioniert. Aus Sicht des ausführbaren Codes haben wir keinen der anderen Rakes, die ich zuvor erwähnt habe (Inkompatibilität auf API-Ebene oder etwas anderes), da die Anwendung so einfach wie möglich ist.

Wir fangen an. Zunächst sind wir davon überzeugt, dass wir am zweiten Spring Boot arbeiten, ansonsten gibt es anscheinend keine Abweichungen.



Es vergehen jedoch 3 Sekunden, 6, 9, aber es gibt immer noch keinen Herman - keine Schlussfolgerung, nichts funktioniert.
Wie so oft widerspricht die Erwartung der Realität. Sie schreiben uns oft in der Dokumentation, dass in Spring Boot tatsächlich alles sofort funktioniert, dass wir im Allgemeinen einfach so beginnen können, wie es ist, mit minimalen Problemen, und dass keine Konfiguration erforderlich ist. Aber sobald es zur Realität kommt, stellt sich oft heraus, dass man die Dokumentation noch lesen sollte. Insbesondere wenn Sie tief graben, finden Sie hier folgende Zeilen:
7.3.1. Aktivieren Sie Planungsanmerkungen
Um die Unterstützung für Annotationen @Scheduled und Async zu aktivieren, können Sie einer Ihrer @ Configuration-Klassen @EnableScheduling und @EnableAsync hinzufügen.
Damit die geplante Annotation funktioniert, müssen Sie eine weitere Annotation mit einer anderen Annotation an die Klasse hängen. Na ja, wie immer im Frühling. Aber warum hat es vorher funktioniert? So etwas haben wir nicht gemacht. Offensichtlich hing diese Anmerkung irgendwo früher im ersten Spring Boot, aber jetzt ist sie aus irgendeinem Grund nicht im zweiten.



Wir beginnen, die Quellcodes des ersten Spring Boot zu durchsuchen. Wir finden, dass es eine Klasse gibt, an der es angeblich hängt. Wir schauen genauer hin, es heißt "MetricExportAutoConfiguration" und ist anscheinend dafür verantwortlich, diese Leistungsmetriken außerhalb an einige zentralisierte Aggregatoren zu liefern, und es hat wirklich diese Anmerkung.



Darüber hinaus funktioniert es so, dass es sein Verhalten auf einmal in die gesamte Anwendung einbezieht und nicht an separate Klassen gehängt werden muss. Diese Klasse war der Lieferant dieses Verhaltens und tat es dann aus irgendeinem Grund nicht. Warum?



Trotzdem bringt uns GitHub zu einer solchen archäologischen Ausgrabung: Im Rahmen des Übergangs zur zweiten Version von Spring Boot wurde diese Klasse zusammen mit der Anmerkung gemäht. Warum? Ja, da sich auch die Metrics Delivery Engine geändert hat: Sie verwenden kein eigenes Skript mehr, sondern wechseln zu Micrometer - eine wirklich sinnvolle Lösung. Das ist nur noch etwas Überflüssiges bei ihm. Vielleicht ist das sogar richtig.

Wer nicht lesen möchte, sieht sich 30 Sekunden lang eine kurze Demo an:


Daraus folgt, dass, wenn wir jetzt die fehlende Anmerkung in unserer ursprünglichen Klasse nehmen und manuell aufhängen, das Verhalten theoretisch korrekt werden sollte.

 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     …”); } } 

Glaubst du, es wird funktionieren? Lassen Sie uns überprüfen. Wir fangen an.



Es ist ersichtlich, dass nach 3 Sekunden, nach 6 und nach 9 die von uns erwartete Nachricht immer noch im Protokoll angezeigt wird.

Wie man ist


Was tun in diesem speziellen und allgemeineren Fall damit? Egal wie moralisch dies auch klingen mag, erstens lohnt es sich, nicht nur kopierte Dokumentationsfragmente zu lesen, sondern auch ein wenig weiter zu gehen, um solche Aspekte abzudecken.

Denken Sie zweitens daran, dass in Spring Boot viele Funktionen (Scheduling, Async, Caching, ...) nicht immer enthalten sind, sondern explizit aktiviert werden müssen.

Drittens ist es nicht wichtig, sicher zu sein: Fügen Sie Ihrem Code Enable * -Anmerkungen (und deren gesamte Familie) hinzu, ohne auf ein Framework zu hoffen. Aber dann stellt sich die Frage: Was passiert, wenn ich und meine Kollegen zufällig ein paar Anmerkungen hinzufügen, wie werden sie sich verhalten? Das Framework selbst behauptet, dass das Duplizieren von Anmerkungen niemals zu Fehlern führt. : . , .

, @EnableAsync Enable Caching , , , , , . , . ? javadoc , . , . , Enable *, , . ? .

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

Wir fangen an.



. , , - 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, , .

Wie man ist


-, , 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' , , — . , , , :


, , . - . , .

Zum Beispiel:

 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, , , .

, - , - , . , . :



, - , , , , -, . .

Wie man ist


: - . -, , 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 .

Was tun? , , 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 )

Wie man ist


, , 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 , .

Andere


, , , 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/de439796/


All Articles