Boot dich selbst, der Frühling kommt (Teil 2)

Published on October 05, 2018

Boot dich selbst, der Frühling kommt (Teil 2)

    Evgeny EvgenyBorisov Borisov (NAYA Technologies) und Kirill Tolkachev (Cyan.Finance, Twitter ) sprechen weiterhin über die Anwendung von Spring Boot zur Lösung von Problemen einer imaginären Iron Bank of Braavos. Im zweiten Teil konzentrieren wir uns auf die Profile und Feinheiten beim Starten der Anwendung.






    Den ersten Teil des Artikels finden Sie hier .


    Bis vor kurzem kam der Kunde und forderte einfach, eine Krähe zu senden. Jetzt hat sich die Situation geändert. Der Winter kam, die Mauer fiel.


    Erstens ändert sich das Prinzip der Kreditvergabe. Wenn sie früher mit einer Wahrscheinlichkeit von 50% an alle außer Starks ausgegeben wurden, werden sie jetzt nur an diejenigen ausgegeben, die Schulden zurückgeben. Deshalb ändern wir die Regeln für die Kreditvergabe in unserer Geschäftslogik. Aber nur für die Filialen der Bank, die dort liegen, wo der Winter bereits gekommen ist, bleibt bei allen anderen alles wie zuvor. Ich erinnere Sie daran, dass dies ein Dienst ist, der entscheidet, ob ein Darlehen ausgestellt wird oder nicht. Wir werden einfach einen anderen Service anbieten, der nur im Winter funktioniert.


    Gehen Sie zu unserer Geschäftslogik:


    public class WhiteListBasedProphetService implements ProphetService {
      @Override
      public boolean willSurvive(String name) {
        return false;
      }
    }

    Wir haben bereits eine Liste der Schuldner.


    spring:
      application.name: money-raven
      jpa.hibernate.ddl-auto: validate
    ironbank:
      те-кто-возвращают-долги:
        - Ланистеры
    ворон:
      куда-лететь: браавос, главный банк
      вкл: true

    Und es gibt eine Klasse, die bereits mit property - verbunden ist теКтоВозвращаютДолги.


    public class ProphetProperties {
      List<String> теКтоВозвращаютДолги;
    }

    Wie in früheren Zeiten injizieren wir es einfach hier:


    public class WhiteListBasedProphetService implements ProphetService {  
      private final ProphetProperties prophetProperties;
      @Override
      public boolean willSurvive(String name) {
        return false;
      }
    }

    Wir erinnern uns an die Konstruktorinjektion (an magische Anmerkungen):


    @Service
    @RequiredArgsConstructor
    public class WhiteListBasedProphetService implements ProphetService {
      private final ProphetProperties prophetProperties;
      @Override
      public boolean willSurvive(String name) {
        return false;
      }
    }

    Fast fertig


    Jetzt müssen wir nur noch diejenigen herausgeben, die die Schulden zurückgeben:


    @Service
    @RequiredArgsConstructor
    public class WhiteListBasedProphetService implements ProphetService {
      private final ProphetProperties prophetProperties;
      @Override
      public boolean willSurvive(String name) {
      return prophetProperties.getТеКтоВозвращаютДолги().contains(name);
      }
    }

    Aber hier haben wir ein kleines Problem. Jetzt haben wir zwei Implementierungen: die alten und die neuen Dienste.


    Description
    Parameter 1 of constructor in com.ironbank.moneyraven.service.TransferMoneyProphecyBackend…
      - nameBasedProphetService: defined in file [/Users/tolkv/git/conferences/spring-boot-ripper…
      - WhileListBackendProphetService: defined in file [/Users/tolkv/git/conferences/spring-boot-ripper...

    Es ist logisch, diese Beans in verschiedene Profile zu unterteilen. Profil зимаТутаund Profil зимаБлизко. Lassen Sie unseren neuen Dienst nur im Profil starten зимаТута:


    @Service
    @Profile(ProfileConstants.зимаТута)
    @RequiredArgsConstructor
    public class WhiteListBasedProphetService implements ProphetService {
      private final ProphetProperties prophetProperties;
      @Override
      public boolean willSurvive(String name) {
        return prophetProperties.getТеКтоВозвращаютДолги().contains(name);
      }
    }

    Und der alte Service ist in зимаБлизко:


    @Service
    @Profile(ProfileConstants.зимаБлизко)
    public class NameBasedProphetService implements ProphetService {
      @Override
      public boolean willSurvive(String name) {
        return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean();
      }
    }

    Der Winter kommt jedoch langsam. Im Königreich, das sich neben der gebrochenen Mauer befindet, ist bereits Winter. Aber irgendwo im Süden - noch nicht. Ie Anwendungen, die sich in verschiedenen Branchen und Zeitzonen befinden, sollten unterschiedlich funktionieren. Im Rahmen unserer Aufgabe können wir die alte Implementierung, in der der Winter gekommen ist, nicht löschen und die neue Klasse verwenden. Wir möchten, dass die Bankangestellten gar nichts tun: Wir installieren für sie eine Anwendung, die bis zum Winter im Sommermodus arbeitet. Und wenn der Winter kommt, starten sie ihn einfach neu und das war's. Sie müssen den Code nicht ändern, keine Klassen löschen. Deshalb haben wir zunächst zwei Profile: Ein Teil der Behälter wird im Sommer erstellt, und ein Teil der Behälter wird im Winter erstellt.


    Es gibt aber noch ein anderes Problem:




    Jetzt haben wir keine einzige Bean, da wir zwei Profile angegeben haben und die Anwendung im Standardprofil startet.


    Wir haben also eine neue Anforderung vom Kunden.


    Eisengesetz 2. Kein Profil kann nicht




    Wir möchten den Kontext nicht ansprechen, wenn das Profil nicht aktiviert ist, weil der Winter bereits da ist und alles sehr schlecht geworden ist. Es gibt bestimmte Dinge, die passieren müssen oder nicht, je nachdem, ob зимаТутаoder зимаБлизко. Sehen Sie sich auch die Ausnahme an, deren Text oben angezeigt wird. Er erklärt nichts. Das Profil ist nicht festgelegt, daher gibt es keine Implementierung ProphetService. Gleichzeitig hat niemand gesagt, dass es notwendig ist, das Profil einzustellen.


    Deshalb möchten wir nun die zusätzliche Sache in unserem Starter straffen, die beim Erstellen des Kontexts überprüft, ob ein Profil festgelegt wurde. Wenn es nicht angegeben ist, werden wir keine solche Ausnahme auslösen (und keine Ausnahme bezüglich des Fehlens einer Ablage).


    Können wir dies mit Hilfe unseres Anwendungslisteners tun? Nein. Dafür gibt es drei Gründe:


    • Ein einzelner Verantwortungslistener ist dafür verantwortlich, dass die Krähen fliegen. Der Listener muss nicht prüfen, ob das Profil aktiviert wurde, da die Profilaktivierung nicht nur den Listener selbst betrifft, sondern auch viel mehr.
    • Wenn der Kontext aufgebaut ist, passieren verschiedene Dinge. Und wir möchten nicht, dass sie beginnen, wenn kein Profil festgelegt wurde.
    • Listener arbeitet am Ende, wenn der Kontext aufgelöst ist. Und die Tatsache, dass es kein Profil gibt, wissen wir viel früher. Warum fünf Minuten warten, bis der Dienst fast gestiegen ist, und dann fällt alles.

    Außerdem weiß ich immer noch nicht, welche Fehler auftreten werden, weil wir ohne Profil aufgestiegen sind (vorausgesetzt, ich kenne die Geschäftslogik nicht). Daher ist es in Abwesenheit eines Profils notwendig, den Kontext zu einem sehr frühen Zeitpunkt zu bringen. Übrigens, wenn Sie eine Spring Cloud verwenden, wird diese für Sie noch relevanter, da die Anwendung zu einem frühen Zeitpunkt eine ganze Reihe von Aufgaben erledigt.


    Zur Umsetzung der neuen Anforderung existiert ApplicationContextInitializer. Dies ist eine weitere Schnittstelle, die es uns ermöglicht, einen bestimmten Federpunkt zu erweitern, indem Sie ihn in spring.factories angeben.




    Wir implementieren diese Schnittstelle und haben den Context Initializer, in dem wir Folgendes haben ConfigurableApplicationContext:


    public class ProfileCheckAppInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
      @Override
      public void initialize(ConfigurableApplicationContext applicationContext) {
      }
    }

    Damit können wir die Umwelt erhalten - das, was SpringApplication für uns vorbereitet hat. Alles Eigentum, das wir ihm übergeben haben, ist dort angekommen. Es enthält unter anderem Profile.


    Wenn dort keine Profile vorhanden sind, sollten wir eine Ausnahme auslösen, dass es unmöglich ist, so zu arbeiten.


    public class ProfileCheckAppInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
      @Override
      public void initialize(ConfigurableApplicationContext applicationContext) {
      if applicationContext.getEnvironment().getActiveProfiles().length == 0 {
          throw new RuntimeException("Нельзя без профиля!");
        }
      }
    }

    Jetzt müssen Sie es in spring.factories registrieren.


    org.springframework.boot.context.properties.EnableConfigurationProperties=com.ironbank.moneyraven.starter.IronConfiguration
    org.springframework.context.ApplicationContextInitializer=com.ironbank.moneyraven.starter.ProfileCheckAppInitializer

    Aus dem Vorstehenden kann man vermuten, dass ApplicationContextInitializerdies eine Art Erweiterungspunkt ist. ApplicationContextInitializerEs funktioniert, wenn der Kontext gerade erst aufgebaut wird und es noch keine Beans gibt.


    Es stellt sich die Frage: Wenn wir geschrieben haben ApplicationContextInitializer, warum hören Sie nicht als Zuhörer in einer Konfiguration, die sich ohnehin erstreckt? Die Antwort ist einfach: Weil es viel früher funktionieren sollte, wenn es keinen Kontext und keine Konfigurationen gibt. Ie es kann noch nicht gespritzt werden. Deshalb schreiben wir es als eine separate Sache vor.


    Der Startversuch zeigte: Es fiel alles schnell genug und es wurde berichtet, dass wir ohne Profil laufen. Und jetzt werden wir versuchen, jedes Profil anzugeben, und alles funktioniert - die Krähe wird gesendet.


    ApplicationContextInitializer - erfüllt, wenn der Kontext bereits erstellt wurde, es enthält jedoch nichts anderes als die Umgebung.




    Wer schafft die Umgebung? Carlson - SpringBootApplication. Er füllt es mit verschiedenen Metainformationen, die dann aus dem Kontext gezogen werden können. Die meisten Dinge können durch injiziert werden @value, etwas kann aus der Umgebung gewonnen werden, da wir gerade Profile erhalten haben.


    Zum Beispiel kommen hier verschiedene Eigenschaften:


    • Wer kann Spring Boot sammeln?
    • die beim Start über die Befehlszeile übertragen werden;
    • systemisch;
    • als Umgebungsvariablen registriert;
    • in den Anwendungseigenschaften vorgeschrieben;
    • in einigen anderen Eigenschaftsdateien geschrieben.

    All dies geht an das Umgebungsobjekt und betrachtet es. Dort erhalten Sie auch Informationen darüber, welche Profile aktiv sind. Das Umgebungsobjekt ist das einzige, was zu dem Zeitpunkt existiert, wenn Spring Boot einen Kontext erstellt.


    Ich würde gerne automatisch erraten, wie das Profil aussehen wird, wenn die Benutzer vergessen haben, es mit den Händen aufzustellen (wir tun alles, damit Bankangestellte, die hilflos genug sind, ohne Programmierer, die Anwendung ausführen können - damit alles für sie funktioniert, egal was passiert). Dazu fügen wir unserem Starter ein Stück hinzu, das das Profil erraten wird зимаТутаoder nicht - abhängig von der Außentemperatur. Und dies wird uns allen zu einer neuen magischen Schnittstelle verhelfen - EnvironmentPostProcessorweil wir dies tun müssen, bevor es funktioniert ApplicationContextInitializer. Und bis ApplicationContextInitializeres nur geht EnvironmentPostProcessor.


    Wir implementieren wieder eine neue Schnittstelle. Es gibt nur eine Methode, die es nur ConfigurableEnvironmentwirft, SpringApplicationweil ConfigurableContextwir keine haben (wir SpringInitializerhaben sie bereits, wir haben sie hier nicht; es gibt nur eine Umgebung).


    public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor {
      @Override
      public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
      }
    }

    In dieser Umgebung können wir ein Profil installieren. Zuerst müssen Sie jedoch überprüfen, dass zuvor noch niemand installiert wurde. Deshalb getActiveProfilesbrauchen wir sowieso eine Bestätigung . Wenn die Leute wissen, was sie tun, und sie ein Profil erstellen, werden wir nicht versuchen, für sie zu raten. Wenn es kein Profil gibt, werden wir versuchen, das Wetter zu verstehen.


    Zweitens müssen wir verstehen, ob das Wetter Winter oder Sommer ist. Wir werden die Temperatur zurückgeben -300.


    public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor {
      @Override
      public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    if (environment.getActivePrifiles().length == 0 && getTemperature() < -272) {
        }
      }
    public int getTemperature() {
      return -300;
    }
    }

    Unter dieser Bedingung haben wir Winter und können ein neues Profil einstellen. Wir erinnern uns, dass das Profil heißt зимаТута:


    public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor {
      @Override
      public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        if (environment.getActivePrifiles().length == 0 && getTemperature() < -272) {
    environment.setActiveProfiles("зимаТута");
        } else {
    environment.setActiveProfiles("зимаБлизко");
        }
      }
    public int getTemperature() {
      return -300;
    }
    }

    Nun müssen Sie EnvironmentPostProcessorin spring.factories angeben .


    org.springframework.boot.context.properties.EnableConfigurationProperties=com.ironbank.moneyraven.starter.IronConfiguration
    org.springframework.context.ApplicationContextInitializer=com.ironbank.moneyraven.starter.ProfileCheckAppInitializer
    org.springframework.boot.env.EnvironmentPostProcessor=com.ironbank.moneyraven.starter.ResolveProfileEnvironmentPostProcessor

    Als Ergebnis läuft die Anwendung ohne Profil, wir sagen, dass dies eine Produktion ist und prüfen, in welchem ​​Profil sie bei uns angefangen hat. Magisch haben wir erkannt, dass wir ein Profil haben зимаТута. Und die Anwendung ist nicht gefallen, denn als ApplicationContextInitializernächstes kommt, was prüft, ob ein Profil vorhanden ist.
    Ergebnis:


    public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor {
      @Override
      public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        if (getTemperature() < -272) {
          environment.setActiveProfiles("зимаТута");
        } else {
          environment.setActiveProfiles("зимаБлизко");
        }
      }
      private int getTemperature() {
        return -300;
      }
    }

    Wir haben darüber gesprochen, EnvironmentPostProcessorwer arbeitet ApplicationContextInitializer. Aber wer lässt es laufen?




    Es beginnt mit einem solchen Freak, der offenbar ein unehelicher Sohn ist ApplicationListenerund EnvironmentPostProcessorweil er von ApplicationListenerund nach geerbt wird EnvironmentPostProcessor. Es heißt ConfigFileApplicationListener(warum "ConfigFile" - niemand weiß).


    Er ist unser Carlson, d.h. Spring Application bietet eine vorbereitete Umgebung zum Abhören zweier Ereignisse: ApplicationPreparedEventund ApplicationEnvironmentPreparedEvent. Wir werden jetzt nicht zerlegen, wer diese Ereignisse wirft. Es gibt eine andere Schicht (meiner Meinung nach bereits völlig überflüssig, zumindest zu diesem Zeitpunkt in der Entwicklung von Spring), die ein Ereignis auslöst, dass die Umgebung jetzt erstellt wird (getrennt von Application.yml, Eigenschaften, Umgebungsvariablen usw.). ).
    Nach dem Empfang ApplicationEnvironmentPreparedEventversteht der Zuhörer, dass es notwendig ist, die Umgebung aufzubauen - alles zu finden EnvironmentPostProcessorund sie arbeiten zu lassen.




    Danach sagt er, SpringFactoriesLoaderliefern Sie alles, was Sie registriert haben, nämlich alles EnvironmentPostProcessor, in spring.actories. Dann stopft er alles EnvironmentPostProcessorin eine Liste.




    und er versteht, dass auch er EnvironmentPostProcessor(in Kombination) sich dort hineinstößt, sie

    gleichzeitig sortiert, mit ihnen reist und jede Methode ruft postProcessEnvironment.


    So werden alle postProcessEnvironmentschon frühzeitig gestartet SpringApplicationInitializer. In diesem Fall beginnt auch der unverständliche EnvironmentPostProcessorName ConfigFileApplicationListener.


    Wenn die Umgebung eingestellt ist, kehrt alles wieder zu Carlson zurück.


    Wenn die Umgebung bereit ist, können Sie einen Kontext erstellen. Und Carlson beginnt mit dem Aufbau von Kontext ApplicationInitializer. Hier haben wir unser eigenes Stück, das prüft, ob es eine Umgebung gibt, in der aktive Profile vorhanden sind. Wenn nicht, fallen wir, weil wir später noch Probleme haben werden. Starter funktionieren weiterhin mit allen üblichen Konfigurationen.


    Das Bild oben zeigt, dass es dem Frühling auch nicht gut geht. Es gibt solche Ausländer von Zeit zu Zeit, die einzelne Verantwortung wird nicht respektiert und es ist notwendig, vorsichtig dort zu klettern.


    Jetzt wollen wir ein wenig über die andere Seite dieser seltsamen Kreatur sprechen, die einerseits Hörer ist und andererseits - EnvironmentPostProcessor.




    Wie EnvironmentPostProcessorer application.yml, Anwendungseigenschaften, beliebige Umgebungsvariablen, Befehlsargumente usw. laden kann Als Zuhörer kann er zwei Ereignisse anhören:


    • ApplicationPreparedEvent
    • ApplicationEnvironmentPreparedEvent

    Die Frage stellt sich:




    Alle diese Ereignisse fanden im alten Frühling statt. Und die, über die wir oben gesprochen haben, sind das Spring Boot-Ereignis (besondere Ereignisse, die er für seinen Lebenszyklus hinzugefügt hat). Und ihr ganzes Rudel. Dies sind die wichtigsten:


    • ApplicationStartingEvent
    • ApplicationEnvironmentPreparedEvent
    • ApplicationPreparedEvent
    • ContextRefreshedEvent
    • EmbeddedServletContainerInitializedEvent
    • ApplicationReadyEvent
    • ApplicationFailedEvent

    Diese Liste ist weit von allen entfernt. Es ist jedoch wichtig, dass einige von ihnen zu Spring Boot und einige zu Spring (gut alt ContextRefreshedEventusw.) gehören.


    Der Nachteil ist, dass nicht alle dieser Ereignisse mit der Anwendung abgerufen werden können (bloße Sterbliche - verschiedene Großmütter - sie können nicht nur die komplexen Ereignisse abhören, die Spring Boot auslöst). Wenn Sie jedoch die geheimen Mechanismen von spring.factories kennen und Ihren Application Listener auf der Ebene von spring.factories definieren, werden Ihnen diese Ereignisse des frühesten Stadiums der Anwendung angezeigt.




    Dadurch können Sie den Start Ihrer Bewerbung frühzeitig beeinflussen. Lustig, aber, dass einige dieser Arbeiten wird an andere Stellen bewegt - wie EnvironmentPostProcessorund ApplicationContextInitializer.


    Es war möglich, alles auf den Hörer zu tun, aber es wäre unbequem und hässlich. Wenn Sie alle Ereignisse hören möchten, die Spring auslöst, nicht nur ContextRefreshedEventund ContextStartedEvent, dann müssen Sie den Listener nicht wie üblich auf eine übliche Weise vorschreiben (sonst wird er zu spät erstellt). Es sollte auch über spring.factories registriert werden, dann wird es viel früher erstellt.


    Übrigens, als wir uns diese Liste angesehen haben, war es uns unklar, wann es überhaupt funktioniert ContextStartedEventund ContextStoppedEvent?




    Es stellte sich heraus, dass diese Ereignisse überhaupt nicht funktionieren. Wir haben lange darüber nachgedacht, welche Art von Ereignissen wir erfassen müssen, um zu verstehen, dass die Anwendung wirklich gestartet wurde. Und es stellte sich heraus, dass die Ereignisse, über die wir sprechen, jetzt erscheinen, wenn Sie Methoden zwangsweise aus dem Kontext ziehen:


    • ctx.start(); -> ContextStartedEvent
    • ctx.stop(); -> ContextStoppedEvent

    Ie Event-s werden nur dann kommen, wenn wir laufen SpringApplication.run, Kontext bekommen, ihn wichsen ctx.start();oder ctx.stop();. Es ist nicht ganz klar, warum dies notwendig ist. Aber Sie haben wieder einen Erweiterungspunkt erhalten.


    Hat Spring etwas damit zu tun? Wenn ja, dann sollte irgendwo eine Ausnahme sein:


    • ctx.stop();  (1)
    • ctx.start(); (2)
    • ctx.close(); (3)
    • ctx.start(); (4)

    In der Tat wird es in der letzten Zeile stehen, denn nach ctx.close();dem Kontext können Sie nichts tun. Aber ctx.stop();vorher anrufen ctx.start();- es ist möglich (nur Spring ignoriert diese Ereignisse - sie sind nur für Sie).


    Schreiben Sie Ihre Zuhörer, hören Sie auf sich, erfinden Sie Ihre Gesetze, was zu tun ist ctx.stop();und was Sie tun sollen ctx.start();.


    Das Gesamtschema der Interaktion und des Lebenszyklus der Anwendung sieht folgendermaßen aus:




    Die Farben hier zeigen verschiedene Lebensabschnitte.


    • Blau ist Spring Boot, die Anwendung wurde bereits gestartet. Dies bedeutet, dass Tomcat-Serviceanforderungen, die von Clients an ihn gesendet werden, verarbeitet werden, der gesamte Kontext genau angehoben wird, alle Ablagen funktionieren, Datenbanken verbunden sind usw.
    • Grün - flog Ereignis ContextRefreshedEventund Kontext. Ab diesem Moment beginnen beispielsweise Anwendungslistener zu arbeiten, die Sie implementieren, indem Sie entweder die ApplicationListener-Annotation festlegen oder über die gleichnamige Schnittstelle ein Generic verwenden, das bestimmte Ereignisse überwacht. Wenn Sie mehr Ereignisse erhalten möchten, müssen Sie den gleichen ApplicationListener in spring.factories registrieren (hier funktioniert der übliche Frühling). Der Streifen markiert den Beginn des Spring Ripper- Berichts .
    • Zu einem früheren Zeitpunkt arbeitet SpringApplication, die einen Kontext für uns vorbereitet. Dies ist die Anwendungsvorbereitungsarbeit, die wir gemacht haben, als wir regelmäßige Spring-Entwickler waren. Beispielsweise haben wir WebXML konfiguriert.
    • Es gibt aber noch frühere Stadien. Es zeigt an, wer, wo und für wen arbeitet.
    • Es gibt auch eine graue Stufe, in der es keine Möglichkeit gibt, dazwischen zu gehen. Dies ist die Phase, in der SpringApplication sofort funktioniert (nur im Code zum Steigen).

    Wenn Sie während des Berichts in zwei Teilen festgestellt haben, dass wir von rechts nach links gegangen sind: Wir haben von Anfang an angefangen, die Konfiguration, die vom Starter kam, vermasselt und dann Folgendes hinzugefügt. Lassen Sie uns diese Kette in umgekehrter Richtung durchgehen.
    Sie schreiben in Ihrer Hauptsache SpringApplication.run. Er findet verschiedene Zuhörer, wirft ihnen ein Ereignis, das er zu bauen begonnen hat. Danach finden die Zuhörer EnvironmentPostProcessor, dass ihnen eine Umgebung zur Verfügung steht. Sobald die Umgebung eingerichtet ist, beginnen wir, den Kontext aufzubauen (Carlson tritt ein). Carlson erstellt den Kontext und lässt alle Anwendungsinitialisierer mit diesem Kontext etwas anfangen. Wir haben einen Erweiterungspunkt. Danach ist der Kontext bereits konfiguriert, und als Nächstes geschieht dasselbe wie in der üblichen Spring-Anwendung, wenn der Kontext erstellt wird BeanFactoryPostProcessor.BeanPostProcessorDie Bohnen sind anpassbar. Das macht der Frühling.


    Wie man läuft


    Wir haben den Prozess des Schreibens einer Bewerbung abgeschlossen.


    Aber wir hatten noch eine Sache, die Entwickler nicht mögen. Sie denken nicht gerne daran, wie am Ende ihre Bewerbung beginnt. Wird der Administrator es in Tomcat, JBoss oder WebLogic ausführen? Es muss einfach funktionieren. Wenn es nicht funktioniert, muss der Entwickler im schlimmsten Fall etwas neu einrichten.


    Also, wie fangen wir an?


    • Kater Krieg;
    • Idee;
    • java -jar/war.

    Kater ist kein massiver Trend, wir werden nicht im Detail darüber sprechen.


    Die Idee ist im Prinzip auch nicht sehr interessant. Es ist nur ein bisschen schlauer, als ich unten erzählen werde. Aber in Idea sollte es grundsätzlich keine Probleme geben. Sie sieht, welche Abhängigkeiten der Starter bringt.
    In diesem Fall java -jarbesteht das Hauptproblem darin, den Klassenpfad zu erstellen, bevor wir die Anwendung ausführen.


    Was haben die Leute 2001 gemacht? Sie haben geschrieben, java -jarwas jar laufen soll, dann ein Leerzeichen classpath=...und dort Skripte angegeben. In unserem Fall gibt es 150 MB unterschiedliche Abhängigkeiten, die Starter hinzugefügt haben. Und all das müsste manuell gemacht werden. Natürlich tut das niemand. Wir schreiben einfach: java -jarWas für ein Glas sollte laufen, und das ist es. Irgendwie ist der Klassenpfad immer noch gebaut. Darüber werden wir jetzt reden.


    Beginnen wir mit der Vorbereitungsphase der JAR-Datei, damit sie überhaupt ohne Tomcat ausgeführt werden kann. Bevor Sie dies tun java -jar, müssen Sie ein Glas bauen. Dieses Gefäß sollte offensichtlich ungewöhnlich sein, eine Art Analogon des Krieges, in dem sich alles befinden wird, einschließlich des eingebetteten Tomcat.


    <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
    </build>

    Als wir das Projekt heruntergeladen haben, haben wir bereits ein Plugin in POM registriert. Hier können Sie übrigens Konfigurationen einfügen, aber dazu später mehr. Als Ergebnis wird neben dem üblichen Glas, das Maven oder Gradle Ihrer Anwendung erstellt, ein anderes ungewöhnliches Glas erstellt. Auf der einen Seite sieht es gut aus:




    Aber wenn du auf die Seite schaust:




    Dies ist im Grunde ein Analogon zum Krieg.


    Mal sehen, worum es ihm geht.


    Es gibt offensichtlich Standardteile wie im üblichen Glas. Wenn wir schreiben java -jar, werden alle Klassen, die sich zum Beispiel in einer Wurzel befinden, zusammengezogen org.springframework.boot. Dies ist jedoch nicht unser Unterricht. Das Paket org.springframework.boot wurde kaum geschrieben. Daher ist uns das in erster Linie vertrautMETA-INF




    Spring Boot macht auch MANIFEST (durch dasselbe Maven- oder Gradle-Plugin), passt es an und legt die Hauptklasse fest, die im Glas ausgeführt wird.


    Tatsächlich sind alle Gläser in zwei Typen unterteilt: ausgelöste und einfache Abhängigkeiten von etwas, das kein eigenes Hauptfach hat. Und wir können java -jarnur auf -jar tun , was die Hauptklasse ausdrückt.


    Es ist logisch anzunehmen, dass dieses Plugin, das uns zum Verpacken und Erstellen dieses MANIFEST macht, in der Hauptklasse die Klasse vorschreibt, die wir hauptsächlich haben (die wir von Idea ausführen). Aber wenn Sie ein wenig tiefer tauchen, kann dies nicht sein. Wer baut dann den Klassenpfad? Wenn wir annehmen java -jarund sagen, dass main, das ausgeführt werden muss, unser main ist, wird es keine Abhängigkeiten finden. Daher registriert sich das Plugin im MANIFEST als JarLauncher-Hauptklasse.




    Ie Neben Code und Abhängigkeiten enthält es auch so etwas, in dem es einen JarLauncher gibt. Seine Aufgabe ist es, unser Hauptprogramm zu starten, aber vorher muss der Klassenpfad erstellt werden.
    Und woher weiß er, was unser Haupt ist? Es gibt eine Eigenschaft - Start-class.


    Ie Initialisieren Sie Ihre Anwendung in zwei Schritten. Im ersten Schritt gibt es den Klassenpfad des Standardglases. Alles, was dort liegt, org.springframework.bootwird im Klassenpfad liegen. Daher sind wir org.springframework.boot.loader.JarLauncherin der Hauptklasse voll gültig   . Und es fängt an, die Hauptklasse geht weiter. Es bildet den Klassenpfad, der sich in befindet BOOT-INF(es gibt einen lib-Daddy mit Abhängigkeiten und eine Daddy-Klasse mit den Klassen, die Sie in Ihrer Anwendung geschrieben haben).


    Wenn wir RavenApplication geschrieben haben, fallen die Eigenschaften in class BOOT-INFund alle Abhängigkeiten wie Tomcat und Bibliotheken fallen in BOOT-INF/lib/. Wenn JarLauncher einen Klassenpfad gebildet hat, ist ein zweiter Schritt erforderlich - die Klasse wird ausgeführt start-class. Der echte Frühling ContextSpringApplicationarbeitet bereits dort - genau der Fluss, den wir zuvor beschrieben haben.


    Woher weiß das Plugin, wer die Startklasse ist? Wir können explizit angeben, welche Klasse gestartet werden soll. Und dann wird dieses Plugin es als Start registrieren.


    Es stellt sich übrigens ein solches Namensparadox heraus. Wir schreiben es durch die Eigenschaft vor, die heißt mainClass, und in MANIFEST wird es sein Start-Class, weil dies mainClassimmer der JarLauncher ist.


    Aber wenn wir nicht explizit gesagt haben, wer mainClass ist, wie wird er das herausfinden? Er hat die folgende Logik. Das Spring Boot-Plugin durchsucht das Projekt und sucht nach mainClass:


    • Wenn es nur einen gibt, dann ist das sicher. Das Plugin macht es für sich - keine zwingende Einstellung der Hauptklasse;
    • Wenn es mehr als eine gibt, schaut es sich die Hauptzusammenfassung der Zusammenfassung an @SpringBootApplicationund wählt sie aus, es sei denn, es gibt natürlich eine zweite;
    • Wenn es eine zweite gibt, gibt es eine Ausnahme, die angibt, was die Hauptklasse registrieren muss. Andernfalls ist nicht klar, an wen Sie Ihren Jar-Nick-Namen mit der Anwendung binden sollen. Ie Die Anwendung wird einfach nicht gestartet oder gar nicht erstellt. Er wird sagen, dass es mehr als einen Kandidaten für die Hauptklasse gibt.
    • Wenn @SpringBootApplication nicht vorhanden ist, wird auch ein Fehler angezeigt.

    JarLauncher ist nicht immer da. Einige nutzen Tomcat und für sie gibt es WarLauncher, dank dem es möglich ist, War-Nick genau wie Jar-Nick auszuführen.


    Aber es gibt Menschen, für die es sogar schwierig ist java -jar. Kann man das vereinfachen? Du kannst Nun wollen wir mal sehen wie.


    <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
               <executable>true</executable>
            </configuration>
         </plugin>
      </plugins>
    </build>

    Wir können eine Option angeben <configuration>und darin <executable>true</executable>oder auch in Gradle, einfach einfacher:


    springBoot {
     executable = true
    }

    Wir geben diese Option an und die Dose verwandelt sich in eine ausführbare Dose. Unsere Anwendung wurde dafür bereits konfiguriert.


    Mal sehen, wie das überhaupt geht. Alle Windows-Benutzer können sich daran erinnern, dass Sie auf die Exe-Datei klicken können. Die Datei wird gestartet. Dasselbe passiert mit der Spring Boot-Anwendung, d. H. mit dem Glas, wenn Sie dies tun. Ich klicke nur bedingt darauf und es lief.
    Wie ist das passiert?


    Wir können unsere Datei öffnen (jar ist ein zip-Archiv, öffnen Sie es in einem Texteditor):




    Spring Boot zauberte etwas.


    Dies ist ein Bash-Skript, das jar-nickname startet. Der Witz ist, dass es am Anfang einen Interpreter gibt, mit dem das Skript ausgeführt werden kann #!/bin/bash. Und das ist die halbe Magie.


    Die zweite Hälfte der Magie ist am Ende des Skripts. Da gibt es exit 0eine Art Müll - unser Zip-Archiv ging von hier aus.




    Der Zauber ist, dass jedes Zip-Archiv ein spezielles Label hat - 0xf4ra. Wenn der Archivierer dieses Tag sieht, erkennt er, dass er den Start des Archivs gefunden hat.




    Davor kann alles sein (Bilder, Texte usw.).


    Das Laufen von Jar wird zur folgenden Operation:


    • Das System liest die erste Zeile - weil es sich nur um eine Datei handelt.
    • Sie sieht, dass es sagt "Lass mich durch Bash laufen" ( #!/bin/bash);
    • bash führt diese Datei aus.
    • es führt das Skript aus und erscheint exit 0;
    • Das Skript selbst erledigt es java -jarauf sich selbst - auf demselben Jar-Nickname ist es;
    • Nachdem der java -jarStandard-ZIP-Archivierer durch das Glas gelaufen ist, das Skript übersprungen hat, der Beginn des Archiv-Tags angezeigt wird, beginnt die Anwendung ab diesem Zeitpunkt.

    Schlussfolgerungen


    Die Schlussfolgerungen beziehen sich in erster Linie auf die Ansicht, dass Spring Boot viel Magie ist und dass dort viele Dinge getan werden, die später nicht gelöst werden können.


    Zuerst können Sie es herausfinden. Dies ist schwieriger als der Umgang mit Spring, da Spring ein kleiner Teil des Lebenszyklus von Spring Boot ist. Dies liegt an der Tatsache, dass er die Verantwortung für die Probleme übernommen hat, die der Entwickler nicht gerne selbst auf sich nimmt - Konfigurationen, Abhängigkeiten, Versionen und Start, die uns den Kopfschmerz nehmen. Übrigens, einige der Probleme, die mit der falschen Verwendung von Spring, Spring Boot, verbunden waren, nahmen auch auf.


    Zweitens sehen wir eine Anmerkung, @SpringBootApplicationin der diejenigen Best Practices gesammelt werden, die sich in guten Spring-Anwendungen befanden.


    Und die dritte ist die neue Infrastruktur, die das Problem der externen Abhängigkeiten löst, z. B. verschiedene Konfigurationen. Wir können eine Eigenschaft als Umgebungsvariable schreiben, wir können sie als var-Argument in unserer Anwendung schreiben, wir können sie als externe Ressource schreiben oder wir können JSON verwenden. In jedem Fall wird alles in die Anwendung geladen und wird durch @valueoder sogar durch einen neueren, sichereren Weg verfügbar sein , wie wir gezeigt haben. Unsere Konfigurationseigenschaften wurden zusammengestellt, wir haben einen Starter als Dienst bereitgestellt, die Leute haben es genommen und Komplimente gemacht, und sie hatten keine Probleme. Darüber hinaus bietet Spring selbst eine große Anzahl dieser Starter. Wir haben bereits gezeigt, dass sie sehr seltsam geschrieben sind, aber es gibt Gründe dafür.


    Die letzte Schlussfolgerung. Haben Sie keine Angst davor, im Darm herumzustochern, um die notwendigen Dinge zu verwenden. Und dann können Sie eigentlich nicht Spring, sondern Spring Boot anpassen und Ihre Starter schreiben. Und wenn es Probleme gibt, wissen Sie, wo Sie debuggen müssen, was zu beobachten ist und wie es behoben und repariert werden kann.




    Minute Werbung. Die Joker 2018 Konferenz findet am 19. und 20. Oktober statt, auf der Jewgeni Borisov zusammen mit Baruch Sadogursky einen Vortrag über "Die Abenteuer von Senor Holmes und Junior Watson in der Softwareentwicklungswelt" (Joker Edition) hält , und Kirill Tolkachev mit Maxim Gorelikov wird den Bericht "Micronaut vs Spring Boot." oder Wer ist der kleinste? " . Im Allgemeinen ist der Joker noch viele interessante und verdient Kontrolle Berichte. Tickets können auf der offiziellen Konferenz- Website erworben werden .