Wie entscheidet Spring Boot, dass die Infrastruktur für einen REST Service oder eine Verbindung zu einer MongoDB benötigt wird? Werfen wir einen Blick hinter die Kulissen des Auto-Configuration-Konzepts und betrachten im Anschluss, wie eigene Auto Configurations erstellt werden können.
Artikelserie
Teil 1: Auto Configuration begreifen und selbst erstellen
Typischerweise werden Spring Boot und die Features in Form von Tutorials erklärt, in denen in einer Klasse die komplette Infrastruktur für einen REST Service bereitgestellt wird. Listing 1 zeigt ein solches Beispiel.
Listing 1: Einfaches Spring-Boot-Beispiel
@SpringBootApplication @RestController public class SimpleWebApplication { @GetMapping("/hello") public String getHello() { return "Hello World!"; } public static void main(String[] args) { SpringApplication.run(SimpleWebApplication.class, args); } }
Natürlich ist es kein guter Stil, den REST-Controller-Code direkt in die Startklasse zu integrieren. Dennoch ist es ein eindrucksvolles Beispiel für die Mächtigkeit und gleichzeitig den Komfort von Spring Boot. Was Spring Boot hierbei leistet, ist durchaus bemerkenswert. Denn basierend auf dem Code in Listing 1 sowie den Abhängigkeiten des Projekts muss Spring Boot die notwendige Infrastruktur erkennen, konfigurieren und bereitstellen. In diesem speziellen Fall einen Web Application Server, das Marshalling und Unmarshalling von REST Requests und nicht zuletzt das Routing auf die korrekten Methoden.
Und an dieser Stelle enden die meisten Tutorials und lassen uns beeindruckt vom Komfort, den Spring Boot uns bietet, zurück. Aber warum funktioniert der obenstehende Code? Dieser Frage gehen wir im Folgenden nach. Allerdings benötigen wir dafür ein gutes Verständnis für ein grundlegendes Konzept: Annotationskomposition.
Stay tuned
Regelmäßig News zur Konferenz und der Java-Community erhalten
Annotationskomposition
Spring Boot verwendet an vielen Stellen verschiedenste Annotationen. Nehmen wir das Beispiel aus Listing 1, sehen wir bereits drei Annotationen: @SpringBootApplication, @RestController und @GetMapping. Annotationen scheinen daher ein wichtiger Bestandteil von Spring Boot zu sein. Diese Annotationen basieren auf einem Konzept, das nicht Spring-Boot-spezifisch ist, sondern aus dem Spring Framework selbst stammt: „Meta-Annotationen“ und darauf aufbauend Annotationskomposition.
Beginnen wir mit Meta-Annotationen: dabei handelt es sich um Annotationen, die auf andere Annotationen angewendet werden können. Betrachten wir die @Service-Annotation von Spring, sehen wir, dass @Service mit @Component annotiert ist. Da @Component auf eine Annotation angewendet werden kann, handelt es sich dabei um eine Meta-Annotation.
Was ist nun die Annotationskomposition? Betrachten wir hierfür die Annotation @RestRepository. Bei dieser fällt auf, dass sie mit @Controller und @ResponseBody annotiert wurde. Somit sind @Controller und @ResponseBody beides ebenfalls Meta-Annotationen. Damit ist @RestController eine Komposition aus @Controller und @ResponseBody.
Das Meta-Annotations-Modell (Kasten: „Exkurs: Java-Meta-Annotationen“) ist dabei keineswegs eine Erfindung des Spring Frameworks. Vielmehr ist es ein Konzept, das Java selbst bietet.
Exkurs: Java-Meta-Annotationen
Java setzt bei der Erstellung eigener Annotationen auf das Meta-Annotations-Modell. Das Verhalten und die Möglichkeiten werden durch Meta-Annotationen ausgedrückt. Dabei gibt es verschiedenste, in der Java Runtime Library vordefinierte Meta-Annotationen. Die meistverwendeten Annotationen werden im Folgenden näher beleuchtet.
@Target
Annotationen lassen sich auf unterschiedlichste Elemente anwenden, unter anderem bei einer Typdeklaration (Klassen, Interfaces, Enums), Feldern, Methoden und noch einigen Stellen mehr. Mit der @Target-Annotation wird eingeschränkt, worauf die Annotation angewendet werden kann. Möchte man beispielsweise eine Annotation nur auf Felder beschränken, kann das mittels @Target({ElementType.FIELD}) erreicht werden.
@Retention
Standardmäßig werden Annotationen in der .class-Datei erhalten, jedoch nicht zur Laufzeit geladen. Möchte man die Annotation mittels Reflection auslesen, muss das mittels @Retention(RetentionPolicy.RUNTIME) definiert werden.
@Documented
Vereinfacht ausgedrückt, definiert @Documented, ob die eigene Annotation in der Javadoc-Dokumentation einer annotierten Klasse auftaucht. Die Bedeutung von @Documented geht jedoch darüber hinaus und definiert, dass eine Annotation einen Teil des öffentlichen Kontrakts darstellt – und hat damit denselben Stellenwert wie die Methoden eines Public API.
@Inherited
Standardmäßig werden Annotationen nicht vererbt. Wurde eine Klasse A mit der Annotation @X versehen, gilt die Annotation nicht für eine von A abgeleitete Klasse B. Soll die Annotation @X auch für Unterklassen gültig sein, kann @Inherited das für die Annotation @X bewirken.
Auf den Spuren der Spring-Boot-Magie
Nachdem wir das grundlegende Konzept der Meta-Annotationen betrachtet haben, schauen wir nun tiefer in die Magie von Spring Boot. Kehren wir hierzu zurück zu dem Beispiel aus Listing 1. Dort finden wir die zentrale Annotation von Spring Boot: @SpringBootApplication. Bei @SpringBootApplication handelt es sich um eine Annotationskomposition, die mit mehreren Annotationen meta-annotiert wird. In Listing 2 ist ein Auszug aus dem Quelltext von @SpringBootApplication zu sehen.
Listing 2: @SpringBootApplication
/* ... */ @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 { /* ... */ }
Die ersten vier Annotationen sind Meta-Annotationen der Java Runtime Library, deren Bedeutung für die aktuelle Betrachtung zweitrangig sind. Für uns sind an dieser Stelle die drei darauffolgenden Annotationen interessanter: @SpringBootConfiguration, @EnableAutoConfiguration und @ComponentScan. Diese Annotationen sind selbst ebenfalls wiederum meta-annotiert, was zu einer Hierarchie führt, die in Abbildung 1 zu sehen ist.
Mittels @ComponentScan wird definiert, dass im Klassenpfad nach mit @Component annotierten Klassen gesucht werden soll. Über diesen Weg findet Spring beispielsweise mit @Service annotierte Klassen. @SpringBootConfiguration ist mit @Configuration meta-annotiert, was damit auch bedeutet, dass @SpringBootApplication ebenfalls eine Spring-Konfiguration darstellt. Dieses Detail ist der Grund, weshalb es möglich ist, in der mit @SpringBootApplication annotierten Klasse direkt @Bean-Definitionen zu integrieren.
Auch wenn die genannten Annotationen interessant sind, so sind wir doch auf der Suche nach der Magie von Spring Boot, die automatisch eine passende Anwendungskonfiguration bereitstellt. Und dabei sticht direkt eine Annotation ins Auge: @EnableAutoConfiguration. Wir sind im Kern der Spring-Boot-Magie angekommen! Natürlich ist es nicht die Annotation selbst, die für die Magie sorgt, sondern ein @Import, der auf AutoConfigurationImportSelector verweist. Üblicherweise wird @Import verwendet, um weitere Spring-Konfigurationen in den Application Context aufzunehmen. Neben mit @Configuration annotierten Klassen darf @Import jedoch auch auf Klassen verweisen, die entweder ImportSelector oder ImportBeanDefinitionRegistrar implementieren. Beides sind Spring-Interfaces, die es ermöglichen, den Aufbau des Application Context zu beeinflussen.
Für das Verständnis von Spring Boot ist der ImportSelector interessant, denn die Klasse AutoConfigurationImportSelector implementiert genau dieses Interface. Ein ImportSelector kann programmatisch entscheiden, welche Spring-Konfigurationen dem Application Context hinzugefügt werden sollen. Dieser Mechanismus wird für das Laden der Auto Configurations verwendet. Was genau Auto Configurations eigentlich sind und wie sie funktionieren, betrachten wir etwas später in diesem Artikel, denn vorher sollten wir herausfinden, woher sie überhaupt stammen.
Der AutoConfigurationImportSelector verwendet hier ein weiteres, weniger bekanntes Spring-Feature: den SpringFactoriesLoader und die damit verbundenen META-INF/spring.factories-Dateien. Bei den spring.factories handelt es sich um Dateien im Properties-Format, mit einem Klassennamen als Key und einer Liste von assoziierten Klassen, jeweils durch ein Komma getrennt. Listing 3 zeigt einen Auszug aus der META-INF/spring.factories von Spring Boot.
Listing 3: Auszug aus META-INF/spring.factories
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ # ...
Wichtig ist hierbei, dass es nicht nur eine einzige META-INF/spring.factories gibt, sondern mehrere. Sucht man in der IDE der Wahl nach spring.factories in einem Spring-Boot-basierenden Projekt, wird man mehrere Dateien finden. Die Aufgabe des SpringFactoriesLoader ist es, diese zu aggregieren und transparent zur Laufzeit bereitzustellen. Für den Aufrufer ist es unerheblich, ob die Liste der Klassennamen aus einer Datei stammen oder aus mehreren. Das bildet die Grundlage für die Umsetzung eigener Auto Configurations und Starter.
Versuchen Sie selbst einmal, alle verfügbaren Auto Configurations aufzulisten. Hierfür verwenden wir die gleiche Logik, wie sie auch der AutoConfigurationImportSelector verwendet. Das entsprechende Codebeispiel ist in Listing 4 zu finden.
Listing 4: SpringFactoriesExploration
package de.digitalfrontiers.springboot.springfactories; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.core.io.support.SpringFactoriesLoader; import java.util.List; public class SpringFactoriesExplorationApplication { public static void main(String[] args) { final List<String> candidates = SpringFactoriesLoader .loadFactoryNames(EnableAutoConfiguration.class, null); candidates.forEach(System.out::println); } }
Auto Configuration
Nachdem wir nun wissen, wie die Auto Configurations gefunden werden, stellt sich natürlich die Frage, was genau eine Auto Configuration eigentlich ist. Die Antwort ist überraschend einfach: Alle Einträge, die in den spring.factories für EnableAutoConfiguration auftauchen, sind selbst normale Spring-Konfigurationen (Klassen, annotiert mit @Configuration). Was Auto Configurations so besonders macht, ist der Zeitpunkt, zu dem sie geladen werden. Der AutoConfigurationImportSelector wird sehr spät im Aufbau des Application Context ausgeführt. So werden @Import-Deklarationen vorher beachtet, und auch der Component Scan findet vorher statt. Damit enthält der Application Context bereits alle Bean-Definitionen der Anwendung, bevor die Auto Configurations betrachtet werden. Das ermöglicht es, die Bean-Deklarationen in den Auto Configurations an Bedingungen zu knüpfen. Diese Bedingungen werden durch @ConditionalOn-Annotationen beschrieben (Kasten: „Übersicht der @ConditionalOn-Annotationen“). Wir betrachten diese Annotationen später in einem konkreten Beispiel.
Übersicht der @ConditionalOn-Annotationen
Spring Boot bietet viele verschiedene @ConditionalOn-Annotationen für die unterschiedlichsten Einsatzzwecke an. Im Folgenden sind die gebräuchlichsten Annotationen beschrieben.
@ConditionalOnProperty
Es wird eine Property erwartet, die ggf. auch einen bestimmten Wert hat. Diese Bedingung eignet sich beispielsweise, um Feature-Toggles umzusetzen.
@ConditionalOnBean, @ConditionalOnMissingBean
Mit diesen Annotationen kann ausgedrückt werden, dass eine Bean eines bestimmten Typs im Application Context existiert (@ConditionalOnBean) oder es keine Beans dieses Typs gibt (@ConditionalOnMissingBean). Im Abschnitt „Bedingte Bean-Definitionen“ wird dies näher betrachtet.
@ConditionalOnClass, @ConditionalOnMissingClass
Ähnlich den Annotationen @ConditionalOnBean und @ConditionalOnMissingBean definieren diese Annotationen eine Erwartungshaltung: entweder, dass eine Klasse auf dem Klassenpfad verfügbar ist oder dass sie es nicht ist.
@ConditionalOnJava
Sind bestimmte Konfigurationen oder Beans abhängig von einer bestimmten Java-Runtime-Version, kann das mittels dieser Annotation ausgedrückt werden.
@ConditionalOnResource
Diese Bedingung erwartet, dass Ressourcen verfügbar sind. Dabei sind alle von Spring unterstützen Ressourcenpfade möglich, beispielsweise classpath:/index.html.
@ConditionalOnWebApplication, @ConditionalOnNotWebApplication
Mit diesen Annotationen können Bean-Deklarationen erstellt werden, abhängig davon, ob es sich um eine Webanwendung handelt oder nicht. Bei @ConditionalOnWebApplication kann zusätzlich unterschieden werden, ob es sich um eine Servlet-basierte oder reaktive Webapplikation handelt.
Als ich Spring Boot für mich entdeckt habe, war meine Erwartungshaltung, dass Spring Boot erst die notwendige Infrastruktur bereitstellt, bevor meine Bean-Deklarationen betrachtet werden. Interessanterweise ist das Konzept genau umgekehrt. Erst werden die Applikation und der Application Context geladen und danach schaut Spring Boot (genauer gesagt: schauen die Auto Configurations), was noch benötigt wird.
Erstellen eigener Auto Configurations und Starter
Mit dem grundlegenden Verständnis für Auto Configuration können wir nun mit eigenen Auto Configurations beginnen. Als Beispiel soll die Auto Configuration für den SSH-Server des Apache-MINA-Projekts dienen. Das Ziel ist es, einen SSH-Server in eine Spring-Boot-Anwendung zu integrieren. Sämtliche nun folgenden Beispiele finden sich im zugehörigen GitHub-Projekt.
Beginnen wir mit einer einfachen Anwendung, bei der das Set-up für den SSH-Server statisch umgesetzt wird (Listing 5).
Listing 5: SShServerApplication.java
@SpringBootApplication public class SshServerApplication { @Bean(initMethod = "start", destroyMethod = "stop") public SshServer sshServer(ShellFactory shellFactory) { final ServerBuilder builder = ServerBuilder.builder(); final SshServer server = builder.build(); server.setPort(8022); server.setKeyboardInteractiveAuthenticator( new DefaultKeyboardInteractiveAuthenticator()); server.setPasswordAuthenticator(passwordAuthenticator()); server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider()); server.setShellFactory(shellFactory); return server; } private PasswordAuthenticator passwordAuthenticator() { return (username, password, session) -> "admin".equals(username) && "password".equals(password); } @Bean public ShellFactory shellFactory() { return new NoopShellFactory(); } public static void main(String[] args) throws Exception { SpringApplication.run(SshServerApplication.class, args); } }
Die Anwendung ist einfach strukturiert. Es handelt sich bereits um eine Spring-Boot-Applikation mit der Deklaration von zwei Beans. Die eine Bean ist der SshServer selbst. Hierbei wird der Server mit einem Port konfiguriert, die Authentifizierung initialisiert und eine sogenannte ShellFactory gesetzt. Die ShellFactory stellt dem Anwender nach einem erfolgreichen SSH-Verbindungsaufbau eine interaktive Shell bereit. Wie diese Shell konkret umgesetzt wird, ist nicht von MINA vorgegeben. Da die Entwicklung eines vollständigen Bash-Äquivalents den Rahmen dieses Artikels sprengen würde, verwenden wir in diesem Beispiel eine NoopShellFactory (Listing 6), die beim Aufruf schlicht „noop“ ausgibt und die Verbindung schließt.
Stay tuned
Regelmäßig News zur Konferenz und der Java-Community erhalten
Listing 6: NoopShellFactory
public class NoopShellFactory implements ShellFactory { @Override public Command createShell(ChannelSession channel) throws IOException { return new AbstractCommandSupport("noop-shell", channel.getExecutorService()) { @Override public void run() { final var out = new PrintWriter(getOutputStream()); out.println("noop"); out.flush(); getExitCallback().onExit(0); } }; } }
Möchten wir nun eine weitere Applikation mit einem SSH-Server ausstatten, haben wir unterschiedliche Möglichkeiten: entweder das Kopieren des Codes in die neue Applikation oder das Extrahieren der Logik und ihre wiederverwendbare Gestaltung. Natürlich möchten wir nicht den Code kopieren, sondern entscheiden uns für eine wiederverwendbare Auto Configuration.
Der erste Schritt ist, die Bean-Deklarationen in eine eigene Spring-Konfiguration zu extrahieren. Basierend auf dem Beispiel aus Listing 5 resultiert das in einer mit @Configuration annotierte Klasse, die wir MinaSSHDAutoConfiguration nennen. Diese neue Klasse darf jedoch weder im Scanbereich des Component-Scanning sein noch mittels @Import importiert werden. In unserem Beispiel befindet sich die Spring-Applikation aus Listing 5 im Package de.digitalfrontiers.springboot.example. Somit darf die MinaSSHDAutoConfiguration weder in diesem noch in einem darunterliegenden Package liegen. Nehmen wir jedoch de.digitalfrontiers.springboot.mina.autoconfigure als Package-Namen, ist die MinaSSHDAutoConfiguration außerhalb des Component-Scanbereichs.
Damit Spring Boot die Auto Configuration findet, muss sie noch in META-INF/spring.factories eingetragen werden:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ de.digitalfrontiers.springboot.mina.autoconfigure.MinaSSHDAutoConfiguration
Bedingte Bean-Definitionen
Wir haben nun erreicht, dass die Konfiguration von SshServer auch von anderen Applikationen verwendet werden kann. Allerdings ist die Konfiguration für alle Applikationen immer identisch, was sicher nicht ausreichend ist. Benötigt eine Anwendung beispielsweise eine spezielle ShellFactory, um Monitoringinformationen über SSH abrufbar zu machen, muss es möglich sein, die vorkonfigurierte NoopShellFactory zu ersetzen. Dabei genügt es nicht, eine weitere Bean vom Typ ShellFactory zu definieren, da dann zwei ShellFactory-Beans existieren und nicht entschieden werden kann, welche davon verwendet werden soll. Entfernt man die NoopShellFactory-Deklaration in unserer MinaSSHDAutoConfiguration, muss immer eine ShellFactory im Application Context der Applikation definiert sein, selbst wenn für die Applikation die Standard-NoopShellFactory völlig ausreichen würde. Die Idee bei Auto Configurations ist, dass diese mit sinnvollen Defaults beginnen und beiseitetreten, wenn die Defaults nicht mehr genügen. Hier ist das die NoopShellFactory. Diese soll als Default verwendet werden, es sei denn, es wird explizit eine Bean vom Typ ShellFactory deklariert. Das erreicht man mit den @ConditionalOn-Annotationen, in diesem Fall mit @ConditionalOnMissingBean. Listing 7 enthält die resultierende MinaSSHDAutoConfiguration.
Listing 7: MinaSSHDAutoConfiguration
@Configuration @ConditionalOnClass(SshServer.class) public class MinaSSHDAutoConfiguration { @Bean(initMethod = "start", destroyMethod = "stop") @ConditionalOnMissingBean public SshServer sshServer(ShellFactory shellFactory, PasswordAuthenticator passwordAuthenticator) { final ServerBuilder builder = ServerBuilder.builder(); final SshServer server = builder.build(); server.setPort(8022); server.setKeyboardInteractiveAuthenticator( new DefaultKeyboardInteractiveAuthenticator()); server.setPasswordAuthenticator(passwordAuthenticator); server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider()); server.setShellFactory(shellFactory); return server; } @Bean @ConditionalOnMissingBean public PasswordAuthenticator passwordAuthenticator() { return (username, password, session) -> "admin".equals(username) && "password".equals(password); } @Bean @ConditionalOnMissingBean public ShellFactory shellFactory() { return new NoopShellFactory(); } }
Eine mit @ConditionalOnMissingBean annotierte @Bean-Deklaration wird nur dann berücksichtigt, wenn im Application Context nicht bereits eine Deklaration existiert. Konkret bedeutet das, dass Anwendungen standardmäßig einen SSH-Server enthalten, der die NoopShellFactory verwendet. Anwendungen, die jedoch eine spezifische Implementierung der ShellFactory verwenden möchten, müssen lediglich eine Bean vom Typ ShellFactory deklarieren.
Dem aufmerksamen Leser ist vermutlich die zusätzliche Annotation @ConditionalOnClass in der Klassendeklaration der Auto Configuration nicht entgangen. Diese Annotation drückt aus, dass die gesamte MinaSSHDAutoConfiguration nur dann anwendbar ist, wenn die Klasse SshServer auf dem Klassenpfad existiert. Kann SshServer nicht gefunden werden, wird die komplette Auto Configuration ignoriert. Damit ergeben sich interessante Möglichkeiten für die Strukturierung der Auto Configuration sowie möglicher Starter.
Auto Configuration und Starter
Bisher haben wir die Starter und deren Bedeutung noch nicht betrachtet. Nachdem wir aber die erste eigene Auto Configuration erstellt haben, ist nun genau der richtige Zeitpunkt, das nachzuholen.
Auto Configurations sorgen für die Konfiguration von Bibliotheken, wenn sich diese auf dem Klassenpfad befinden. Ein Starter sorgt für genau diesen Klassenpfad, indem er das Dependency-Management übernimmt. Wir benötigen die Dependency zur eigentlichen Library (in unserem Beispiel sshd-mina) und die Bibliothek, in der sich unsere jüngst erstellte MinaSSHDAutoConfiguration befindet. Die Spring-Boot-Dokumentation empfiehlt hierbei eine Aufteilung in zwei Module: Eins ist der jeweilige Starter, das zweite enthält die Auto Configuration. In Abbildung 2 findet sich die exemplarische Umsetzung.
Die Spring-Boot-Anwendung definiert damit eine Abhängigkeit auf den mina-sshd-spring-boot-starter. Und ganz im Spirit von Spring Boot ist das das Einzige, was der Anwendungsentwickler machen muss, um von der Auto Configuration und dem Dependency-Management zu profitieren. Der Starter hat Abhängigkeiten zu allen für diesen Starter relevanten Libraries (hier: sshd-mina) sowie die passenden autoconfigure-Module. Diese Struktur erlaubt eine klare Trennung der Verantwortlichkeiten: Der Starter ist für das Dependency-Management zuständig, das autoconfigure-Modul für die Bereitstellung der Auto Configuration.
Dabei fällt einerseits auf, dass die Abhängigkeit des Moduls mina-autoconfigure zu sshd-mina als optional angegeben wurde, andererseits auch, dass der Starter den Zusatz sshd im Modulnamen enthält, was bei mina-autoconfigure jedoch nicht der Fall ist. Letzteres ist nicht etwa ein Tippfehler, sondern wurde ganz bewusst gewählt.
Mina bietet eine Reihe von verschiedenen Libraries, die auf der Mina-Kernbibliothek aufbauen. Nehmen wir als Beispiel die Unterstützung für SFTP, die durch Mina angeboten wird. Da diese ebenfalls auf dem SshServer basiert, ist es sinnvoll, die entsprechende Auto Configuration in demselben Modul zu implementieren wie die MinaSSHDAutoConfiguration (in mina-autoconfigure). Die Abhängigkeit zu der notwendigen Bibliothek wird dann ebenfalls als optional deklariert und schlussendlich ein passender Starter bereitgestellt. Es ergibt sich eine Modulstruktur wie in Abbildung 3 zu sehen.
Damit kann ein Entwickler nun entscheiden, ob er nur SSH benötigt oder doch auch SFTP – und das primär durch das Setzen einer Dependency. Welche Auto Configuration aktiviert wird, entscheidet sich damit primär über den Klassenpfad und mittels der Annotation @ConditionalOnClass.
SIE LIEBEN JAVA?
Den Core-Java-Track entdecken
Fazit
Das Auto-Configuration-Konzept ist so einfach wie genial. Hat man einmal begonnen, in der Struktur von Auto Configuration und Starter zu denken und zu entwickeln, bieten sich viele Möglichkeiten. Kopierter Code (von dem wir alle wissen, dass er viel zu häufig existiert) kann reduziert und vereinheitlicht werden. Unternehmenskonventionen können in Starter und Auto Configuration umgesetzt werden und nicht mehr nur in länglichen, selten aktualisierten Verfahrensanweisungen.
Ausblick
Es endet aber nicht nur bei den Auto Configurations und Startern. Jeder, der mit Spring Boot entwickelt hat, kennt die application.yaml oder application.properties. Mit ihnen können Parameter wie beispielsweise Ports konfiguriert werden. Einen Port haben wir auch in unserem SSH-Server-Codebeispiel gesetzt. Wäre es nicht extrem hilfreich, wenn man diesen Port mittels eines Eintrags in der application.yaml einfach ändern könnte? Genau hierfür hat Spring Boot ein weiteres Konzept, das mit Features wie Validierung und Type Safety aufwartet, genannt „Externalized Configuration“. Dieses Konzept werden wir im nächsten Teil der Reihe genauer beleuchten.
Nun haben wir unseren eigenen Starter und Auto Configurations erstellt, und es wäre wirklich genial, wenn man den Spring Initializr dazu bringen könnte, die eigenen Starter anzubieten. Und typisch für das Spring-Ökosystem: Auch das ist möglich! Wie genau, sehen wir im zweiten Teil dieser Artikelserie.