Evgeny
EvgenyBorisov Borisov (NAYA Technologies) und Kirill
tolkkv Tolkachev (Cyan.Finance,
Twitter ) sprechen am Beispiel eines Starters für eine imaginäre Eisenbank über die wichtigsten und interessantesten Momente von Spring Boot.

Der Artikel basiert auf dem
Bericht von Eugene und Cyril von unserer Joker 2017-Konferenz. Unter dem Schnitt befindet sich das Video- und Textprotokoll des Berichts.
Die Joker-Konferenz wird von vielen Banken gesponsert. Stellen wir uns also vor, dass die Anwendung, in der wir die Arbeit von Spring Boot und den von uns erstellten Starter untersuchen, mit der Bank verbunden ist.

Angenommen, Sie erhalten einen Auftrag für einen Antrag von der Eisenbank von Braavos. Eine gewöhnliche Bank überweist einfach Geld hin und her. Zum Beispiel so (wir haben eine API dafür):
http://localhost:8080/credit\?name\=Targarian\&amount\=100
Und in der Iron Bank muss vor dem Geldtransfer die API der Bank berechnen, ob eine Person es zurückgeben kann. Vielleicht wird er den Winter nicht überleben und es wird niemanden geben, der zurückkommt. Daher wird ein Dienst bereitgestellt, der die Zuverlässigkeit überprüft.
Wenn wir beispielsweise versuchen, Geld nach Targaryen zu überweisen, wird die Operation genehmigt:

Aber wenn Stark, dann nein:

Kein Wunder: Starks sterben zu oft. Warum Geld überweisen, wenn eine Person den Winter nicht überlebt?
Mal sehen, wie es innen aussieht.
@RestController @RequiredArgsConstructor public class IronBankController { private final TransferMoneyService transferMoney; private final MoneyDao moneyDao; @GetMapping("/credit") public String credit(@RequestParam String name, @RequestParam long amount) { long resultedDeposit = transferMoney.transfer(name, amount); if (resultedDeposit == -1) { return "Rejected<br/>" + name + " <b>will`t</b> survive this winter"; } return format( "<i>Credit approved for %s</i> <br/>Current bank balance: <b>%s</b>", name, resultedDeposit ); } @GetMapping("/state") public long currentState() { return moneyDao.findAll().get(0).getTotalAmount(); } }
Dies ist ein normaler String-Controller.
Wer ist für die Logik der Wahl verantwortlich, wem soll ein Darlehen gewährt werden und wem - nicht? Einfache Zeile: Wenn Sie Stark heißen, verraten wir Sie mit Sicherheit nicht. In anderen Fällen - wie viel Glück. Gewöhnliche Bank.
@Service public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) { return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } }
Alles andere ist nicht so interessant. Dies sind einige Anmerkungen, die die ganze Arbeit für uns erledigen. Alles ist sehr schnell.
Wo sind alle Hauptkonfigurationen? Es gibt nur einen Controller. In Dao ist es im Allgemeinen eine leere Schnittstelle.
public interface MoneyDao extends JpaRepository<Bank, String> { }
In Diensten - nur Übersetzungs- und Vorhersagedienste, an die Sie ausgeben können. Es gibt kein Conf-Verzeichnis. Tatsächlich haben wir nur application.yml (eine Liste derjenigen, die Schulden zurückzahlen). Und Haupt ist das häufigste:
@SpringBootApplication @EnableConfigurationProperties(ProphetProperties.class) public class MoneyRavenApplication { public static void main(String[] args) { SpringApplication.run(MoneyRavenApplication.class, args); } }
Wo ist also all die Magie verborgen?
Tatsache ist, dass Entwickler nicht gerne über Abhängigkeiten nachdenken, Konfigurationen konfigurieren, insbesondere wenn es sich um XML-Konfigurationen handelt, und darüber nachdenken, wie ihre Anwendung gestartet wird. Daher löst Spring Boot diese Probleme für uns. Wir müssen nur eine Bewerbung schreiben.
Abhängigkeiten
Das erste Problem, das wir immer hatten, ist ein Versionskonflikt. Jedes Mal, wenn wir verschiedene Bibliotheken verbinden, die auf andere Bibliotheken verweisen, treten Abhängigkeitskonflikte auf. Jedes Mal, wenn ich im Internet las, dass ich einen Entity-Manager hinzufügen muss, stellte sich eine Frage, und welche Version sollte ich hinzufügen, damit nichts kaputt geht?
Spring Boot löst das Problem von Versionskonflikten.
Wie bekommen wir normalerweise ein Spring Boot-Projekt (wenn wir nicht an einen Ort gekommen sind, an dem es bereits existiert)?
- oder gehen Sie zu start.spring.io , setzen Sie die Kontrollkästchen, die Josh Long uns beigebracht hat, klicken Sie auf das Download-Projekt und öffnen Sie das Projekt, in dem bereits alles vorhanden ist.
- oder verwenden Sie IntelliJ, wo dank der angezeigten Option die Kontrollkästchen im Spring Initializer direkt von dort aus gesetzt werden können.
Wenn wir mit Maven arbeiten, hat das Projekt die Datei pom.xml, in der sich ein übergeordnetes Spring Boot befindet, das als Spring
spring-boot-dependencies
. Es wird einen riesigen Block des Abhängigkeitsmanagements geben.
Ich werde jetzt nicht auf die Details von Maven eingehen. Nur zwei Worte.
Der Abhängigkeitsverwaltungsblock registriert keine Abhängigkeiten. Dies ist ein Block, mit dem Sie Versionen angeben können, falls diese Abhängigkeiten benötigt werden. Und wenn Sie im Abhängigkeitsverwaltungsblock eine Art von Abhängigkeit angeben, ohne die Version anzugeben, sucht Maven nach einem Abhängigkeitsverwaltungsblock, in dem diese übergeordnete Version im übergeordneten POM oder an einer anderen Stelle geschrieben ist. Das heißt, Wenn ich in meinem Projekt eine neue Abhängigkeit hinzufüge, werde ich die Version nicht mehr angeben, in der Hoffnung, dass sie irgendwo im übergeordneten Element angegeben ist. Und wenn es nicht im übergeordneten Element angegeben ist, führt dies mit Sicherheit zu keinem Konflikt mit irgendjemandem. In unserem Abhängigkeitsmanagement werden gut fünfhundert Abhängigkeiten angezeigt, die alle miteinander übereinstimmen.
Aber was ist das Problem? Das Problem ist, dass ich in meiner Firma zum Beispiel meinen eigenen Eltern-Pom habe. Was soll ich mit meinem Eltern-Pom tun, wenn ich Spring verwenden möchte?

Wir haben keine Mehrfachvererbung. Wir wollen unseren Pom verwenden und den Abhängigkeitsverwaltungsblock von außen erhalten.

Dies kann getan werden. Es reicht aus, den Stücklistenimport des Abhängigkeitsverwaltungsblocks zu registrieren.
<dependencyManagement> <dependencies> <dependency> <groupId>io.spring.platform</groupId> <artifactId>platform-bom</artifactId> <version>Brussels-SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Wer mehr über bom wissen will - siehe den Bericht "
Maven vs. Gradle ". Dort wurde das alles ausführlich erklärt.
Heutzutage ist es unter großen Unternehmen ziemlich in Mode geworden, so große Blöcke des Abhängigkeitsmanagements zu schreiben, in denen sie alle Versionen ihrer Produkte und alle Versionen von Produkten angeben, die ihre Produkte verwenden und die nicht miteinander in Konflikt stehen. Und das nennt man bom. Dieses Ding kann ohne Vererbung in Ihren Abhängigkeitsverwaltungsblock importiert werden.
Und so wird es in Gradle gemacht (wie immer das Gleiche, nur einfacher):
dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE' } }
Lassen Sie uns nun über die Abhängigkeiten selbst sprechen.
Was schreiben wir in die Bewerbung? Das Abhängigkeitsmanagement ist gut, aber wir möchten, dass die Anwendung über bestimmte Fähigkeiten verfügt, z. B. über HTTP zu antworten, eine Datenbank zu haben oder JPA zu unterstützen. Daher müssen wir jetzt nur noch drei Abhängigkeiten erhalten.
Früher sah es so aus. Ich möchte mit der Datenbank arbeiten und sie beginnt: Es wird eine Art Transaktionsmanager benötigt, dementsprechend wird das spring-tx-Modul benötigt. Ich brauche einen Ruhezustand, daher ist EntityManager, Hibernate-Core oder etwas anderes erforderlich. Ich konfiguriere alles über Spring, also brauche ich Spring Core. Das heißt, für eine einfache Sache musste man über ein Dutzend Abhängigkeiten nachdenken.
Heute haben wir Vorspeisen. Die Idee eines Starters ist, dass wir eine Abhängigkeit davon setzen. Zunächst aggregiert er die Abhängigkeiten, die für die Welt benötigt werden, aus der er stammt. Wenn es sich beispielsweise um einen Sicherheitsstarter handelt, denken Sie nicht darüber nach, welche Abhängigkeiten benötigt werden, sondern kommen sofort in Form von transitiven Abhängigkeiten zum Starter an. Wenn Sie mit Spring Data Jpa arbeiten, setzen Sie eine Abhängigkeit vom Starter und es werden alle Module angezeigt, die für die Arbeit mit Spring Data Jpa erforderlich sind.
Das heißt, Unser Pom sieht folgendermaßen aus: Es enthält nur die 3-5 Abhängigkeiten, die wir benötigen:
'org.springframework.boot:spring-boot-starter-web' 'org.springframework.boot:spring-boot-starter-data-jpa' 'com.h2database:h2'
Mit den aussortierten Abhängigkeiten ist alles einfacher geworden. Wir müssen jetzt weniger denken. Es gibt keinen Konflikt und die Anzahl der Abhängigkeiten hat abgenommen.
Kontexteinstellung
Lassen Sie uns über den nächsten Schmerz sprechen, den wir immer hatten - den Kontext festlegen. Jedes Mal, wenn wir eine Anwendung von Grund auf neu schreiben, nimmt die Konfiguration der gesamten Infrastruktur viel Zeit in Anspruch. Wir haben entweder in der XML- oder in der Java-Konfiguration viele sogenannte Infrastructure Beans registriert. Wenn wir mit dem Ruhezustand gearbeitet haben, brauchten wir die EntityManagerFactory-Bean. Viele Infrastruktur-Beans - Transaktionsmanager, Datenquelle usw. - Es war notwendig, mit den Händen einzustellen. Natürlich fielen sie alle in einen Zusammenhang.
Während des
Spring Ripper- Berichts haben wir den Kontext im Main erstellt. Wenn es sich um den XML-Kontext handelte, war er anfangs leer. Wenn wir den Kontext über
AnnotationConfigApplicationContext
, gab es einige Beanpost-Prozessoren, die die Beans gemäß den Annotationen konfigurieren konnten, aber der Kontext war auch fast leer.
Und jetzt gibt es
SpringApplication.run
und es ist kein Kontext sichtbar:
@SpringBootApplilcation class App { public static void main(String[] args) { SpringApplication.run(App.class,args); } }
Aber eigentlich haben wir einen Kontext.
SpringApplication.run
gibt uns einen Kontext zurück.

Dies ist ein völlig untypischer Fall. Früher gab es zwei Möglichkeiten:
- Wenn es sich um eine Desktop-Anwendung handelt, mussten Sie direkt in der Hauptsache mit Ihren Händen neu schreiben,
ClassPathXmlApplicationContext
auswählen usw.
- Wenn wir mit Tomcat zusammengearbeitet haben, gab es einen Servlet-Manager, der nach einigen Konventionen nach XML suchte und standardmäßig einen Kontext daraus erstellte.
Mit anderen Worten, der Kontext war irgendwie. Und wir haben immer noch einige Konfigurationsklassen an die Eingabe übergeben. Im Großen und Ganzen haben wir die Art des Kontexts gewählt. Jetzt haben wir nur noch
SpringApplication.run
, es nimmt Konfigurationen als Argumente und erstellt einen Kontext
Rätsel: Was können wir dort passieren?
Gegeben:RipperApplication.class
public… main(String[] args) { SpringApplication.run(?,args); }
Frage: Was kann dort noch übertragen werden?
Optionen:RipperApplication.class
String.class
"context.xml"
new ClassPathResource("context.xml")
Package.getPackage("conference.spring.boot.ripper")
Die AntwortDie Antwort lautet:
Die Dokumentation besagt, dass dort alles übertragen werden kann. Zumindest wird dies kompiliert und irgendwie funktionieren.

Das heißt, In der Tat sind alle Antworten richtig. Jeder von ihnen kann zum
String.class
, sogar
String.class
, und unter bestimmten Bedingungen müssen Sie nicht einmal etwas tun, damit es funktioniert. Aber das ist eine andere Geschichte.
Das einzige, was in der Dokumentation nicht gesagt wird, ist, in welcher Form wir dorthin geschickt werden sollen. Dies ist aber bereits aus dem Bereich des geheimen Wissens.
SpringApplication.run(Object[] sources, String[] args) # APPLICATION SETTINGS (SpringApplication) spring.main.sources= # class name, package name, xml location spring.main.web-environment= # true/false spring.main.banner-mode=console # log/off
SpringApplication
ist hier wirklich wichtig - weiter unten auf den Folien werden wir es mit Carlson haben.
Unser Carlson schafft eine Art Kontext basierend auf den Eingaben, die wir an ihn weitergeben. Ich erinnere Sie daran, wir geben ihm zum Beispiel so fünf wunderbare Optionen, dass Sie mit
SpringApplication.run
alles zum
SpringApplication.run
:
RipperApplication.class
String.class
"context.xml"
new ClassPathResource("context.xml")
Package.getPackage("conference.spring.boot.ripper")
Was macht
SpringApplication
für uns?

Als wir den Kontext durch
new
in main durch
new
, hatten wir viele verschiedene Klassen, die die
ApplicationContext
Schnittstelle implementieren:

Und welche Optionen gibt es, wenn Carlson den Kontext erstellt?
Es werden nur zwei Arten von Kontexten erstellt: entweder ein
WebApplicationContext
(
WebApplicationContext
) oder ein generischer Kontext (
AnnotationConfigApplicationContext
).

Die Auswahl des Kontexts basiert auf dem Vorhandensein von zwei Klassen im Klassenpfad:

Das heißt, die Anzahl der Konfigurationen hat nicht abgenommen. Um einen Kontext zu erstellen, können wir alle Konfigurationsoptionen angeben. Um den Kontext zu erstellen, kann ich ein grooviges Skript oder XML übergeben. Ich kann angeben, welche Pakete gescannt oder die mit einigen Anmerkungen gekennzeichnete Klasse übergeben werden sollen. Das heißt, ich habe alle Möglichkeiten.
Dies ist jedoch Spring Boot. Wir haben noch keinen einzigen Bin erstellt, keine einzige Klasse, wir haben nur main, und darin befindet sich unser Carlson -
SpringApplication.run
. Am Eingang erhält er eine Klasse, die mit einer Art Spring Boot-Anmerkung versehen ist.
Wenn Sie in diesen Kontext schauen, was wird dort passieren?
In unserer Anwendung gab es nach dem Anschließen eines Starterpaares 436 Behälter.

Fast 500 Bohnen, nur um mit dem Schreiben zu beginnen.

Als nächstes werden wir verstehen, woher diese Bohnen kamen.
Aber zuerst wollen wir das Gleiche tun.
Die Magie der Vorspeisen ist, dass wir nicht nur alle Probleme mit Suchtproblemen lösen, sondern auch nur 3-4 Vorspeisen miteinander verbinden und 436 Behälter haben. Wir würden 10 Starter verbinden, es würde mehr als 1000 Bins geben, da jeder Starter, mit Ausnahme von Abhängigkeiten, bereits Konfigurationen bringt, in denen einige notwendige Bins registriert sind. Das heißt, Sie sagten, dass Sie einen Starter für das Web möchten, also benötigen Sie einen Servlet-Dispatcher und
InternalResourceViewResolver
. Wir haben den jpa-Starter angeschlossen - wir benötigen die
EntityManagerFactory
Bean. Alle diese Bohnen befinden sich bereits irgendwo in den Starterkonfigurationen und kommen auf magische Weise ohne unser Zutun zur Anwendung.
Um zu verstehen, wie dies funktioniert, werden wir heute einen Starter schreiben, der auch Infrastruktur-Bins für alle Anwendungen bereitstellt, die diesen Starter verwenden.
Eisengesetz 1.1. Schicken Sie immer einen Raben

Schauen wir uns die Anforderung des Kunden an. Iron Bank hat viele verschiedene Anwendungen, die in verschiedenen Filialen ausgeführt werden. Kunden möchten, dass jedes Mal, wenn die Anwendung steigt, ein Rabe gesendet wird - Informationen, dass die Anwendung gestiegen ist.
Beginnen wir mit dem Schreiben des Codes in der Anwendung einer bestimmten Eisenbank (Eisenbank). Wir werden einen Starter schreiben, damit alle Iron Bank-Anwendungen, die von diesem Starter abhängen, automatisch einen Raben senden können. Wir erinnern uns, dass Starter es uns ermöglichen, Abhängigkeiten automatisch zu verschärfen. Und vor allem schreiben wir fast keine Konfiguration.
Wir machen einen Listener, der auf den zu aktualisierenden Kontext wartet (das letzte Ereignis), wonach er einen Raben sendet. Wir werden
ContextRefreshEvent
hören.
public class IronListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(" ..."); } }
Wir schreiben Listener in der Starterkonfiguration. Bisher wird es nur einen Listener geben, aber morgen wird der Kunde nach einigen anderen Infrastrukturelementen fragen, und wir werden sie auch in dieser Konfiguration schreiben.
@Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } }
Es stellt sich die Frage, wie die Konfiguration unseres Starters automatisch in alle Anwendungen passt, die diesen Starter verwenden.
Für alle Gelegenheiten gibt es ein "etwas aktivieren".

Wirklich, wenn ich von 20 Startern abhängig bin, muss ich
@Enable
? Und wenn der Starter mehrere Konfigurationen hat? Die Hauptkonfigurationsklasse wird mit
@Enable*
aufgehängt. Wie ist der Neujahrsbaum?

Tatsächlich möchte ich eine Art Umkehrung der Kontrolle auf der Abhängigkeitsebene erreichen. Ich möchte den Anlasser anschließen (damit alles funktioniert) und weiß nichts darüber, wie seine Innenseiten heißen. Daher werden wir spring.factories verwenden.

Also, was ist spring.factories
Die Dokumentation besagt, dass es solche spring.factories gibt, in denen Sie die Entsprechung von Schnittstellen angeben müssen und was Sie darauf laden müssen - unsere Konfigurationen. Und all dies wird magisch im Kontext erscheinen, während verschiedene Bedingungen auf sie wirken werden.

So erhalten wir die Umkehrung der Kontrolle, die wir brauchten.

Versuchen wir zu implementieren. Anstatt auf die Eingeweide des Starters zuzugreifen, den ich angeschlossen habe (nehmen Sie diese Konfiguration und diese ...), wird alles genau das Gegenteil sein. Der Starter hat eine Datei namens
spring.factories . In dieser Datei schreiben wir vor, welche Konfiguration dieses Starters für alle aktiviert werden soll, die ihn heruntergeladen haben. Wenig später werde ich erklären, wie genau dies in Spring Boot funktioniert - irgendwann werden alle Gläser gescannt und nach der Datei spring.factories gesucht.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ironbank.IronConfiguration
@Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } }
Jetzt müssen wir nur noch den Starter im Projekt anschließen.
compile project(':iron-starter')
In Maven müssen Sie ebenfalls die Abhängigkeit registrieren.
Wir starten unsere Anwendung. Der Rabe sollte in dem Moment abheben, in dem er aufsteigt, obwohl wir in der Anwendung selbst nichts getan haben. In Bezug auf die Infrastruktur haben wir natürlich den Starter geschrieben und konfiguriert. Aber aus Sicht des Entwicklers haben wir nur die Abhängigkeit verbunden und die Konfiguration erschien - der Rabe flog. Alles wie wir wollten.
Das ist keine Magie. Die Umkehrung der Kontrolle sollte keine Magie sein. So wie der Gebrauch des Frühlings keine Magie sein sollte. Wir wissen, dass dies in erster Linie ein Rahmen für die Umkehrung der Kontrolle ist. Da es eine Umkehrung der Steuerung für Ihren Code gibt, gibt es auch eine Umkehrung der Steuerung für Module.
@SpringBootApplication um den Kopf
Erinnern Sie sich an den Moment, als wir den Kontext hauptsächlich mit unseren Händen aufgebaut haben. Wir haben
new AnnotationConfigApplicationContext
und eine Konfiguration an die Eingabe übergeben, die eine Java-Klasse war. Jetzt schreiben wir auch
SpringApplication.run
und übergeben dort die Klasse, bei der es sich um die Konfiguration handelt. Nur ist sie mit einer weiteren ziemlich leistungsfähigen Anmerkung
@SpringBootApplication
, die die ganze Welt trägt.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { … }
Erstens gibt es im Inneren
@Configuration
, das heißt, es ist eine Konfiguration. Sie können dort
@Bean
schreiben und die Beans wie gewohnt registrieren.
Zweitens steht
@ComponentScan
darüber. Standardmäßig werden absolut alle Pakete und Unterpakete gescannt. Wenn Sie damit beginnen, Dienste im selben Paket oder in seinen
@Service
-
@Service
,
@RestController
- zu
@Service
,
@RestController
diese automatisch gescannt, da die Hauptkonfiguration Ihren Scanvorgang startet.
Eigentlich macht
@SpringBootApplication
nichts Neues. Er hat einfach alle Best Practices zusammengestellt, die in Spring-Anwendungen vorhanden waren. Dies ist jetzt eine Art Annotation-Komposition, einschließlich
@ComponentScan
.
Darüber hinaus gibt es noch Dinge, die vorher nicht da waren -
@EnableAutoConfiguration
. Dies ist die Klasse, die ich im Frühjahr vorgeschrieben habe.
@EnableAutoConfiguration
, wenn Sie schauen, trägt
@Import
mit sich:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
Die Hauptaufgabe von
@EnableAutoConfiguration
besteht darin, den Import
@EnableAutoConfiguration
, den wir in unserer Anwendung
@EnableAutoConfiguration
wollten, da seine Implementierung uns hätte zwingen müssen, den Namen einer Klasse vom Starter aus zu schreiben. Und das können wir nur aus der Dokumentation herausfinden. Aber alles sollte für sich sein.
Sie müssen auf diese Klasse achten. Es endet mit
ImportSelector
. Im regulären Frühling schreiben wir
Import(Some Configuration.class)
Konfiguration und sie wird wie alle ihre Abhängigen geladen. Dies ist
ImportSelector
, dies ist keine Konfiguration.
ImportSelector
alle unsere Starter in den Kontext. Es verarbeitet die Annotation
@EnableAutoConfiguration
aus spring.factories, die auswählt, welche Konfigurationen geladen werden sollen, und fügt die in IronConfiguration angegebenen Beans dem Kontext hinzu.

Wie macht er das?
Zunächst wird die einfache Dienstprogrammklasse SpringFactoriesLoader verwendet, die sich mit spring.factories befasst und alles aus den Fragen lädt. Er hat zwei Methoden, aber sie sind nicht sehr unterschiedlich.

Spring Factories Loader gab es in Spring 3.2, nur niemand hat es benutzt. Es wurde anscheinend als mögliche Entwicklung des Frameworks geschrieben. Und so entwickelte sich daraus Spring Boot, wo es so viele Mechanismen gibt, die die spring.factories-Konvention verwenden. Wir werden weiter zeigen, dass Sie neben der Konfiguration auch in spring.factories schreiben können - Listener, ungewöhnliche Prozessoren usw.
static <T> List<T> loadFactories( Class<T> factoryClass, ClassLoader cl ) static List<String> loadFactoryNames( Class<?> factoryClass, ClassLoader cl )
So funktioniert die Steuerungsinversion.
Wir scheinen dem offenen geschlossenen Prinzip zu entsprechen, nach dem es nicht notwendig ist, jedes Mal irgendwo etwas zu ändern. Jeder Starter bringt viele nützliche Dinge in das Projekt ein (bisher sprechen wir nur über die Konfigurationen, die er enthält). Und jeder Starter kann eine eigene Datei namens spring.factories haben. Mit seiner Hilfe erzählt er, was genau er trägt. Und in Spring Boot gibt es viele verschiedene Mechanismen, die von allen Startern das bringen können, was spring.factories sagen.Aber dieses ganze Schema hat eine Nuance. Wenn wir untersuchen, wie es im Frühling selbst funktioniert, werden wir org.springframework.boot:spring-boot-autoconfigure
feststellen, dass Menschen, die sich dieses ganze Starterschema ausgedacht haben, eine Abhängigkeit haben : Es gibt eine Linie mit META-INF / spring.factories mitEnableAutoConfiguration
und es hat viele Konfigurationen (als ich das letzte Mal nachgesehen habe, gab es dort ungefähr 80 nicht verbundene Autokonfigurationen). spring-boot-autoconfigure.jar/spring.factories</b> org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.\ ...
Das heißt, unabhängig davon, ob ich den Starter angeschlossen habe oder nicht, wenn ich mit Spring Boot arbeite, gibt es immer eines der JARs (JAR von Spring Boot selbst), in dem sich seine persönlichen spring.factories befinden, in denen 90 Konfigurationen geschrieben sind. Jede dieser Konfigurationen kann viele andere Konfigurationen enthalten, zum Beispiel CacheAutoConfiguration
genau so etwas - etwas, von dem wir wegkommen wollten: for (int i = 0; i < types.length; i++) { Imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports;
Darüber hinaus wird dann eine Karte statisch aus der dortigen Klasse entfernt, und die geladenen Konfigurationen (die nicht in diesem Frühjahr enthalten sind. Fabriken) sind in dieser Karte fest codiert. Sie werden nicht so leicht zu finden sein. private static final Map<CacheType, Class<?>> MAPPINGS; static { Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>(); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); addGuavaMapping(mappings); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); }
Das Interessanteste ist, dass sie alle beim Booten wirklich versuchen werden, zu booten.
Sie werden es versuchen. Aber:
Um die Zwischenergebnisse zusammenzufassen. Ein Teil der Konfigurationen - gute, freundliche, richtige Starter, die die Umkehrung der Kontrolle und das Prinzip des offenen Schließens beobachten - tragen ihre Federfabriken, in die ihre Eingeweide geschrieben sind. Wir werden genau das tun, im Prinzip können wir nichts anderes tun.Darüber hinaus gibt es einen weiteren Teil der im Spring Boot selbst vorgeschriebenen Konfigurationen, die immer geladen werden - es gibt 90 weitere. Es gibt auch 30 weitere Konfigurationen, die in Spring Boot einfach fest codiert sind.Das Ganze steigt und dann beginnen die Konfigurationen gefiltert zu werden. Ende 2013 gab es einen Berichtüber die Neuerungen in Spring 4, wo gesagt wurde, dass eine Annotation erschienen ist @Conditional
, die es ermöglicht, Bedingungen in ihre Annotationen zu schreiben, die sich auf Klassen beziehen, die true
oder zurückgeben false
. Abhängig davon werden die Beans entweder erstellt oder nicht. Da die Java-Konfiguration in Spring auch eine Bean ist, können Sie dort auch andere Bedingungen festlegen. Daher werden Konfigurationen berücksichtigt, aber wenn bedingte Rückgaben erfolgen false
, werden sie verworfen.Aber es gibt Nuancen. Dies führt zunächst zu einer Situation, in der sich der Behälter abhängig von einigen Umgebungseinstellungen befinden kann oder nicht.
Betrachten Sie dies als Beispiel.Eisengesetz 1.2. Rabe nur in Produktion
Der Kunde hat eine neue Anforderung. Rabe ist eine teure Sache, es gibt nicht sehr viele von ihnen. Daher müssen sie nur gestartet werden, wenn wir wissen, dass die Produktion gestiegen ist.
Dementsprechend sollte der Listener, der die Krähe startet, nur erstellt werden, wenn es sich um eine Produktion handelt. Lass es uns versuchen.Wir gehen in die Konfiguration und schreiben: @Configuration <b>@ConditionalOnProduction</b> public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } }
Wie entscheiden wir, ob es sich um eine Produktion handelt oder nicht? Ich hatte eine seltsame Firma, die sagte: "Wenn Windows auf dem Computer ist, bedeutet dies nicht Produktion, aber wenn nicht Windows, dann Produktion." Jeder hat seine eigene Bedingung.Insbesondere sagte die Eisenbank, dass sie dies manuell verwalten möchten: Wenn der Dienst hochgefahren wird, sollte ein Popup-Fenster angezeigt werden: "Produktion oder nicht". Eine solche Bedingung ist in Spring Boot nicht vorgesehen. @Retention(RUNTIME) @Conditional(OnProductionCondition.class) public @interface ConditionalOnProduction { }
Wir machen eine gute alte Popap: public class OnProductionCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return JOptionPane.showConfirmDialog(parentComponent: null, " ?") == 0; } }
Lass es uns versuchen.
Wir erhöhen den Service, klicken im Fenster auf Ja und der Rabe fliegt (ein Listener wird erstellt).Wir fangen wieder an, antworten Nein, der Rabe fliegt nicht.Die Annotation @Conditional(OnProductionCondition.class)
bezieht sich also auf die gerade geschriebene Klasse, in der es eine Methode gibt, die true
oder zurückgeben soll false
. Eine solche Klimaanlage kann unabhängig erfunden werden, was die Anwendung sehr dynamisch macht und es ihr ermöglicht, unter verschiedenen Bedingungen unterschiedlich zu arbeiten.Pazzler
Also haben @ConditionalOnProduction
wir geschrieben. Wir können verschiedene Konfigurationen vornehmen und diese auf Bedingung stellen. Angenommen, wir haben unseren eigenen Zustand und er ist beliebt @ConditionalOnProduction
. Und es gibt zum Beispiel 15 Bohnen, die nur in der Produktion benötigt werden. Ich habe sie mit dieser Anmerkung markiert.Frage: Die Logik, die herausfindet, ob es sich um eine Produktion handelt oder nicht, wie oft sollte sie funktionieren?Welchen Unterschied macht es? Nun, vielleicht ist diese Logik teuer, es braucht Zeit und Zeit ist Geld.Zur Veranschaulichung haben wir uns ein Beispiel ausgedacht: @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() { return new DragonGlassFactory(); } ... }
Hier haben wir zwei Behälter: einen regulären, einen Konfigurationsbereich. Beide sind mit bedingten Anmerkungen versehen - sie werden nur benötigt, wenn der Winter gekommen ist.Anmerkung kostet zweimal. Jeder Anruf beim Wetterzentrum in der Welt von Game of Thrones ist teuer - Sie müssen jedes Mal Geld bezahlen, um herauszufinden, wie das Wetter ist.
Wenn dies mit Caching funktioniert, wird die Logik nur einmal aufgerufen ( OnProductionCondition.class
dh es wird einmal aufgerufen , ein Fenster mit einer Auswahl wird einmal angezeigt - Produktion oder nicht). Konsequente Arbeit sieht logisch aus. Andererseits wird zu einem bestimmten Zeitpunkt eine Konfiguration erstellt, und eine weitere Bean kann in wenigen Sekunden erstellt werden, wenn sich etwas ändert. Was ist, wenn der Winter in diesen 5 Sekunden kommt?Richtige Antwortnicht sehr klar - 300 oder 400. Es gibt tatsächlich eine Art komplettes Spiel. Wir haben uns sehr lange umgesehen, um zuerst zu verstehen, was los war. Wie es passiert, ist ein separates Thema.Die Situation ist so. Wenn kondishn über der Spitzenklasse (Klasse @Component
, @Configuration
oder @Service
zusammen mit ihm kondishn ist), erfüllt er dreimal auf jeden solchen bin. Wenn diese Konfiguration im Starter registriert ist, dann zweimal. @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... }
Wenn der Behälter in der Konfiguration registriert ist, dann immer einmal. @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() { return new DragonGlassFactory(); } ... }
Daher hat dieses Rätsel keine genaue Antwort, da Sie herausfinden müssen, wo die Konfiguration geschrieben ist. Wenn es im Starter registriert ist, funktioniert sein Zustand aus irgendeinem Grund zweimal, und der Zustand für den Behälter funktioniert auf jeden Fall einmal, wir erhalten 300. Wenn sich die Konfiguration jedoch nicht im Starter befindet, startet nur sein Zustand dreimal plus ein weiteres Mal für den Behälter . Wir bekommen 400.Die Frage stellt sich: Wie funktioniert das überhaupt und warum ist es so? Und meine Antwort lautet nur:
Es spielt keine Rolle, wie es funktioniert. Es ist wichtig, Folgendes zu verstehen: Wenn Sie Ihre Bedingungsanmerkung schreiben, lohnt es sich, sie selbst und über ein statisches Feld zwischenzuspeichern, damit die Logik nicht oft aufgerufen wird. Denn selbst wenn Sie diese Anmerkung einmal verwendet haben, funktioniert die Logik mehr als einmal.Eisengesetz 1.3. Rabe an
Wir entwickeln unseren Starter weiter. Wir müssen irgendwie den Flug des Raben spezifizieren.
In welcher Datei schreiben wir Dinge für den Starter vor? Starter bringen eine Konfiguration, in der es Bohnen gibt. Wie sind diese Beans konfiguriert? Woher bekommen sie die Datenquelle, den Benutzer usw. Natürlich haben sie Standardeinstellungen für alle Gelegenheiten, aber wie lassen sie dies neu definieren? Es gibt zwei Möglichkeiten: application.properties
und application.yml
. Dort können Sie einige Informationen eingeben, die in IDEA immer noch wunderschön automatisch vervollständigt werden.Was macht unseren Starter schlimmer? Jeder, der es benutzt, sollte auch erkennen können, an welchen Adressen die Krähe fliegt - wir müssen eine Liste der Empfänger erstellen. Dies ist der erste.
Zweitens möchten wir, dass der Listener nicht erstellt und der Rabe nicht gesendet wird, wenn die Person keine Adressaten registriert hat. Wir benötigen eine zusätzliche Bedingung, um einen Listener zu erstellen, den der Rabe sendet. Das heißt,
Der Starter selbst wird benötigt, weil er neben einem Raben viele verschiedene Dinge haben kann. Aber wenn nicht geschrieben steht, wohin der Rabe fliegen soll, wird dieser einfach nicht geschaffen.Und das dritte - wir möchten auch automatisch vervollständigen, damit Leute, die unseren Starter zu sich gezogen haben, ein Kompliment für alle Eigenschaften erhalten, die der Starter liest.Für jede dieser Aufgaben haben wir unser eigenes Werkzeug. Zunächst müssen Sie sich jedoch die vorhandenen Anmerkungen ansehen. Vielleicht passt etwas zu uns? @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ...
In der Tat gibt es hier Dinge, die uns helfen werden. Zuallererst @ConditionalOnProperty
. Dies ist eine Bedingung, die funktioniert, wenn eine bestimmte Eigenschaft oder Eigenschaft mit einem in application.yml angegebenen Wert vorhanden ist. Ebenso müssen wir @ConfigurationalProperty
eine automatische Vervollständigung durchführen.Autocomplete
Wir müssen sicherstellen, dass alle Eigenschaften automatisch vervollständigt werden. Es wäre schön, wenn dies nicht nur bei Personen, die sie in ihrer application.yml registrieren, sondern auch in unserem Starter automatisch vervollständigt würde.Nennen wir unser Eigentum "Rabe". Er muss wissen, wohin er fliegen soll. @ConfigurationProperties("") public class RavenProperties { List<String> ; }
IDEA sagt uns, dass hier etwas nicht stimmt:
Die Dokumentation besagt, dass wir keine Abhängigkeit hinzugefügt haben (in Maven würde es keinen Verweis auf die Dokumentation geben, sondern eine Schaltfläche „Abhängigkeit hinzufügen“). Fügen Sie es einfach Ihrem Projekt hinzu. subproject { dependencies { compileOnly 'org.springframework.boot:spring-boot-configuration-processor' compile 'org.springframework.boot: spring-boot-starter' } }
Jetzt haben wir laut IDEA alles.Ich werde erklären, welche Art von Sucht wir hinzugefügt haben. Jeder weiß, was ein Anmerkungsprozessor ist. In einer vereinfachten Form kann dies in der Kompilierungsphase etwas bewirken. Zum Beispiel hat Lombok einen eigenen Annotationsprozessor, der in der Kompilierungsphase viel nützlichen Code generiert - Setter, Getter.Woher kommt die automatische Vervollständigung der Eigenschaft, die sich in den Anwendungseigenschaften befindet? Es gibt eine JSON-Datei, mit der IDEA arbeiten kann. Diese Datei beschreibt alle Eigenschaften, die IDEA automatisch kompilieren kann. Wenn Sie die Eigenschaft möchten, die Sie für den Starter erstellt haben, kann IDEA auch automatisch kompiliert werden. Sie haben zwei Möglichkeiten:- Sie können diesen JSON manuell selbst aufrufen und in einem bestimmten Format hinzufügen.
- Sie können den Anmerkungsprozessor von Spring Boot aus aufrufen, wodurch dieses JSON-Element in der Kompilierungsphase selbst generiert werden kann. Welche Eigenschaften dort hinzugefügt werden sollen, wird durch die magische Spring Boot-Annotation bestimmt, mit der wir Klassen markieren können, die Eigentümer sind. In der Kompilierungsphase findet der Anmerkungsprozessor Spring Boot alle mit Tags versehenen Klassen
@ConfigurationalProperties
, liest die Namenseigenschaft daraus und generiert JSON. Als Ergebnis erhält jeder, der vom Starter abhängig ist, diesen JSON als Geschenk.
Sie müssen sich auch daran erinnern, @EnableConfigurationProperties
dass diese Klasse in Ihrem Kontext als Bean angezeigt wird. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction public RavenListener ravenListener() { return new RavenListener(); } }
Es sieht nicht alles sehr gut aus, aber Sie müssen dies tun, damit es etwas früher als der Rest der Beans erscheint (da der Rest der Beans seine Eigenschaft verwendet, um sich selbst zu konfigurieren).Infolgedessen mussten zwei Anmerkungen eingefügt werden:@EnableConfigurationProperties
durch Angabe, wessen Eigenschaften;
@ConfigurationalProperties
sagen, was für ein Präfix.
Und man darf Getter und Setter nicht vergessen. Sie sind auch wichtig, sonst funktioniert nichts - Action klettert nicht.Als Ergebnis haben wir eine Datei, die im Prinzip manuell geschrieben werden kann. Aber niemand schreibt gerne manuell. { "hints": [], "groups": [ { "sourceType": "com.ironbank.RavenProperties", "name": "", "type": "com.ironbankRavenProperties" } ], "properties": [ { "sourceType": "com.ironbank.RavenProperties", "name": ".", "type": "java.util.List<java.lang.String>" } ] }
Adresse für Rabe
Wir haben den ersten Teil der Aufgabe erledigt - wir haben einige Eigenschaften bekommen. Aber noch ist niemand mit diesen Eigenschaften verwandt. Jetzt müssen sie als Bedingung festgelegt werden, um unseren Hörer zu erstellen. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener() { return new RavenListener(); } }
Wir haben eine weitere Bedingung hinzugefügt - ein Rabe sollte nur unter der Bedingung erstellt werden, dass irgendwo jemand gesagt hat, wohin er fliegen soll.Jetzt schreiben wir in application.yml, wohin wir fliegen sollen. spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---: - : -: , : true
Es bleibt logisch vorzuschreiben, dass er dorthin fliegt, wo es ihm gesagt wurde.Dazu können wir einen Konstruktor generieren. Die neue Feder hat eine Konstruktoreinspritzung - dies ist der empfohlene Weg. Eugene lässt @Autowired
alles durch Reflexion in der Anwendung erscheinen. Ich liebe es, den Konventionen zu folgen, die der Frühling bietet: @RequiredArgsConstructor public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } }
Aber es ist nicht kostenlos. Einerseits bekommen Sie nachweisbares Verhalten, andererseits bekommen Sie einige Hämorrhoiden. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Nirgendwo gibt es eine @Aurowired
, mit Spring 4.3 können Sie es nicht installieren. Wenn es einen einzelnen Konstruktor gibt, ist dies der Fall @Aurowired
. In diesem Fall wird eine Annotation verwendet @RequiredArgsConstructor
, die einen einzelnen Konstruktor generiert. Dies entspricht diesem Verhalten: public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } }
Spring empfiehlt, auf diese Weise zu schreiben oder Lombok zu verwenden. Jürgen Holler, der seit 2002 80% des Spring-Codes schreibt, empfiehlt Ihnen, @Aurowired
ihn so einzustellen , dass er sichtbar ist (ansonsten sehen die meisten Leute und sehen keine Injektion). public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Aurowired public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } }
Wie haben wir für diesen Ansatz bezahlt? Wir mussten RavenProperties
die Java-Konfiguration ergänzen . Und wenn ich es @Aurowired
über das Feld lege , müsste nichts geändert werden.Also werden die Krähen ausgesandt. Wir haben die Aufgabe abgeschlossen, die es den Benutzern unseres Starters ermöglichte, Komplimente in ihren Konfigurationen zu machen, während wir einen Behälter haben, der sich abhängig von diesen Konfigurationen ein- und ausschaltet.Eisengesetz 1.4. Benutzerdefinierter Rabe
Es kommt vor, dass Sie das Verhalten des Starters anpassen müssen. Zum Beispiel haben wir unseren eigenen schwarzen Raben. Und wir brauchen einen weißen, der raucht, und wir wollen ihn senden, damit die Leute Rauch am Horizont sehen.Gehen wir von der Allegorie zum wirklichen Leben. Der Starter brachte mir ein paar Infrastrukturbohnen, und das ist großartig. Aber ich mag nicht, wie sie konfiguriert sind. Ich bin in die Anwendungseigenschaften gekommen und habe dort etwas geändert, und jetzt gefällt mir alles. Es gibt jedoch Situationen, in denen die Einstellungen so kompliziert sind, dass es einfacher ist, die Datenquelle selbst zu registrieren, als die Anwendungseigenschaften herauszufinden. Das heißt, wir möchten die Datenquelle selbst in dem vom Starter empfangenen Bin registrieren. Was wird dann passieren?Ich habe selbst etwas registriert und der Starter hat mir meine Datenquelle gebracht. Habe ich jetzt zwei Oder wird man einen vernichten (und welchen?)Wir möchten Ihnen eine weitere Bedingung zeigen, die es dem Starter ermöglicht, nur dann eine Art Behälter mitzubringen, wenn die Person, die den Starter verwendet, keinen solchen Behälter hat. Wie sich herausstellte, ist dies völlig trivial.Es gibt eine Vielzahl von Bedingungen, die uns bereits gestellt wurden: @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ...
Im Prinzip @ConditionalOnMissingBean
gibt es auch, also verwenden Sie einfach die fertige. Fahren wir mit der Konfiguration fort, in der wir angeben, dass sie nur erstellt werden soll, wenn noch niemand einen solchen Bin erstellt hat. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnMissingBean</b> public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Wenn Sie die meisten Starter öffnen, werden Sie sehen, dass jeder Behälter, jede Konfiguration mit einem solchen Bündel von Anmerkungen aufgehängt ist. Wir versuchen nur, ein Analogon zu machen.Beim Versuch, den Raben zu starten, ging nicht, aber das Ereignis erschien, das wir in unserem neuen Hörer geschrieben haben - MyRavenListener
.
Hier gibt es zwei wichtige Punkte.Der erste Punkt ist, dass wir uns von unserem bestehenden Hörer begeistert haben und dort keinen Hörer geschrieben haben: @Component public class MyRavenListener implements ApplicationListener { public MyRavenListener(RavenProperties ravenProperties) { super(ravenProperties); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println("event = " + event); }); } }
Zweitens - wir haben es mit Hilfe der Komponente geschafft. Wenn wir es in einer Java-Konfiguration gemacht haben, d.h. würde die gleiche Klasse wie eine Konfigurations-Bean registrieren, würde nichts für uns funktionieren.Wenn ich bereinige extends
und nur eine Art Anwendungs-Listener mache, @ConditionalOnMissingBean
funktioniert das nicht. Aber seitdem
Die Klasse wird auch aufgerufen. Wenn wir versuchen, sie zu erstellen, können wir schreiben ravenListener
- genau wie in unserer Konfiguration. Oben haben wir uns auf die Tatsache konzentriert, dass der Bean-Name in der Java-Konfiguration der Name der Methode ist. In diesem Fall erstellen wir einen Bin namens ravenListener
.Warum müssen Sie das alles wissen? Mit Spring Boot ist normalerweise alles super, aber nur am Anfang. Wenn das Projekt voranschreitet, erscheint ein Starter, der zweite, der dritte. Sie fangen an, einige Dinge mit Ihren Händen zu schreiben, weil selbst der beste Starter nicht das gibt, was Sie brauchen. Und Behälterkonflikte beginnen. Daher ist es gut, wenn Sie zumindest eine allgemeine Vorstellung davon haben, wie Sie sicherstellen können, dass keine Bohne erstellt wird, und wie Sie die Bohne in Ihrem Haus registrieren, damit der Starter keinen Konflikt verursacht (oder wenn Sie zwei Starter haben, die ein und dasselbe bringen den gleichen Behälter, damit sie nicht miteinander in Konflikt stehen). Um den Konflikt zu lösen, schreibe ich meine Bean, die sicherstellt, dass weder die erste noch die zweite erstellt wird.Darüber hinaus ist ein Bohnenkonflikt eine gute Situation, weil Sie es sehen. Wenn wir dieselben Bin-Namen angegeben haben, liegt kein Konflikt vor. Eine Bohne überschreibt einfach eine andere. Und Sie werden lange verstehen, wo was war. Wenn wir beispielsweise eine Art Datenquelle erstellen @Bean
, wird die vorhandene Datenquelle überschrieben @Bean
.Übrigens, wenn der Starter das trägt, was Sie nicht benötigen, stellen Sie einfach einen Behälter mit derselben ID her und fertig. Richtig, wenn der Starter in einer Version den Namen der Methode ändert, dann ist es das, Ihr Bin wird wieder zwei sein.ConditionalOnPuzzler
Wir haben @ConditionalOnClass
, @ConditionalOnMissingBean
kann es zu schreiben Klassen sein. Betrachten Sie als Beispiel die Ausführungskonfiguration.Es gibt Seife, es gibt ein Seil - wir hängen am Galgen. Es gibt einen Stuhl und eine Strömung - es ist logisch, eine Person auf einen Stuhl zu setzen. Es gibt eine Guillotine und gute Laune - das heißt, Sie müssen Köpfe hacken. @Configuration public class { @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public () { return new ("..."); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public c() { return new (" "); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public () { return new (" "); } }
Wie werden wir ausführen?Frage: Wie können Typanmerkungen im Allgemeinen @ConditionalOnMissingClass
funktionieren?Angenommen, ich habe eine Methode, die einen Galgen erzeugt. Ein Galgenbehälter sollte jedoch nur erstellt werden, wenn Seife und Seil vorhanden sind. Aber es gibt keine Seife. Wie kann ich verstehen, dass es keine Seife oder nur ein Seil gibt. Was passiert, wenn ich versuche, Anmerkungen aus einer Methode zu lesen, und diese Anmerkungen sich auf Klassen beziehen, die dies nicht sind? Darf ich solche Anmerkungen machen?Antwortoptionen:- ClassDefNotFound? , . - , ClassDefNotFound , reflection- , conditional as long as;
- , . reflection . , .
- ;
- .
Die Antwort: , . reflection . exception, , — . reflection? , , , , — ClassDefNotFound
.
Dies funktioniert mit ASM. Wenn Spring dies durch Reflexion sieht - überhaupt nichts -, analysiert er den Bytecode bedingt und manuell. Er liest die Datei, um diese Datei nicht vorzeitig herunterzuladen, und versteht, was @Conditional
mit Seife und Seil da ist. Er kann bereits separat prüfen, ob diese Klassen im Kontext vorhanden sind. Bei ASM geht es jedoch nicht um Geschwindigkeit. Dies ist eine Gelegenheit, eine Klasse zu lesen, ohne sie zu laden, und die Methodeninformationen zu verstehen.Jürgen Hoeller empfiehlt jedoch, nicht an Klassennamen gebunden zu sein und Bedingungen vorzuschreiben, obwohl es eine Anmerkung gibt OnMissingClass
, die den Klassennamen (String) als Parameter verwenden kann. Wenn Sie dieser Empfehlung folgen, funktioniert alles schneller und ASM wird nicht benötigt. Aber nach der Quelle zu urteilen, tut das niemand.Eisengesetz 1.5. Schalten Sie die Krähe ein und aus
Wir brauchten eine andere Eigenschaft - die Möglichkeit, die Krähe manuell zu aktivieren oder zu deaktivieren. Um niemanden garantiert zu schicken. Dies ist die letzte Bedingung, die wir Ihnen zeigen.Unser Starter gibt nichts außer einem Raben. Daher fragen Sie sich vielleicht, warum Sie es ein- und ausschalten können. Können Sie es einfach nicht nehmen? Aber im zweiten Teil werden zusätzliche nützliche Dinge in diesen Starter gesteckt. Insbesondere wird ein Rabe möglicherweise nicht benötigt - er ist teuer und kann ausgeschaltet werden. Gleichzeitig ist es nicht sehr gut, den letzten Punkt zu entfernen, an den es gesendet werden soll - es sieht aus wie eine Krücke.Deshalb werden wir alles durchmachen @ConditionalOnProperty(".")
. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Und er schwört uns, dass dies nicht möglich ist: Doppelte Anmerkung. Das Problem ist, dass eine Annotation mit einigen Parametern nicht wiederholbar ist. Wir können dies nicht für zwei Eigenschaften tun.Wir haben Methoden für diese Annotation, es gibt String und dies ist ein Array - Sie können dort mehrere Eigenschaften angeben. @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; boolean relaxedNames() default true; }
Und alles ist gut, bis Sie versuchen, den Wert für jedes Element in diesem Array separat anzupassen. Wir haben eine Eigenschaft
, die false
eine andere Eigenschaft sein sollte und ist, die string
einen bestimmten Wert haben sollte. Sie können jedoch nur einen Wert für alle Eigenschaften angeben.Das heißt, Sie können dies nicht tun: @ConditionalOnProduction @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".",havingValue="false") public IronBankApplicationListener applicationListener() { ... }
@ConditionalOnProduction @ConditionalOnProperty( name = { ".", ".", "." }, havingValue = "true" ) public IronBankApplicationListener applicationListener() { ... }
Das hier hervorgehobene ist kein Array.Es gibt eine Perversion, mit der Sie mit mehreren Eigenschaften arbeiten können, jedoch nur mit einem Wert: AllNestedConditions
und AnyNestedCondition
.
Es sieht ehrlich gesagt seltsam aus. Aber es funktioniert. Versuchen wir, eine Konfiguration vorzunehmen - eine neue Bedingung, die sowohl .
als auch berücksichtigt .
. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnRaven public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Wir setzen unsere Anmerkung @Conditional()
und hier müssen wir eine Klasse anmelden. @Retention(RUNTIME) @Conditional({OnRavenCondional.class}) public @interface CondionalOnRaven { }
Wir schaffen es. public class OnRavenCondional implements Condition { }
Außerdem mussten wir eine Art Konditionierung implementieren, aber wir können dies möglicherweise nicht tun, da wir die folgende Anmerkung haben: public class CompositeCondition extends AllNestedConditions { @ConditionalOnProperty( name = ".", havingValue = "false") public static class OnRavenProperty { } @ConditionalOnProperty( name = ".enabled", havingValue = "true", matchIfMissing = true) public static class OnRavenEnabled { } ... }
Wir haben eine zusammengesetzte Conditional
, die auch von einer anderen Klasse geerbt wird - entweder AllNestedConditions
oder AnyNestedCondition
- und die andere Klassen enthält, die gewöhnliche Anmerkungen mit Gewürzen enthalten. Das heißt,
stattdessen @Condition
müssen wir angeben: public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } }
In diesem Fall müssen Sie einen Konstruktor erstellen.Jetzt müssen wir hier statische Klassen erstellen. Wir machen eine Art Klasse (nennen wir es R). public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } public static class R {} }
Wir machen unseren Wert
(es muss genau sein true
). public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(".") public static class R {} @ConditionalOnProperty(value= ".", havingValue = "true") public static class C {} }
Um dies zu wiederholen, merken Sie sich einfach den Klassennamen. Der Frühling hat gute Java-Docks. Sie können IDEA verlassen, das Java-Dock lesen und verstehen, was zu tun ist.Wir setzen unsere @ConditionalOnRaven
. Im Prinzip können Sie beide und @ConditionalOnProduction
und einschließen @ConditionalOnMissingBean
, aber jetzt werden wir dies nicht tun. Mal sehen, was passiert ist. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnRaven @ConditionalOnMissingBean public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
In Abwesenheit eines
Raben sollte nicht fliegen. Er flog nicht.Ich möchte nicht wetten
, weil wir zuerst eine automatische Vervollständigung vornehmen müssen - dies ist eine unserer Anforderungen. @Data @ConfigurationalProperties("") public class RavenProperties { List<String> ; boolean ; }
Das ist alles.
Standardmäßig false
setzt true
aufapplication.yml: jpa.hibernate.ddl-auto: validate ironbank: ---: - : : , : true
Wir starten und unser Rabe fliegt.Auf diese Weise können wir zusammengesetzte Anmerkungen erstellen und eine beliebige Anzahl vorhandener Anmerkungen einfügen, auch wenn diese nicht wiederholbar sind. Dies funktioniert in jedem Java. Es wird uns retten.Im zweiten Teil des Artikels, der in den nächsten Tagen veröffentlicht wird, konzentrieren wir uns auf die Profile und Feinheiten beim Starten der Anwendung.
Minute der Werbung.
Die Joker 2018-Konferenz findet vom 19. bis 20. Oktober statt, auf der Jewgeni Borisow zusammen mit Baruch Sadogursky einen Vortrag mit dem Titel „Die Abenteuer von Senor Holmes und Junior Watson in der Welt der Softwareentwicklung [Joker Edition]“ hält . Kirill Tolkachev und Maxim Gorelikov präsentieren den Bericht „Micronaut vs Spring Boot Oder wer ist hier der Kleinste? “ .
Im Allgemeinen ist der Joker noch viele interessante und verdient Kontrolle Berichte. Tickets können auf der offiziellen Website der Konferenz gekauft werden.Und wir haben auch eine kleine Umfrage für Sie!