Spring Boot 3 - François Fernandès - E-Book

Spring Boot 3 E-Book

François Fernandès

0,0
39,90 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.
Mehr erfahren.
Beschreibung

Ihr umfassender Leitfaden für Spring Boot 3 - Spring Boot von Null auf Hundert - Neuerungen in Spring Boot 3 und Migration von Spring Boot 2 - Beispiele für verschiedenste Anwendungsfälle Entdecken Sie die Leistungsfähigkeit von Spring Boot 3 mit diesem umfassenden Leitfaden. Von den Grundlagen zu Spring Boot 3 über fortgeschrittene Themen wie zum Beispiel reaktive Programmierung bis zu eigenen Erweiterungen von Spring Boot. Was Sie in diesem Buch erwartet: Universelle Zugänglichkeit: Egal, ob Sie gerade erst anfangen oder bereits Erfahrung mit Spring Boot haben, dieses Buch führt Sie durch die Grundlagen bis hin zu fortgeschrittenen Konzepten. Praxisorientierte Lernweise: Nutzen Sie zahlreiche praxisnahe Beispiele, um das Gelernte sofort anzuwenden. Die klare Darstellung und Anwendung in realen Szenarien machen den Lernprozess effektiv und eingängig. Neuerungen in Version 3: Entdecken Sie die spezifischen Features von Spring Boot 3. Die Autoren gehen detailliert auf die neuesten Entwicklungen ein und zeigen, wie Sie das volle Potenzial dieser Version ausschöpfen können. Ob Sie ein erfahrener Entwickler sind oder gerade erst in die Welt von Spring Boot eintauchen – dieses Buch vermittelt Ihnen das notwendige Wissen, um moderne und effiziente Java-Anwendungen zu entwickeln.

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB
MOBI

Seitenzahl: 506

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



François Fernandès ist Senior Solution Architect bei Digital Frontiers und hat fast 20 Jahre Erfahrung in der Java-Softwareentwicklung und dem Java-Ökosystem. Seine Erfahrungen reichen dabei von der Entwicklung von Java-Desktop-Anwendungen über verteilte Applikationen bis hin zu Softwarearchitekturen in hybriden Cloud-Umgebungen. Den ersten Kontakt mit Spring hatte François mit dem Spring Framework 2.0. Seither begleiten ihn das Spring Framework, Spring Boot sowie diverse weitere Spring-Projekte in den unterschiedlichsten Einsätzen.

Tom Hombergs ist Principal Developer bei Atlassian in Sydney und dort für den Java Tech Stack verantwortlich, der von hunderten Teams weltweit zur Entwicklung der Atlassian-Produkte genutzt wird. Spring Boot ist ein zentraler Bestandteil dieses Tech Stacks. Er arbeitet seit vielen Jahren mit Spring und Spring Boot und trieb bei Atlassian unter anderem die unternehmensweite Migration von Spring Boot 2 zu Spring Boot 3 für über 800 (Micro-)Services voran. Tom ist Autor von »Get Your Hands Dirty on Clean Architecture« (deutsche Ausgabe: »Clean Architecture Praxisbuch«) und »Stratospheric – from Zero to Production with Spring Boot and AWS« und schreibt gelegentlich auf reflectoring.io über Themen der Softwareentwicklung.

Benedikt Jerat ist Senior Consultant bei Digital Frontiers. Seine Schwerpunkte liegen auf der Softwareentwicklung im Umfeld von JVM-Sprachen wie Java, Kotlin und Scala, wobei er besonders auf Spring und Spring Boot setzt. In seinen Projekten fokussiert er sich auf die funktionale Programmierung und die Prinzipien des Software Craftsmanship. Benedikt ist Autor zahlreicher Blog-Posts und Artikel in Fachzeitschriften und regelmäßiger Speaker auf Konferenzen zu aktuellen Themen.

Florian Pfleiderer ist Co-Founder und Senior Consultant bei Digital Frontiers. In dieser Rolle beschäftigt er sich vor allem mit den Themen Architektur, Microservices und Software Craftsmanship und unterstützt seine Kunden auf ihrem Weg in die Cloud. Das Spring Framework benutzte er zum ersten Mal vor über zehn Jahren, bei Spring Boot war er Nutzer der ersten Stunde. Trotz oder gerade wegen einiger Ausflüge in andere Ökosysteme kehrt er immer wieder gerne zu Spring Boot zurück und empfiehlt es seinen Kunden mit Begeisterung und Überzeugung. Außerhalb von Projekten gibt er sein Wissen regelmäßig auf Konferenzen oder in Fachzeitschriften weiter.

Copyright und Urheberrechte:

Die durch die dpunkt.verlag GmbH vertriebenen digitalen Inhalte sind urheberrechtlich geschützt. Der Nutzer verpflichtet sich, die Urheberrechte anzuerkennen und einzuhalten. Es werden keine Urheber-, Nutzungs- und sonstigen Schutzrechte an den Inhalten auf den Nutzer übertragen. Der Nutzer ist nur berechtigt, den abgerufenen Inhalt zu eigenen Zwecken zu nutzen. Er ist nicht berechtigt, den Inhalt im Internet, in Intranets, in Extranets oder sonst wie Dritten zur Verwertung zur Verfügung zu stellen. Eine öffentliche Wiedergabe oder sonstige Weiterveröffentlichung und eine gewerbliche Vervielfältigung der Inhalte wird ausdrücklich ausgeschlossen. Der Nutzer darf Urheberrechtsvermerke, Markenzeichen und andere Rechtsvorbehalte im abgerufenen Inhalt nicht entfernen.

François Fernandès · Tom Hombergs ·Benedikt Jerat · Florian Pfleiderer

Spring Boot 3

So geht moderne Java-Entwicklung –Konzepte und Anwendungen

François Fernandès · Tom Hombergs · Benedikt Jerat · Florian Pfleiderer

Lektorat: Dr. Benjamin Ziech

Projektkoordinierung: Julia Griebel

Copy-Editing: Annette Schwarz, Ditzingen

Satz: inpunkt[w]o, Wilnsdorf (www.inpunktwo.de)

Herstellung: Stefanie Weidner

Umschlaggestaltung: Eva Hepper, Silke Braun

Bibliografische Information der Deutschen Nationalbibliothek

Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie;

detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

ISBN:

Print

978-3-86490-994-8

PDF

978-3-98890-125-5

ePub

978-3-98890-126-2

1. Auflage 2024

Copyright © 2024 dpunkt.verlag GmbH

Wieblinger Weg 17

69123 Heidelberg

Schreiben Sie uns:

Falls Sie Anregungen, Wünsche und Kommentare haben, lassen Sie es uns wissen: [email protected].

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.

Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autoren noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.

Inhaltsübersicht

1Einleitung

Teil IGrundlagen

2Hallo, Spring Boot

3Spring-Grundlagen

4Spring-Boot-Grundlagen

5Konfiguration

6Build Management mit Spring Boot

7Einführung ins Testen

8Troubleshooting einer Spring-Boot-Anwendung

Teil IIAnwendungsfälle

9Einen REST-Service entwickeln

10Das Reactor-Framework verwenden

11Eine GraphQL-API entwickeln

12Integration einer SPA mit Spring Boot

13Ein serverbasiertes Web-Frontend entwickeln

14Eine Datenbank anbinden

15Eine CLI-Anwendung entwickeln

16Architektur-Governance mit Spring Boot

Teil IIIReferenz

17Testing

18Spring Reactive

19Spring Web MVC

20HTTP-Clients mit Spring

21GraphQL

22Spring Boot Developer Tools

23Events

24Caching

25Messaging

26Spring Data

27Spring Cloud Config

28Spring Security

29Observability

30Docker-Images mit Spring Boot

31Native Images mit Spring Boot

32Spring Boot erweitern

33Coordinated Restore at Checkpoint (CRaC)

34Migration von Spring Boot 2 zu Spring Boot 3

35Ausblick

Inhaltsverzeichnis

1Einleitung

1.1Warum Spring Boot?

1.2Für wen ist dieses Buch?

1.3Aufbau des Buchs

1.4Codebeispiele

Teil IGrundlagen

2Hallo, Spring Boot

2.1Einleitung

2.2JDK installieren

2.3Kickstart mit dem Spring Initializr

2.4Projektstruktur

2.5Die Anwendung bauen

2.6Die Anwendung starten

2.7Einen REST-Controller bauen

3Spring-Grundlagen

3.1Dependency Injection und Inversion of Control

3.1.1Inversion of Control

3.1.2Dependency Injection

3.2Der Spring Application Context

3.3Application Context mit XML konfigurieren

3.4Java-Konfiguration im Detail

3.4.1@Configuration und @Bean

3.4.2@Component und @ComponentScan

3.4.3@Configuration und @ComponentScan kombinieren

3.5Was haben wir vom Spring Container?

4Spring-Boot-Grundlagen

4.1Bootstrapping

4.2Den Application Context beeinflussen

4.3Embedded Webserver

4.4Dependency Management

4.5Integrationen

4.6Produktionsbetrieb

5Konfiguration

5.1Warum Konfiguration?

5.2Konfigurationsparameter

5.2.1Konfigurationsparameter definieren

5.2.2Parameter als String injizieren

5.2.3Parameter typsicher injizieren

5.2.4Parameter validieren

5.2.5Default-Werte definieren

5.3Profile

5.3.1Konfigurationsparameter für ein Profil definieren

5.3.2Ein Profil aktivieren

5.3.3Konfigurationswerte aus unterschiedlichen Quellen kombinieren

5.4Das Environment-Bean

5.5Best Practices für Konfigurationsmanagement

5.5.1Konfigurationsdateien im JAR

5.5.2Secrets als Umgebungsvariablen definieren

5.5.3Umgebungsvariablen explizit definieren

5.5.4Profile nur für Umgebungen

5.5.5Konfiguration von Default-Werten

6Build Management mit Spring Boot

6.1Überblick

6.2Gradle oder Maven?

6.3Spring Boot »Fat JAR«

6.4Das Gradle Plugin

6.5Das Maven Plugin

6.6Dependency Management mit Spring Boot

6.6.1Spring-Boot-BOM mit Gradle konsumieren

6.6.2Spring-Boot-BOM mit Maven konsumieren

7Einführung ins Testen

7.1Testen – wieso, weshalb, warum?

7.2Spring Boot Starter Test

7.3Unit-Tests

7.4SpringExtension (JUnit 5)

7.5Integrationstests mit Spring

7.5.1@SpringBootTest

7.5.2Ausblick auf Slice-Annotationen

8Troubleshooting einer Spring-Boot-Anwendung

8.1Spring-Boot-Magie

8.2Troubleshooting-Werkzeuge

8.2.1Lokal reproduzieren

8.2.2Debug-Modus

8.2.3Actuator-Endpoints

8.2.4Logging

8.2.5Hooks

8.3Troubleshooting-Anwendungsfälle

8.3.1Welche Beans stehen mir zur Verfügung?

8.3.2Wo kommt eine Bean her?

8.3.3Warum ist meine Bean nicht im Application Context?

8.3.4Warum ist eine Bean doppelt im Application Context?

8.3.5Wie ist meine Anwendung konfiguriert?

8.3.6Welche Konfigurationsparameter werden (nicht) genutzt?

8.3.7Welche Endpoints stellt meine Anwendung zur Verfügung?

8.3.8Warum startet meine Anwendung nicht?

Teil IIAnwendungsfälle

9Einen REST-Service entwickeln

9.1Was ist REST?

9.2Codebeispiel

9.3Endpoints

9.4Request Body und Parameter

9.5Fehlerbehandlung

10Das Reactor-Framework verwenden

10.1Was ist das Reactor-Framework?

10.2Codebeispiel

10.3Andere reaktive Operatoren

11Eine GraphQL-API entwickeln

11.1GraphQL in Kürze

11.2Die Beispielanwendung

11.3Erstellung der Anwendung

11.4Abbildung des Datenmodells in GraphQL

11.5Implementierung des Controllers

11.5.1Query Mapping

11.5.2Schema Mapping

11.5.3Mutation Mapping

11.6Implementierung einer Subscription

12Integration einer SPA mit Spring Boot

12.1Herausforderungen von SPAs

12.1.1Handhabung von URLs

12.1.2Integration unterschiedlicher Toolings

12.2Mögliche Varianten für ein Deployment

12.2.1Integration in das Spring-Boot-JAR

12.2.2Unabhängige Deployments des Frontends und Backends

12.3Ein Spring Boot Backend mit React Frontend

12.3.1Verzeichnisstruktur

12.3.2Konfiguration des Builds

12.3.3Integration einer REST-API in das Frontend

12.3.4Unterstützung von Deep-Links mit der History-API

13Ein serverbasiertes Web-Frontend entwickeln

13.1Warum serverseitig?

13.2Die Beispielanwendung

13.3»Hello World« mit Thymeleaf

13.4Formulardaten verarbeiten

13.5Interaktivität mit HTMX

13.6Spring Boot Developer Tools

13.7Weitere Ressourcen

14Eine Datenbank anbinden

14.1Datenbanken

14.2Codebeispiel

14.3Spring Data Repositories

14.4Transaktionen

15Eine CLI-Anwendung entwickeln

15.1Eine ADR-Management-Anwendung

15.2Erstellen des Projekts

15.2.1Die ADR-API

15.3Ein erstes Kommando

15.4Registrieren von Kommandos

15.4.1Programmatische Registrierung von Kommandos

15.4.2Kommandos mit Annotationen definieren

15.4.3AvailabilityProvider

15.4.4Anzeige eines eigenen Prompts

15.5Ausführung im interaktiven oder Kommando-Modus

15.6Alternativen zu Spring Shell

16Architektur-Governance mit Spring Boot

16.1Warum Architektur-Governance?

16.2Komponentenbasierte Architektur

16.3Komponentenbasierte Architektur mit Spring Boot

16.4Governance mit ArchUnit

16.5Spring Modulith

Teil IIIReferenz

17Testing

17.1Überblick

17.2Unit Testing

17.2.1Unit Testing ohne Spring

17.2.2Unit Testing Utilities im Spring Framework

17.3Integration Testing

17.3.1Spring TestContext Framework

17.3.2SpringExtension und SpringRunner

17.3.3TestContextManager und ContextConfiguration

17.4Wichtige Testfunktionalitäten im Überblick

17.4.1@SpringBootTest

17.4.2Context Initializers

17.4.3@TestPropertySource

17.4.4@ActiveProfiles

17.4.5@TestConfiguration

17.4.6@TestExecutionListeners

17.5Testen von einzelnen Schichten mit Test Slices

17.5.1Was sind Test Slices?

17.5.2@WebMvcTest

17.5.3@DataJpaTest

17.5.4@JsonTest

17.5.5Weitere Test Slices

18Spring Reactive

18.1Warum Reactive?

18.2Grundlagen

18.2.1Reactive Streams und Project Reactor

18.2.2Reaktive Datentypen: Mono und Flux

18.2.3Reactive Chaining

18.3Spring WebFlux

18.3.1Annotierte Controller

18.3.2Funktionale Endpoints

18.3.3WebClient statt RestTemplate

18.4Testen von Reactive Streams

18.4.1StepVerifier

18.4.2WebFluxTest

19Spring Web MVC

19.1REST

19.1.1RestController

19.1.2Request-Daten auslesen

19.1.3Reactive RestController

19.2JSON-Mapping mit Jackson

19.2.1Jackson Annotations

19.2.2Custom Serializer

19.3Error Handling

19.4API-Dokumentation mit Spring REST Docs

19.4.1Setup für REST Docs

19.4.2JUnit-Test zur Generierung

19.4.3Nutzen von Snippets

19.5HATEOAS

19.5.1Wieso überhaupt HATEOAS?

19.5.2Repräsentation von Ressourcen

19.5.3Hypermedia Links & Relations

19.5.4Komfortable Links mit dem WebMvcLinkBuilder

19.5.5Affordances

19.5.6Integration mit RestTemplate und WebClient

20HTTP-Clients mit Spring

20.1RestTemplate

20.2RestClient

20.3WebClient

20.4HTTP-Interface

21GraphQL

21.1Einführung

21.2GraphQL in Kürze

21.2.1Das GraphQL-Schema

21.2.2Die Query Language

21.2.3Ein GraphQL-Schema kann noch mehr

21.3Ablauf eines GraphQL-Requests

21.3.1Transportmethoden

21.4Erstellung einer Spring-Boot-Anwendung mit GraphQL-API

21.4.1Verwenden des Spring Initializr

21.4.2Manuelles Hinzufügen der notwendigen Abhängigkeiten

21.4.3Interaktives Testen mit GraphiQL

21.5Implementierung einer GraphQL-API

21.5.1Definieren eines GraphQL-Schemas

21.5.2GraphQL Controller in Spring Boot

21.5.3Optimierte Abfragen mit Batches

21.5.4Definition eigener Skalare

21.6Fehlerbehandlung

22Spring Boot Developer Tools

22.1Warum Developer Tools?

22.2Developer Tools aktivieren

22.3Restart

22.4Live Reload

22.5Einschränkungen der Developer Tools

23Events

23.1Lose Kopplung

23.2Events versenden

23.3Events empfangen

23.3.1ApplicationListener

23.3.2@EventListener

23.4Synchron oder asynchron?

23.5Spring Boot ApplicationEvents

24Caching

24.1Cache-Configuration

24.2Caching-Annotationen

24.3Cache-Implementierungen

24.3.1EhCache

24.3.2Caffeine

25Messaging

25.1Messaging im Überblick

25.2JMS

25.2.1Mit einer JMS-API verbinden

25.2.2Nachrichten senden

25.2.3Nachrichten empfangen

25.2.4Message Converter

25.3AMQP

25.3.1Das AMQP-Protokoll

25.3.2Konfiguration

25.3.3Nachrichten versenden

25.3.4Nachrichten empfangen

25.4Kafka

25.4.1Konfiguration

25.4.2Nachrichten versenden und empfangen

25.5Ausblick: Spring Cloud Stream

25.5.1Konzepte

25.5.2Nachrichten senden und empfangen

25.5.3Vorteile

26Spring Data

26.1Überblick über Spring Data

26.2Spring Data Repositories

26.2.1Das Repository-Interface

26.2.2Query-Methoden

26.3Die DataSource Bean

26.4JPA

26.4.1Welchen Mehrwert bietet Spring Data JPA?

26.4.2JPA-Repositories und -Entities

26.4.3Datenbankinitialisierung mit JPA und Hibernate

26.5R2DBC

26.5.1Datenbankverbindung über ConnectionFactory

26.5.2Datenbankzugriff

26.6NoSQL mit Spring Data MongoDB

26.6.1MongoDatabaseFactory und MongoTemplate

26.6.2Spring Data Repository für MongoDB

26.6.3Integrationstests mit @DataMongoTest

26.7Schema-Migration mit Flyway

27Spring Cloud Config

27.1Warum Spring Cloud Config?

27.2Der Spring Cloud Config-Server

27.3Environment Repository

27.4Spring Cloud Config-Client

28Spring Security

28.1Konzepte

28.1.1Authentication

28.1.2Authorization

28.1.3Auto-Configuration

28.2Absicherung von Methoden

28.3Absicherung von HTTP-Pfaden

28.4Benutzerverwaltung

28.5Security Testing

28.6OAuth

28.6.1Was ist OAuth?

28.6.2OAuth Resource-Server

28.6.3OAuth-Client

29Observability

29.1Warum Observability?

29.2Admin-Endpoints mit Actuator

29.2.1Anwendungs-Metadaten ausgeben

29.2.2Einen eigenen Actuator-Endpoint entwickeln

29.3Logging

29.3.1SLF4J

29.3.2Logging via application.yml konfigurieren

29.3.3Logback und Log4J direkt konfigurieren

29.3.4Kombination der Logging-Konfigurationen

29.4Metriken

29.4.1Standardmetriken exportieren

29.4.2Metriken an ein Observability-Produkt exportieren

29.4.3Eigene Metriken exportieren

29.4.4Tags

29.4.5Metriken programmatisch anpassen

29.4.6Histogramme und Perzentile

29.5Tracing

29.5.1Traces programmatisch auswerten

29.5.2Traces in Log-Events ausgeben

29.5.3Traces exportieren

29.5.4Clients instrumentieren

29.5.5Baggage

30Docker-Images mit Spring Boot

30.1Warum Docker?

30.2Einfaches Docker-Image mit Spring Boot

30.3Optimierte Docker-Images

30.4Optimiertes Docker-Image mit Spring Boot

30.5Docker abstrahieren mit Buildpacks

31Native Images mit Spring Boot

31.1Warum Native?

31.2Was ist ein Native Image?

31.3Anwendungsfälle für Native Images

31.4Ahead-of-Time-Optimierung mit Spring Boot

31.5Ein natives Image erstellen

31.6Ein natives Image testen

31.7Reachability-Metadaten erstellen

32Spring Boot erweitern

32.1Cross-Cutting Concerns

32.2@Configuration und @Import

32.3@Enable…-Annotationen

32.4@AutoConfiguration

32.5Bedingte @Configuration

32.6Testen von @AutoConfigurations

32.7Starter

32.8Fortgeschrittene Erweiterungspunkte

32.8.1FactoryBean

32.8.2BeanPostProcessor

32.8.3BeanDefinitionRegistryPostProcessor

32.8.4EnvironmentPostProcessor

33Coordinated Restore at Checkpoint (CRaC)

33.1Warum CRaC?

33.2Checkpoint und Restore

33.3Checkpoint und Restore mit Spring Boot

33.4Automatische Checkpoints

33.5Checkpoints in Docker

33.6CRaC vs. GraalVM

34Migration von Spring Boot 2 zu Spring Boot 3

34.1Überblick

34.2Schritt 1: Bibliotheken analysieren und aktualisieren

34.3Schritt 2: Auf Java 17 aktualisieren

34.4Schritt 3: Das Spring-Boot-Upgrade vorbereiten

34.4.1WebSecurityConfigurerAdapter durch WebSecurityFilterChain ersetzen

34.4.2@AutoConfiguration

34.4.3@LocalServerPort

34.4.4@EnableWebFluxSecurity

34.5Schritt 4: Spring Boot aktualisieren

34.5.1Spring Boot auf 3.x aktualisieren

34.5.2Bibliotheken aktualisieren

34.5.3javax durch jakarta ersetzen

34.5.4Spring Cloud Sleuth durch Micrometer ersetzen

34.5.5@ConstructorBinding

34.5.6HttpStatusCode

34.5.7Konfigurationsparameter

35Ausblick

Index

1Einleitung

1.1Warum Spring Boot?

Das Spring Framework und Spring Boot sind aus dem Java-Ökosystem nicht mehr wegzudenken. Ohne Zweifel führt Spring Boot das Feld der Java-basierten Application-Frameworks an. In einer Umfrage von Jetbrains aus dem Jahr 2022 gaben 67% der befragten Java-Entwickler an, Spring Boot zu nutzen, und 41% nutzten Spring MVC (beide Technologien werden in diesem Buch behandelt)1. Das Framework auf Platz 3 kommt nur auf einen einstelligen Prozentwert, und 23% der Befragten gaben an, gar kein Web-Framework zu nutzen, was die Prominenz von Spring nur verdeutlicht.

Softwareunternehmen wie Netflix und Atlassian setzen auf Spring Boot, um die Softwareentwicklung über Hunderte von Teams hinweg zu standardisieren!

Die führende Rolle von Spring Boot kommt nicht von ungefähr. Spring Boot basiert auf dem Spring Framework, das seit Anfang der 2000er Jahre kontinuierlich und sorgsam weiterentwickelt wird. Die Idee, ein auf Spring basierendes »Boot«-Framework zu entwickeln, das es uns vereinfacht, eine Spring-Anwendung zu konfigurieren, zu integrieren und zu betreiben, war nur eine Frage der Zeit.

Spring Boot macht uns als Entwickler produktiver, weil es uns viel Arbeit abnimmt. Es setzt dabei stark auf Konventionen, sodass wir nicht jede Kleinigkeit selbst konfigurieren müssen und sofort loslegen können. Diese große Stärke von Spring Boot wird aber teilweise auch als Hindernis empfunden, wenn man mit diesen Konventionen nicht vertraut ist oder nicht weiß, wo man sie nachschlagen kann.

Genau dieses Hindernis möchten wir mit diesem Buch aus dem Weg räumen.

1.2Für wen ist dieses Buch?

Dieses Buch richtet sich sowohl an Java-Entwickler, die noch keine Erfahrung mit Spring oder Spring Boot haben, als auch an solche, die bereits Spring Boot 2 gearbeitet haben und einige Neuerungen in Spring Boot 3 kennenlernen oder ihr allgemeines Spring-Boot-Wissen auffrischen möchten.

Es bietet sich an, grundlegende Java-Kenntnisse (oder allgemeine Kenntnisse in objektorientierter Programmierung) mitzubringen, andernfalls sind die Konzepte und Codebeispiele in diesem Buch schwer nachzuvollziehen.

Da Spring Boot eine Fülle von Integrationen mit anderen Systemen anbietet, von denen einige in diesem Buch behandelt werden, eignet sich das Buch ebenfalls als Nachschlagewerk für Architekten, die Spring und Spring Boot für den Einsatz in einem Projekt evaluieren möchten.

Auch wenn dieses Buch die wichtigsten Features und Konzepte erläutert, erhebt es keinen Anspruch auf Vollständigkeit. Das Spring-Ökosystem ist mittlerweile so umfangreich, dass ein Buch mit einer vollständigen Abdeckung unpraktikabel wäre und vermutlich nur als Monitorstütze verwendet werden würde. Daher konzentrieren wir uns in diesem Buch auf die wichtigen Aspekte und verweisen an vielen Stellen für bestimmte Details auf die sehr gute Referenzdokumentation der jeweiligen Spring-Projekte. Es geht nicht darum, jeden Konfigurationsparameter auswendig zu lernen, sondern darum, die Konzepte zu verstehen, um sie dann bei Bedarf anwenden (und nachschlagen) zu können.

1.3Aufbau des Buchs

Dieses Buch ist in drei Teile aufgeteilt.

In Teil I werden die Grundlagen von Spring und Spring Boot beschrieben. Dieser Teil ist insbesondere für Leser geeignet, die sich noch nicht viel mit Spring und Spring Boot beschäftigt haben. Leser, die bereits Erfahrung mit beiden Frameworks gesammelt haben, mögen Teil I gegebenenfalls ganz oder teilweise überspringen. Die Konzepte aus Teil I sind für das Verständnis vieler Kapitel aus den Teilen II und III erforderlich.

Teil II ist gedacht als Nachschlagewerk für bestimmte Anwendungsfälle. Jedes Kapitel in Teil II betrachtet einen konkreten Anwendungsfall (zum Beispiel die Entwicklung einer REST-API), mit dem man jederzeit in einem Softwareprojekt konfrontiert sein kann. Die Kapitel geben jeweils einen Überblick darüber, wie man den entsprechenden Anwendungsfall mit Spring Boot lösen kann.

Die Kapitel in Teil III sind Referenzkapitel, die im Detail auf Spring-Boot-Features eingehen. Hier lernt der Leser alles, was notwendig ist, um diese Features in einer Anwendung zu nutzen.

Mit Ausnahme von Teil I bauen die Kapitel nicht aufeinander auf, sodass die Kapitel größtenteils unabhängig voneinander gelesen werden können. Wir laden dazu ein, der Neugier zu folgen und diejenigen Kapitel zuerst zu lesen, die gerade das Interesse wecken oder im aktuellen Projekt von Bedeutung sind. Sollte ein Kapitel doch mal ein Konzept aus einem anderen Kapitel erfordern, findet sich ein Verweis im Text.

1.4Codebeispiele

Ein Codebeispiel sagt mehr als tausend Worte. Das haben wir uns mit diesem Buch zu Herzen genommen. Die Konzepte von Spring und Spring Boot werden mit weit über 300 Codebeispielen veranschaulicht, um sie in Aktion zu sehen.

Noch viel mehr als ein Codebeispiel sagt jedoch eine lauffähige Anwendung. Deshalb haben wir für viele der Kapitel jeweils eine Spring-Boot-Anwendung entwickelt und in einem GitHub-Repository zur Verfügung gestellt. Wir empfehlen, dieses Repository zu klonen und mit den darin enthaltenen Spring-Boot-Anwendungen beim Lesen der Kapitel herumzuspielen. Am besten lernt man doch beim Spielen mit Code!

Das Repository ist unter der URL https://github.com/dxfrontiers/spring-boot-3-buch frei verfügbar.

Damit alle Codebeispiele aus diesem Buch und dem Repository lauffähig sind, empfehlen wir die Installation von JDK 21 und die Nutzung der aktuellen Versionen von Spring Boot 3.x und Spring 6.x. Die meisten Beispiele werden auch mit etwas älteren Versionen funktionieren, aber einige Features sind erst mit späteren Versionen verfügbar.

Teil I

Grundlagen

2Hallo, Spring Boot

Im ersten Kapitel bauen wir direkt eine lauffähige »Hello World«-Anwendung, um mit einem Erfolgserlebnis zu starten.

2.1Einleitung

Dieses Buch ist ein Praxisbuch. In fast allen Kapiteln werden wir anhand von Codebeispielen die verschiedenen Aspekte einer Spring-Boot-Anwendung diskutieren. In den meisten Kapiteln sind diese Codebeispiele einer Beispielanwendung entnommen, deren Code auf GitHub öffentlich verfügbar ist. Wir laden dazu ein, den Beispielcode beim Lesen des Buches in der lokalen IDE durchzustöbern.

In diesem Sinne beginnen wir dieses Buch auch mit einem praktischen »Hello World«-Beispiel. Leser, die bereits Erfahrung mit Spring Boot haben, können dieses Kapitel guten Gewissens überspringen. Wenn nicht, laden wir dazu ein, den Beispielen in diesem Kapitel zu folgen und eine erste lauffähige Spring-Boot-Anwendung zu bauen.

In diesem Kapitel werden wir nicht jedes kleine Detail erläutern. Aber keine Angst, wir verweisen stets auf spätere Kapitel, in denen die Details vertieft werden.

2.2JDK installieren

Um eine Spring-Boot-Anwendung zu entwickeln, müssen wir ein JDK (Java Development Kit) auf unserem Rechner installieren. Spring Boot 3 benötigt ein JDK der Version 17 oder höher. Wenn Sie bereits ein solches JDK installiert haben, können Sie mit dem nächsten Abschnitt fortfahren.

Wir empfehlen zur Verwaltung des JDK einen Runtime Manager wie asdf1, jEnv2 oder SDKMan3. Diese Tools machen es einfach, zwischen verschiedenen Versionen eines JDK hin- und herzuwechseln. SDKMan und asdf automatisieren darüber hinaus auch das Herunterladen von verschiedenen JDK-Versionen. Wir können mit einem einzigen Kommandozeilenbefehl ein bestimmtes JDK herunterladen oder bestimmen, welches der bereits heruntergeladenen JDKs wir gerade benutzen möchten.

Zur Installation der Runtime Manager verweisen wir auf die Websites der Tools. Im Folgenden zeigen wir, wie wir mit asdf ein JDK installieren, das wir für alle Beispiele in diesem Buch benutzen können.

Vorausgesetzt, asdf ist erfolgreich installiert, müssen wir zunächst das Java-Plugin installieren (asdf unterstützt nämlich auch Runtimes anderer Programmiersprachen):

> asdf plugin add java

Dann können wir zum Beispiel mit dem folgenden Befehl ein JDK installieren:

> asdf install java latest:corretto

Hier installieren wir die aktuelle Version des Amazon-Corretto-JDK. Wir können uns auch eine Liste aller anderen JDKs anzeigen lassen und dann eines von denen installieren:

> asdf list all java | grep \\-21\\.

Diese Liste ist sehr lang, sodass wir sie hier mithilfe von grep auf die JDKs der Version 21 filtern.

Nun, da wir ein JDK installiert haben (oder mehrere), müssen wir unsere Umgebung noch so konfigurieren, dass dieses JDK auch überall genutzt wird:

> asdf local java latest:corretto

Der Befehl java --version sollte nun die erwartete Version des JDK ausgeben.

2.3Kickstart mit dem Spring Initializr

Nun können wir unser Beispielprojekt erstellen. Das geht am einfachsten mit dem Spring Initializr, der unter https://start.spring.io/ verfügbar ist. Hier können wir einige Parameter, wie zum Beispiel die Java-Version, auswählen und uns eine ZIP-Datei mit einem Projektskelett erstellen lassen. Wir werden den Spring Initializr im Rest dieses Buches noch häufig verwenden, um Beispielprojekte für unterschiedliche Anwendungsfälle zu generieren.

Für unser »Hello World«-Beispielprojekt wählen wir die folgenden Parameter:

Project

: Gradle-Kotlin

Language

: Java

Spring Boot

: aktuelle 3.x-Version (Standardeinstellung)

Dependencies

: Spring Web

Alle anderen Einstellungen belassen wir mit ihren Standardwerten. Über den Button »Add Dependencies« können wir eine Auswahl bestimmter Integrationen und Features auswählen, die unserem Projekt dann als Dependencies hinzugefügt werden. Wir wählen zu Beginn nur die »Spring Web«-Dependency, um eine einfache Webanwendung zu entwickeln.

Mit einem Klick auf den »Generate«-Button lassen wir uns das Projekt erstellen und laden die ZIP-Datei herunter, die wir auf unserer Festplatte entpacken. Dann laden wir den Ordner als Projekt in unsere IDE und schauen uns im Projekt um.

2.4Projektstruktur

Die zentrale Datei im Projektordner ist die Datei build.gradle.kts, unser Gradle-Build-Skript. Es ist in der Kotlin-DSL geschrieben, weil wir bei Erstellung des Projekts »Gradle-Kotlin« ausgewählt haben. Wir können auch »Gradle-Groovy« wählen, um das Skript in Groovy zu erstellen, oder »Maven«, um Maven als Build-Tool zu nutzen und eine pom.xml-Datei zu erzeugen.

Im Build-Skript finden wir eine Dependency zum Modul spring-boot-starter-web, das uns alle Features mitbringt, um eine Webanwendung zu entwickeln. Wir finden außerdem eine Dependency zum Modul spring-boot-starter-test, das uns einige Testwerkzeuge zur Verfügung stellt, die wir später im Testing-Kapitel noch genauer betrachten.

Das Build-Skript konfiguriert darüber hinaus die Plugins org.spring-framework.boot und io.spring.dependency-management, über die wir in Kapitel 6 noch mehr lernen.

Wir finden des Weiteren die Dateien gradlew und gradlew.bat und den Ordner gradle, die alle zum Gradle Wrapper4 gehören. Die gradlew-Skripte können wir nutzen, um Gradle aufzurufen, ohne Gradle lokal installieren zu müssen. Sie prüfen, ob eine Gradle-Installation verfügbar ist, und laden sie, falls erforderlich, herunter. So können wir unser Projekt auf unterschiedlichen Rechnern laufen lassen, ohne auf jedem Rechner Gradle installieren zu müssen. Wir werden unser Projekt später mit ./gradlew build bauen.

Im src-Ordner finden wir genau drei Dateien:

DemoApplication.java

: Diese Klasse ist der Einstiegspunkt zu unserer Spring-Boot-Anwendung. Sie ist mit der Annotation

@SpringBootApplication

versehen, die wir in

Kapitel 4

genauer untersuchen. Wird die

main()

-Methode dieser Klasse aufgerufen, startet unsere Anwendung.

application.properties

: Dies ist die zentrale Konfigurationsdatei für unsere Anwendung. Diese Datei ist leer, weil unsere Anwendung aktuell mit der Standardkonfiguration von Spring Boot zufrieden ist. Wir schauen uns die Konfiguration einer Spring-Boot-Anwendung im Detail in

Kapitel 5

an.

DemoApplicationTests.java

: Diese Klasse ist mit der Annotation

@SpringBootTest

versehen. Diese sorgt dafür, dass unsere Anwendung für jede

@Test

-Methode gestartet wird. Die leere Test-Methode

contextLoads()

hat augenscheinlich keinen Effekt, da unsere Anwendung aber für diesen Test gestartet wird, prüft dieser leere Test implizit, dass unsere Anwendung fehlerfrei starten kann. Schleicht sich zum Beispiel ein Konfigurationsfehler ein, schlägt dieser Test fehl. Er gibt uns also eine gewisse Sicherheit, obwohl die Test-Methode leer ist. Wir beschäftigen uns mit den Details des Testing mit Spring Boot in

Kapitel 7

.

Das sind auch schon alle Dateien unserer Anwendung. Mehr braucht unsere Anwendung nicht, um zu starten.

2.5Die Anwendung bauen

Was macht man als Erstes, wenn man ein neues Projekt lokal untersucht? Richtig, man führt den Build-Prozess aus, um Vertrauen in das Projekt zu gewinnen. Erst wenn der Build-Prozess problemlos läuft, sollten wir damit anfangen, Änderungen am Projekt vorzunehmen.

In unserem Fall nutzen wir Gradle als Build-Werkzeug, und der Build-Befehl lautet wie folgt:

> ./gradlew build

Mit diesem Befehl starten wir den Gradle Wrapper, der bei Bedarf Gradle herunterlädt, den Code kompiliert, die Tests ausführt und ein Artefakt erzeugt, das unsere Anwendung beinhaltet. Der Build-Prozess sollte mit einem befriedigenden BUILD SUCCESSFUL quittiert werden, wenn alles wie erwartet funktioniert.

Gradle legt während des Builds den Ordner build an, der alle Ergebnisse des Build-Prozesses beinhaltet. Im Ordner build/libs finden wir eine Datei demo-0.0.1-SNAPSHOT.jar (der Name kann variieren, je nachdem, was man bei der Initialisierung des Projekts auf start.spring.io eingegeben hat). Diese Datei ist ein sogenanntes »Fat JAR«, also eine JAR-Datei, die alles beinhaltet, was unsere Anwendung braucht. Wir diskutieren die Build-Prozesse und Fat JARs noch im Detail in Kapitel 6.

Nachdem wir sichergestellt haben, dass unsere Anwendung kompiliert, können wir nun etwas mit ihr herumspielen.

2.6Die Anwendung starten

Wir können unsere Anwendung auf verschiedene Wege starten, um sie auszuprobieren.

Nachdem wir die Anwendung mit ./gradlew build gebaut haben, können wir die erzeugte JAR-Datei direkt mit dem Befehl java -jar <DATEINAME> starten. Diese Art, die Anwendung zu starten, ist einem echten Produktivszenario am ähnlichsten. Die JAR-Datei würde dabei auf eine Zielmaschine kopiert (am besten verpackt in einem Docker-Image, mehr dazu in Kapitel 30) und dort dann einfach gestartet.

In der täglichen Arbeit an unserer Anwendung wäre das allerdings unpraktisch, da wir zuvor immer den Build ausführen und auf deren Fertigstellung warten müssten.

Wir können unsere Anwendung auch direkt aus unserer IDE heraus starten. Wenn wir zum Beispiel in IntelliJ zu unserer Application-Klasse navigieren, sehen wir dort einen grünen »Play«-Button. Dieser startet die Anwendung, und wir können die Ausgabe direkt in der IDE-Konsole verfolgen.

Ein dritter Weg, die Anwendung zu starten, ist es, das Spring-Boot-Plugin von Gradle zu nutzen. Wir können einfach den Kommandozeilenbefehl ./gradlew bootRun ausführen, und die Anwendung wird gestartet. Der Vorteil dieses Vorgehens ist es, dass wir etwaige Konfigurationsparameter unserer Anwendung in der build.gradle.kts-Datei hinterlegen können, sodass alle Entwickler die Anwendung in derselben Konfiguration starten können, ohne immer manuell die Parameter eingeben zu müssen. Aktuell hat unsere Anwendung aber noch keinerlei Konfigurationsparameter, deshalb ist es egal, wie wir sie starten. Das Kapitel 5 befasst sich noch im Detail mit der Konfiguration einer Spring-Boot-Anwendung.

Egal, wie wir die Anwendung starten, das Ergebnis ist aktuell noch nicht sehr beeindruckend. Das Einzige, was wir sehen, sind einige Log-Ausgaben in der Konsole, die hoffentlich mit einer Meldung wie dieser hier enden:

Started DemoApplication in 1.352 seconds

Die Anwendung läuft also. Sie tut aber noch nichts. Das möchten wir nun ändern.

2.7Einen REST-Controller bauen

Das einfachste Element, das wir unserer Anwendung hinzufügen können, um ihr etwas Funktionalität zu geben, ist ein REST-Controller. Da wir das Modul spring-boot-starter-web unserem Projekt schon als Dependency hinzugefügt haben, haben wir bereits das Spring Web MVC Framework im Classpath, das alles mitbringt, was wir dafür brauchen.

Das MVC in »Spring Web MVC« steht für »Model, View, Controller«, ein beliebtes Pattern für Anwendungen, die eine Benutzeroberfläche anbieten. Der Controller nimmt Anfragen entgegen, erzeugt ein Model (oder lädt es aus einer Datenbank) und erzeugt eine View, die das Model für den Benutzer aufbereitet und zur Anzeige bringt. Diese View wird dem Benutzer dann angezeigt, in unserem Fall im Browser.

Ein einfacher »Hello World«-Controller sieht so aus:

Listing 2–1Ein einfacher Web-Controller

@RestController

public class HelloController {

@GetMapping("/hello/{name}")

public String hello(

@PathVariable("name") String name

) {

return String.format("Hello %s", name);

}

}

Wir können diese Klasse einfach neben der DemoApplication-Klasse ablegen und Spring Boot greift sie automatisch auf.

Mit der Annotation @RestController markieren wir die Klasse als REST-Controller. Der Controller bietet also eine REST-Schnittstelle an, die Webanfragen entgegennimmt und ein Textformat als Antwort zurückgibt (üblicherweise JSON). Die »View« aus MVC besteht in diesem Fall also einfach aus Text und nicht aus einer Benutzeroberfläche. Wie wir eine HTML-Benutzeroberfläche mit Spring MVC entwickeln, lernen wir in Kapitel 13.

Mit der Annotation @GetMapping an der hello()-Methode teilen wir Spring MVC mit, dass Anfragen über ein HTTP GET an den Pfad /hello/{name} an diese Methode geroutet werden sollen. Spring Boot startet automatisch einen Webserver, wenn wir Spring MVC benutzen, der HTTP-Anfragen entgegennimmt und an das Framework zur weiteren Verarbeitung übergibt. In der Parameterliste der hello()-Methode nutzen wir die Annotation @PathVariable ("name"), um den Wert der Variable {name} im Pfad in den Methodenparameter name zu injizieren.

In der Methode nutzen wir die name-Variable dann, um einen String zusammenzubauen und an den Aufrufer zurückzugeben. Diesen String können wir als das Model aus MVC betrachten, das in diesem Fall auch gleichzeitig die View ist, weil wir es ungefiltert an den Aufrufer zurückgeben.

Wir können die Anwendung nun starten und im Browser zum Beispiel http://localhost:8080/hello/bob aufrufen; wir sollten dann den String »Hello Bob« im Browser sehen. Spring Boot startet den Webserver standardmäßig auf Port 8080, das ist aber konfigurierbar.

Damit haben wir bereits eine erste funktionsfähige Spring-Boot-Anwendung gebaut! Die Annotationen, die wir benutzt haben, mögen vielleicht noch etwas magisch erscheinen, aber keine Angst: Die Annotationen müssen nicht auswendig gelernt werden. Im Rest des Buches werden wir diese Magie entzaubern und Ihnen Ressourcen an die Hand geben, die es Ihnen erleichtern, die richtigen Annotationen für Ihren Anwendungsfall zu finden. Für die Arbeit mit REST-Controllern tun wir das konkret in Kapitel 9.

3Spring-Grundlagen

Spring Boot basiert auf dem Spring Framework. Um Spring Boot zu verstehen, müssen wir die Grundlagen des Spring Frameworks verstehen, die wir in diesem Kapitel diskutieren.

3.1Dependency Injection und Inversion of Control

Spring ist im Kern ein Dependency Injection Framework. Es bietet mittlerweile zwar eine Menge anderer Features, die das Entwicklerleben vereinfachen, aber diese bauen meist auf dem Dependency Injection Framework auf.

Dependency Injection wird oft mit Inversion of Control gleichgesetzt. Wir möchten die beiden Begriffe hier kurz erläuternd in das Spring Framework einordnen und diskutieren, wie Dependency Injection mit dem Spring Framework funktioniert.

3.1.1Inversion of Control

Das Konzept von Inversion of Control (Kontrollumkehr) ist es, die Kontrolle über den Aufruf von Programmcode an ein Framework abzugeben. Dies kann zum Beispiel in Form einer Funktion geschehen, die wir selbst programmieren, dann aber an ein Framework übergeben, das sie anschließend zum richtigen Zeitpunkt aufruft. Diese Funktion nennen wir »Callback«.

Ein Beispiel für einen Callback ist eine Funktion, die in einer Serveranwendung ausgeführt werden soll, wenn eine bestimmte URL aufgerufen wird. Wir programmieren die Funktion, aber wir rufen sie nicht selbst auf. Stattdessen übergeben wir die Funktion an ein Framework, das auf HTTP-Anfragen auf einem bestimmten Port horcht, die Anfrage analysiert und nach bestimmten Parametern dann an eine der registrierten Callback-Funktionen weiterleitet. Das Spring-Web-MVC-Projekt basiert auf genau diesem Mechanismus. Wir werden uns später noch mit Web MVC auseinandersetzen.

3.1.2Dependency Injection

Dependency Injection ist eine konkrete Ausprägung von Inversion of Control. Wie der Name andeutet, geht es bei Dependency Injection um Abhängigkeiten. Eine Klasse A ist abhängig von einer anderen Klasse B, wenn die Klasse A eine Methode von B aufruft. In Programmcode wird diese Abhängigkeit häufig in der Form eines Attributs in A vom Typ B ausgedrückt:

Listing 3–1Der Service erzeugt sein eigenes UserDatabase-Objekt.

In diesem Beispiel benötigt der GreetingService ein Objekt vom Typ UserDatabase, um seine Arbeit zu machen. Wenn wir ein Objekt vom Typ GreetingService instanziieren, instanziiert es automatisch ein Objekt vom Typ UserDatabase.

Die Klasse GreetingService ist also selbst dafür verantwortlich, die Abhängigkeit zu UserDatabase aufzulösen. Dies ist aus mehreren Gründen problematisch.

Zunächst erzeugt diese Lösung eine sehr starke Kopplung der beiden Klassen. Der GreetingService muss wissen, wie ein UserDatabase-Objekt erzeugt wird. Was wäre, wenn die Erzeugung eines UserDatabase-Objekts nicht so einfach ist? Um eine Datenbankverbindung zu öffnen, werden üblicherweise einige Parameter benötigt:

Listing 3–2Der Service braucht Parameter, um selbst ein UserDatabase-Objekt zu erzeugen.

Der GreetingService erzeugt immer noch seine eigene Instanz vom Typ UserDatabase, aber er muss plötzlich wissen, welche Parameter eine Datenbankverbindung benötigt. Die Kopplung zwischen GreetingService und UserDatabase ist soeben noch stärker geworden. Wir möchten diese Details im GreetingService gar nicht sehen!

Was, wenn andere Klassen in unserer Anwendung auch ein UserDatabase-Objekt benötigen? Wir möchten nicht, dass jede Klasse wissen muss, wie man ein UserDatabase-Objekt erzeugt!

Durch die starke Kopplung werden Details der Klasse UserDatabase über die ganze Codebase verteilt. Eine Änderung an UserDatabase würde also viele Änderungen an anderen Stellen im Code nach sich ziehen.

Dies erschwert nicht nur die Entwicklung des Anwendungscodes, sondern auch das Schreiben von Tests. Wenn wir die Klasse GreetingService testen möchten, brauchen wir in diesem Beispiel die URL und den Port einer echten Datenbank. Wenn wir ungültige Verbindungsparameter übergeben, funktioniert die Methode greet() nicht mehr!

Um die starke Kopplung zwischen den Klassen aufzuheben, ändern wir den Code, sodass wir die Abhängigkeit in den Konstruktor »injizieren« können:

Listing 3–3Der Service bekommt das UserDatabase-Objekt im Konstruktor »injiziert«.

Es gibt immer noch eine Kopplung zwischen GreetingService und UserDatabase, aber diese ist viel loser als vorher, denn GreetingService muss nun nicht mehr wissen, wie ein UserDatabase-Objekt erzeugt wird. Die Kopplung ist auf das erforderliche Minimum reduziert. Dieses Pattern wird »Constructor Injection« genannt, da wir die Abhängigkeiten einer Klasse in Form von Konstruktorparametern übergeben.

In einem Test können wir nun ein Mock-Objekt vom Typ UserDatabase erzeugen (zum Beispiel mit einer Mock-Bibliothek wie Mockito; mehr dazu in Kap. 7) und dieses an den GreetingService übergeben. Da wir das Verhalten des Mocks kontrollieren, brauchen wir keine Verbindung zu einer echten Datenbank mehr, um die Klasse GreetingService zu testen.

Im Anwendungscode instanziieren wir die Klasse UserDatabase nur einmal und übergeben diese Instanz an alle Klassen, die sie benötigen. Anders ausgedrückt »injizieren« wir die Abhängigkeit zu UserDatabase in die Konstruktoren der anderen Klassen.

Dieses »Injizieren« von Abhängigkeiten kann in einer echten Anwendung mit mehreren hundert Klassen sehr umständlich werden, da wir alle Klassen in der richtigen Reihenfolge instanziieren und die Abhängigkeiten zwischen ihnen explizit ausprogrammieren müssen. Die Folge ist sehr viel »Boilerplate«-Code, der sich häufig ändert und uns von der eigentlichen Entwicklung abhält.

Genau hier kommt Dependency Injection ins Spiel. Ein Dependency Injection Framework wie Spring übernimmt die Aufgabe, den Großteil der Klassen unserer Anwendung zu instanziieren, sodass wir uns nicht mehr darum kümmern müssen. Hier wird deutlich, dass Dependency Injection eine Ausprägung von Inversion of Control ist, denn wir geben die Kontrolle über die Instanziierung unserer Objekte an das Dependency Injection Framework ab.

Die Aufgabenteilung zwischen Spring und uns als Entwicklern sieht in etwa so aus:

Wir programmieren die Klassen

GreetingService

und

UserDatabase

.

Wir drücken die Abhängigkeit zwischen den beiden Klassen durch einen Parameter vom Typ

UserDatabase

im Konstruktor von

GreetingService

aus.

Wir teilen Spring mit, dass es die Kontrolle über die Klassen

GreetingService

und

UserDatabase

übernehmen soll.

Spring instanziiert die Klassen in der richtigen Reihenfolge, um Abhängigkeiten aufzulösen, und erzeugt ein Objektnetzwerk mit einem Objekt für jede übergebene Klasse.

Wenn wir ein Objekt vom Typ

GreetingService

oder

UserDatabase

benötigen, fragen wir Spring nach diesem Objekt.

In einer echten Anwendung verwaltet Spring natürlich nicht nur zwei Objekte, sondern ein komplexes Netzwerk von hunderten oder tausenden Objekten. Dieses Netzwerk wird in Spring »Application Context« genannt, da es den Kern unserer Anwendung ausmacht.

Wie der Application Context funktioniert, schauen wir uns im nächsten Abschnitt an.

3.2Der Spring Application Context

Die Objekte im Application Context werden »Beans« genannt. Wenn man nicht aus der Java-Welt kommt, ist der Begriff »Bean« (Bohne) vermutlich eher verwirrend. Spring ist ein Framework für die Programmiersprache Java. Java ist der Name einer Insel in Indonesien, auf der Kaffee angebaut wird. Die Kaffee-Art wird ebenfalls »Java« genannt. Und Kaffee wird aus Kaffeebohnen hergestellt. In der Java Community hat man sich deshalb dafür entschieden, bestimmte Objekte in Java (der Programmiersprache) »Beans« zu nennen. Ziemlich weit hergeholt, aber der Begriff hat sich eingebürgert.

Der Application Context ist also im Grunde nur ein Netzwerk von Java-Objekten, genannt »Beans«. Spring instanziiert diese Beans für uns und löst die Abhängigkeiten zwischen den Beans über Constructor Injection auf.

Woher weiß Spring aber, welche Beans es für uns erzeugen und in seinem Application Context verwalten soll? Hier kommt der Begriff »Konfiguration« ins Spiel.

Eine Konfiguration im Kontext von Spring ist eine Definition der Beans, die wir für unsere Anwendung benötigen. Im einfachsten Fall ist dies eine Liste von Klassen. Spring nimmt diese Klassen, instanziiert sie und nimmt die entstandenen Objekte (Beans) in den Application Context auf.

Wenn die Instanziierung der Klassen nicht möglich ist (zum Beispiel, wenn ein Bean-Konstruktor eine andere Bean erwartet, die aber nicht Teil der Konfiguration ist), bricht Spring die Erstellung des Application Context mit einer Exception ab.

Dies ist einer der Vorteile, den Spring bietet: Eine fehlerhafte Konfiguration führt in den meisten Fällen dazu, dass die Anwendung gar nicht erst startet und somit keinen Schaden zur Laufzeit anrichten kann.

Es gibt mehrere Wege, eine Spring-Konfiguration zu erstellen. In den meisten Anwendungsfällen ist es komfortabel und praktisch, die Konfiguration in Java zu programmieren. In Anwendungsfällen, wo der Quellcode komplett frei von Abhängigkeiten zum Spring Framework sein soll, kann aber auch eine Konfiguration mit XML sinnvoll sein.

3.3Application Context mit XML konfigurieren

Zu den Anfängen von Spring musste der Application Context mit XML konfiguriert werden. Die Konfiguration mit XML ermöglicht es, die Konfiguration und den Code komplett voneinander zu trennen. Der Code muss nicht wissen, dass er von Spring verwaltet wird.

Eine Beispielkonfiguration in XML sieht so aus:

Listing 3–4Beans mit XML definieren

<?xml version="1.0" encoding="UTF-8"?>

<beans>

<bean id="userDatabase"

class="de.springboot3.xml.UserDatabase"/>

<bean id="greetingService"

class="de.springboot3.xml.GreetingService">

<constructor-arg ref="userDatabase"/>

</bean>

</beans>

In dieser Konfiguration werden die Beans userDatabase und greetingService definiert. Jede Bean-Deklaration ist eine Anleitung für Spring, wie diese Bean instanziiert wird.

Die Klasse UserDatabase hat einen Default-Konstruktor ohne Parameter, weshalb es ausreicht, Spring den Namen der Klasse zu nennen. Die Klasse GreetingService hat einen Konstruktorparameter vom Typ UserDatabase, weshalb wir mit dem constructor-ref-Element auf die zuvor deklarierte userDatabase-Bean verweisen.

Mit dieser XML-Deklaration können wir nun einen Application Context erzeugen:

Listing 3–5Einen Application Context aus einer XML-Datei erzeugen

public class XmlConfigMain {

public static void main(String[] args) {

new ClassPathXmlApplicationContext(

"application-context.xml");

applicationContext.getBean(

GreetingService.class);

System.out.println(greetingService.greet(1));

}

}

Wir übergeben dem Konstruktor von ClassPathXmlApplicationContext die XML-Konfiguration, und Spring erzeugt daraus ein ApplicationContext-Objekt für uns.

Dieser ApplicationContext ist nun unser IoC-Container, und wir können ihn zum Beispiel nach einer Bean vom Typ GreetingService fragen.

Die Konfiguration per XML sieht in diesem Beispiel noch recht überschaubar aus, kann aber in größeren Anwendungen sehr umfangreich werden. Für uns als Java-Entwickler wäre es doch angenehm, wenn wir eine solch umfangreiche Konfiguration in Java selbst verwalten und die Vorteile des Java-Compilers und der IDE für uns nutzen könnten.

3.4Java-Konfiguration im Detail

Spätestens seit dem Erfolg von Spring Boot wird die XML-Konfiguration meist nur noch in Sonderfällen und Legacy-Anwendungen genutzt, und die Konfiguration mit Java ist zum Standard geworden. Deshalb werden wir uns diesen Weg, auch »Java-Config« genannt, genauer anschauen.

3.4.1@Configuration und @Bean

Der Kern einer Java-Konfiguration ist eine Java-Klasse, die mit der Spring-Annotation @Configuration annotiert ist:

Listing 3–6Eine einfache @Configuration-Klasse

@Configuration

public class GreetingConfiguration {

@Bean

UserDatabase userDatabase() {

return new UserDatabase();

}

@Bean

GreetingService greetingService(

UserDatabase userDatabase) {

return new GreetingService(userDatabase);

}

}

Diese Konfiguration ist äquivalent zu der XML-Konfiguration aus dem letzten Abschnitt. Mit der Annotation @Configuration teilen wir Spring mit, dass diese Klasse einen Teil des Application Context beisteuert. Ohne diese Annotation wird Spring nicht aktiv.

Eine Konfigurationsklasse kann dann Factory-Methoden wie hier userDatabase() und greetingService() deklarieren, die jeweils ein Objekt erzeugen. Mit der Annotation @Bean markieren wir solche Factory-Methoden. Spring findet diese Methoden und ruft sie auf, um einen Application Context zu erzeugen.

Abhängigkeiten zwischen Beans, wie hier die Abhängigkeit von GreetingService zu UserDatabase, werden über Parameter der Factory-Methoden aufgelöst. In unserem Fall wird Spring zuerst die Methode userDatabase() aufrufen, um eine UserDatabase-Bean zu erzeugen, und diese dann in die Methode greetingService() übergeben, um eine GreetingService-Bean zu erzeugen.

Mithilfe der Klasse AnnotationConfigApplicationContext können wir dann einen ApplicationContext erzeugen:

Listing 3–7Einen Application Context aus einer Java-Config erzeugen

public class JavaConfigMain {

public static void main(String[] args) {

new AnnotationConfigApplicationContext(

GreetingConfiguration.class);

applicationContext.getBean(

GreetingService.class);

System.out.println(greetingService.greet(1));

}

}

Der Konstruktor von AnnotationConfigApplicationContext erlaubt uns auch, mehrere Konfigurationsklassen zu übergeben anstatt nur eine einzige. Dies ist hilfreich für größere Anwendungen, denn wir können die Konfiguration von vielen Beans in mehrere Konfigurationsklassen aufteilen, um die Übersicht zu bewahren.

3.4.2@Component und @ComponentScan

Die Konfiguration Hunderter oder gar Tausender von Beans via Java für eine große Anwendung kann sehr mühselig werden. Um dies zu vereinfachen, bietet Spring die Möglichkeit, im Java-Classpath nach Beans zu »scannen«.

Dieser Scan wird mit der Annotation @ComponentScan aktiviert:

Listing 3–8Scannen nach Beans im Package de.springboot3

@Configuration

@ComponentScan("de.springboot3")

public class GreetingScanConfiguration {

}

Wie zuvor erstellen wir eine Konfigurationsklasse (annotiert mit @Configuration). Hier definieren wir die Beans aber nicht selbst in Form von Factory-Methoden annotiert mit der @Bean-Annotation, sondern fügen die neue Annotation @ComponentScan hinzu.

Mit dieser Annotation weisen wir Spring an, im Package de.springboot3 nach Beans zu scannen. Findet der Scan eine Klasse, die mit der Annotation @Component versehen ist, wird aus dieser Klasse eine Bean erzeugt (das heißt, die Klasse wird instanziiert und dem Spring Application Context hinzugefügt).

Wir müssen also einfach alle Klassen, für die Spring eine Bean erzeugen soll, wie folgt mit der @Component-Annotation versehen:

Listing 3–9Mit @Component annotierte Klassen werden zu Beans.

@Component

public class GreetingService {

// …

}

@Component

public class UserDatabase {

// ...

}

Abhängigkeiten zwischen den Beans werden wie zuvor durch Konstruktorparameter ausgedrückt, und Spring löst diese automatisch auf – dieser Mechanismus wird häufig auch »Autowiring« genannt.

@Bean vs. @Component

Die Annotationen @Bean und @Component drücken ein ähnliches Konzept aus: Beide markieren ein Bean, das dem Application Context von Spring hinzugefügt werden soll. Diese Ähnlichkeit kann insbesondere zu Anfang verwirren.

Der Hauptunterschied beider Ansätze ist, dass wir mit der @Bean-Annotation selbst die Kontrolle über die Instanziierung der Beans haben und mit der @Component-Annotation Spring die Instanziierung überlassen. @Bean ist nur an Methoden erlaubt und @Component nur an Klassen.

Der Java-Compiler hilft uns hier etwas, denn er meckert, wenn wir eine @Bean-Annotation an einer Klasse benutzen oder eine @Component-Annotation an einer Methode. Wir können sie also nicht verwechseln. Wir können aber sehr wohl Methoden und Klassen annotieren, die von Spring gar nicht erkannt werden!

Spring wertet die @Bean-Annotation nur innerhalb einer @Configuration-Klasse und die @Component-Annotation nur an Klassen aus, die von einem Component-Scan gefunden werden!

3.4.3@Configuration und @ComponentScan kombinieren

Spring schreibt uns nicht vor, wie wir die Beans unserer Anwendung konfigurieren. Wir können sie per XML oder per Java-Config konfigurieren oder gar beide Varianten miteinander kombinieren. Wir können auch explizite Bean-Definitionen per @Bean-Methode und einen Scan per @ComponentScan kombinieren:

Listing 3–10Mischen von Component-Scan und Java-Config

@Configuration

@ComponentScan("de.springboot3.java.mixed")

class MixedConfiguration {

@Bean

GreetingService greetingService(

UserDatabase userDatabase) {

return new GreetingService(userDatabase);

}

}

// no @Component annotation!

class GreetingService {…}

@Component

class UserDatabase {...}

In dieser Konfiguration erzeugt Spring eine Bean vom Typ UserDatabase, da die Klasse mit @Component annotiert ist und ein @ComponentScan konfiguriert ist. Die Bean vom Typ GreetingService hingegen wird durch die explizite @Bean-annotierte Factory-Methode definiert.

Modulare Konfiguration

Die Konfiguration einer größeren Anwendung mit hunderten Beans kann schnell unübersichtlich werden.

Die explizite Konfiguration per @Bean-Annotation hat den Vorteil, dass die Konfiguration der Beans in einigen wenigen @Configuration-Klassen gebündelt ist und so leicht zu begreifen ist.

Die implizite Konfiguration per @ComponentScan und @Component hat den Vorteil, dass wir nicht jede Bean selbst definieren müssen, ist dafür aber über sehr viele @Component-Annotationen und somit über die gesamte Codebase verteilt und schwieriger zu begreifen.

Ein bewährtes Prinzip ist es, die Spring-Konfiguration entlang der Architektur der Anwendung zu gestalten. Jedes Modul der Anwendung sollte in seinem eigenen Package liegen und seine eigene @Configuration-Klasse besitzen. In dieser @Configuration-Klasse können wir dann entweder einen @ComponentScan für das Modulpackage konfigurieren oder eine explizite Konfiguration per @Bean-Methoden. Um die Module zu einer Gesamtanwendung zusammenzuführen, erstellen wir eine übergeordnete @Configuration-Klasse, die einen @ComponentScan für das Hauptpackage definiert. Dieser Scan wird alle @Configuration-Klassen in diesem und den untergeordneten Packages aufgreifen.

3.5Was haben wir vom Spring Container?

Wir wissen nun, dass Spring uns einen IoC-Container bietet, der Objekte (Beans) für uns instanziiert und verwaltet. Das erspart uns die Arbeit, den Lebenszyklus dieser Objekte selbst zu verwalten.

Aber das ist erst der Anfang. Da Spring die Kontrolle über alle Beans hat, kann Spring eine Menge anderer Aufgaben für uns übernehmen. Zum Beispiel kann Spring die Aufrufe von Bean-Methoden abfangen, um zum Beispiel eine Datenbanktransaktion zu starten oder zu committen. Wir können Spring auch als Event-Bus nutzen. Dafür senden wir ein Event an den Spring-Container, und Spring leitet das Event an alle interessierten Beans weiter.

Diesen und vielen anderen Features werden wir im Rest des Buches auf den Grund gehen. Die Grundlage all dieser Features ist das Spring-Programmiermodell, dessen Kern wir in diesem Kapitel bereits kennengelernt haben. Dieses Programmiermodell ist eine Mischung aus Annotationen, Konventionen und Interfaces, die wir zu einer Gesamtanwendung kombinieren können.

4Spring-Boot-Grundlagen

Spring Boot baut auf dem Spring Framework auf und bietet eine Menge an zusätzlichen Funktionen und Integrationen. Etwas vereinfacht kann man sagen, dass das Spring Framework sich auf Funktionen rund um den Application Context fokussiert, während Spring Boot darauf aufbauend Funktionen bereitstellt, die man in vielen Anwendungen im Produktivbetrieb benötigt oder die das Entwicklerleben vereinfachen. In diesem Kapitel geben wir einen Überblick über die Kernfunktionen von Spring Boot, die wir in späteren Kapiteln noch genauer betrachten werden.

4.1Bootstrapping

Der Name »Spring Boot« stammt von der Kernfunktionalität von Spring Boot: dem Bootstrapping. Mit »Bootstrapping« ist in diesem Fall der Prozess gemeint, einen Haufen Quellcode in eine Anwendung zu überführen und den Benutzern zugänglich zu machen.

Wir haben im vorherigen Kapitel bereits gesehen, wie wir mithilfe von Spring (ohne Spring Boot) einen ApplicationContext erzeugen und starten können. Wie aber machen wir aus diesem ApplicationContext eine richtige Anwendung? Genau hierbei hilft Spring Boot.

Anstatt dass wir manuell einen ApplicationContext erzeugen, lassen wir Spring Boot dies für uns tun. Wir stellen lediglich eine main()-Methode zur Verfügung, die als Spring-Boot-Anwendung deklariert ist:

Listing 4–1Code, um eine Spring-Boot-Anwendung zu starten

@SpringBootApplication

public class SpringBootBasicsApplication {

public static void main(String[] args) {

SpringApplication.run(

SpringBootBasicsApplication.class, args);

}

}

In der main()-Methode rufen wir die statische Methode SpringApplication.run() auf und reichen eine Klasse hinein, die mit der Annotation @SpringBootApplication versehen ist. Üblicherweise ist dies dieselbe Klasse, die auch die main()-Methode beinhaltet, wie im Beispiel oben.

Damit wir die Klassen @SpringBootApplication und SpringApplication zur Verfügung haben, müssen wir das Modul org.springframework.boot:spring-boot-starter als Dependency in unserer pom.xml- oder build.gradle.kts-Datei deklarieren.

Sobald wir die main-Methode zum Beispiel in unserer IDE starten, sehen wir folgende Log-Ausgabe:

Listing 4–2Log-Ausgabe beim Starten einer Spring-Boot-Anwendung

2023-07-03T11:48:41.624+10:00 INFO 44185 ---

[main] d.s.s.SpringBootBasicsApplication:

Starting SpringBootBasicsApplication using Java 18 with PID 44185

2023-07-03T11:48:41.629+10:00 INFO 44185 ---

[main] d.s.s.SpringBootBasicsApplication:

No active profile set, falling back to 1 default profile: "default"

2023-07-03T11:48:42.482+10:00 INFO 44185 ---

[main] d.s.s.SpringBootBasicsApplication:

Started SpringBootBasicsApplication in 1.288 seconds (process running for 1.87)

Process finished with exit code 0

Dieser Log-Auszug zeigt uns bereits einige interessante Dinge:

Wir nutzen Spring Boot in Version 3.0.1.

Wir nutzen Java 18.

Spring Boot startet im

default

-Profil, weil wir kein bestimmtes Profil definiert haben. Das bedeutet, dass Spring Boot das Konzept von »Profilen« unterstützt! Was genau ein Profil ist und wie wir es nutzen können, sehen wir später in

Kapitel 5

.

Es hat 1,288 Sekunden gedauert, die Spring-Boot-Anwendung zu starten.

Spring Boot hat das Logging für uns konfiguriert, sodass unsere Log-Ausgaben mit Datum, Log-Level, Prozess-ID, Thread-Name usw. formatiert sind.

Spring Boot hat also automatisch bereits einiges getan, das uns hilft, eine Anwendung zu starten, und wir werden im Rest des Buchs noch viele weitere Dinge lernen, die Spring Boot für uns tun kann.

Nach der oben gezeigten Log-Ausgabe beendet sich unsere Beispielanwendung allerdings direkt wieder, ohne irgendetwas getan zu haben, und gibt den Rückgabewert 0 an die Kommandozeile zurück, der angibt, dass die Anwendung erfolgreich beendet wurde. Das ist wenig hilfreich, deshalb schauen wir uns einige weitere Features von Spring Boot an, die uns helfen, eine echte Anwendung zu bauen.

4.2Den Application Context beeinflussen

In Kapitel 3 haben wir bereits gesehen, wie man einen ApplicationContext beeinflussen kann. Genau wie in einer Spring-Anwendung ist der ApplicationContext das Herz einer Spring-Boot-Anwendung, und wir können ihn genauso beeinflussen, wie wir es aus Spring gewohnt sind (mit einigen Extras, die wir in Kapitel 32 lernen werden).

Wenn wir uns den Quellcode der Annotation @SpringBootApplication anschauen, sehen wir Folgendes:

Listing 4–3Meta-Annotationen an der Annotation SpringBootApplication

Wir erkennen die @ComponentScan-Annotation aus dem letzten Kapitel! Zusätzlich sehen wir die Annotation der @SpringBootConfiguration, deren Quellcode wie folgt aussieht:

Listing 4–4Meta-Annotationen an der Annotation SpringBootConfiguration

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Configuration

@Indexed

public @interface SpringBootConfiguration {

...

}

Hier erkennen wir die Annotation @Configuration wieder!

Annotationen an Annotationen nennt man Meta-Annotationen. Wenn Spring die Annotationen von Klassen untersucht, ist das Framework schlau genug, auch die Meta-Annotationen (und Meta-Meta-Annotationen usw.) auszuwerten. Für uns bedeutet das, dass unsere Hauptklasse SpringBootBasicsApplication sowohl mit @ComponentScan als auch mit @Configuration (meta-)annotiert ist.

Da unsere Hauptklasse mit @ComponentScan annotiert ist, wertet Spring automatisch alle @Component- und @Configuration-Annotationen im Package der Klasse und allen Packages darunter aus, instanziiert Objekte der annotierten Klassen und fügt sie dem ApplicationContext hinzu, wie in Kapitel 3 beschrieben.

Außerdem behandelt Spring unsere Hauptklasse als @Configuration-Klasse, sodass wir @Bean-annotierte Factory-Methoden direkt hier hinzufügen können, wenn wir dies möchten.

Um ein »Hello World« zu implementieren, könnten wir unsere Anwendung also zum Beispiel wie folgt erweitern:

Listing 4–5Eine minimale, lauffähige Spring-Boot-Anwendung

@SpringBootApplication

public class SpringBootBasicsApplication {

public static void main(String[] args) {

SpringApplication.run(

SpringBootBasicsApplication.class, args);

}

@Bean

public HelloBean helloBean() {

return new HelloBean();

}

static class HelloBean {

public HelloBean() {

System.out.println("Hello!");

}

}

}

Wir erzeugen eine Bean vom Typ HelloBean, die im Konstruktor eine Nachricht ausgibt. Starten wir die Anwendung erneut, sehen wir diese Log-Ausgabe, bevor die Anwendung stoppt.

Bisher haben wir gesehen, wie wir Spring Boot nutzen können, um eine Anwendung zu entwickeln, die eine einzige Aktion durchführt (in unserem Fall »Hello!« auf der Kommandozeile auszugeben). In Kapitel 15 werden wir sehen, wie wir dies nutzen können, um vollumfängliche Kommandozeilen-Programme zu entwickeln.

Viele Anwendungen, die wir heutzutage entwickeln, sind aber Webanwendungen und keine Kommandozeilen-Programme, weshalb wir uns als Nächstes den Embedded-Webserver-Features von Spring Boot widmen.

4.3Embedded Webserver

Traditionell wurden Java-basierte Webanwendungen auf Application-Servern wie Tomcat oder JBoss deployt. Die Anwendung wurde dabei als WAR-Datei (Web Archive) verpackt und auf den Server kopiert, der das Archiv dann auspackte, einige Metadaten aus Deskriptor-XML-Dateien auslas und HTTP-Requests an die von der Anwendung definierten Endpoints weiterleitete.

Spring Boot hat das traditionelle Deployment von Webanwendungen auf den Kopf gestellt. Anstatt eine WAR-Datei zu erzeugen und auf einen Server zu kopieren, erzeugen wir mit Spring Boot standardmäßig eine »runnable« JAR-Datei (auch »Fat JAR« genannt), die unseren Anwendungscode, alle Dependencies und einen Webserver beinhaltet. Starten wir diese JAR-Datei mit dem Befehl java -jar app.jar, so startet Spring Boot automatisch den Webserver, der dann HTTP-Requests entgegennimmt und an unsere Anwendung weiterleitet.

Möchten wir eine Webanwendung entwickeln, müssen wir lediglich das Modul org.springframework.boot:spring-boot-starter-web als Depedency in unserer pom.xml- oder build.gradle.kts-Datei ergänzen.

Wenn wir die Anwendung dann starten, sehen wir die folgende Log-Ausgabe zusätzlich zu den Log-Ausgaben von zuvor:

Listing 4–6Meldung nach einem erfolgreichen Start des Embedded-Tomcat-Servers

Tomcat started on port(s): 8080 (http) with context path ''

Spring Boot hat automatisch einen Tomcat-Server gestartet, der auf Port 8080 horcht, um HTTP-Requests zu empfangen. Unsere Anwendung stoppt nun nicht mehr direkt nach dem Start, sondern wartet auf HTTP-Requests so lange, bis sie beendet wird (oder abstürzt).

Der Embedded Webserver macht die Auslieferung einer Spring-Boot-Webanwendung sehr einfach. Wir können die JAR-Datei zum Beispiel in ein Docker-Image verpacken und mit dem Befehl java -jar app.jar starten. Mit dem Profilkonzept, das wir bereits in der Log-Ausgabe oben gesehen haben, macht Spring Boot es uns auch sehr einfach, die Anwendung für verschiedene Umgebungen (development, staging, production, …) zu konfigurieren. Hierfür müssen wir lediglich eine Datei wie application-<profile>.yml mit in die JAR-Datei packen (oder neben der JAR-Datei ablegen) und das Profil aktivieren. Dies werden wir im Detail noch in Kapitel 5 kennenlernen.

4.4Dependency Management

Ein weiteres Kernfeature von Spring Boot ist das Dependency Management.

Da wir in unserer Anwendung nicht jedes Rad neu erfinden möchten, nutzen wir häufig eine große Anzahl an Bibliotheken, die wir in unserer pom.xml- oder build.gradle.kts-Datei als Dependencies definieren. Jede dieser Libraries existiert in Versionen, die mit unserem Code (und den anderen Bibliotheken) kompatibel sind, und oft in Versionen, die veraltet (oder zu neu) und mit unserer Anwendung nicht kompatibel sind. Wir müssen die Versionen all unserer Dependencies verwalten, sodass sie stets miteinander kompatibel bleiben.

Hier schafft Spring Boot Abhilfe. Spring Boot bietet uns eine sogenannte »Bill of Materials« (BOM), die eine Menge an Libraries und deren Versionen definiert, sodass sie stets miteinander und mit der aktuellen Spring-Boot-Version kompatibel sind. Diese BOM beinhaltet natürlich nicht alle Bibliotheken, die es gibt, aber alle Bibliotheken, die von Spring Boot selbst oder von einer der offiziellen Integrationen zwischen Spring Boot und anderen Produkten (wie zum Beispiel Datenbanken) benötigt werden.

Um die Spring-Boot-BOM zu nutzen, müssen wir lediglich das Modul org.springframework.boot:spring-boot-dependencies in derselben Version wie Spring Boot in unsere pom.xml- oder build.gradle.kts-Datei importieren. Nutzen wir das Spring-Boot-Plugin für Maven oder Gradle, erledigt das Plugin dies automatisch für uns. Wenn wir dann eine Dependency zu einer Bibliothek deklarieren, können wir die Version der Bibliothek einfach weglassen und sie wird automatisch in der Version geladen, die in der Spring-Boot-BOM definiert ist. In Kapitel 6 gehen wir in die Details des Maven- und Gradle-Plugins für Spring Boot.

4.5Integrationen

Spring Boot bringt einige Integrationen mit häufig genutzten Bibliotheken mit, sodass wir diese Integrationen nur noch konfigurieren, aber nicht mehr selbst programmieren müssen.

Ein Beispiel hierfür ist die Integration mit einer Datenbank. Fügen wir beispielsweise das Modul org.postgresql:postgresql in unserer pom.xml- oder build.gradle.kts-Datei hinzu (ohne Versionsnummer, denn dank Spring Boots Dependency Management wird automatisch die aktuelle kompatible Version genutzt), erkennt Spring Boot den Postgre SQL-Treiber im Classpath und stellt automatisch ein DataSource-Objekt im ApplicationContext zur Verfügung, das wir dann nutzen können, um auf die Datenbank zuzugreifen. Wir müssen Spring Boot lediglich noch die URL, den Benutzernamen und das Passwort der Datenbank mitteilen, indem wir einige Konfigurationsparameter in der zentralen Konfigurationsdatei von Spring Boot namens application.yml hinzufügen.

Das von Spring Boot zur Verfügung gestellte DataSource-Objekt kann von uns direkt genutzt werden. Es wird aber auch von weiteren Integrationen genutzt. Fügen wir beispielsweise eine Dependency zu Spring Data JPA oder Spring Data JDBC hinzu, erkennt Spring Boot dies ebenfalls und stellt diesen das DataSource-Objekt zur Verfügung. Wir müssen die DataSource dann nicht mehr direkt benutzen, sondern können die Abstraktionen von Spring Data nutzen, um auf die Datenbank zuzugreifen, was uns das Leben deutlich vereinfacht.

Dieses Beispiel der Datenbankintegration folgt einem typischen Muster für Spring-Boot-Integrationen. Spring Boot erkennt eine Dependency im Classpath und konfiguriert automatisch einige Beans im Application Context, die dann von uns (oder anderen Integrationen) genutzt werden können. Benötigt die Integration noch weitere Konfigurationsparameter, können wir diese in der Datei application.yml zur Verfügung stellen. Müssen wir die von der Integration zur Verfügung gestellten Beans darüber hinaus noch beeinflussen, können wir die Beans im Application Context »überschreiben«, indem wir sie selbst als @Bean oder @Component definieren.

Die Integration mit einer Datenbank wird in so vielen Anwendungen benötigt, dass Spring Boot sie im Kern bereits mitliefert. Andere Integrationen (wie zum Beispiel mit einem Cache oder einem Messaging-System) werden nicht in jeder Anwendung benötigt, sodass wir sie erst mit einem sogenannten »Starter« aktivieren müssen. Möchten wir in unserer Anwendung beispielsweise einen Redis-Cache benutzen, müssen wir das Modul org.springframework.boot:spring-boot-starter-data-redis hinzufügen, das dann die Integration mit Redis aktiviert. Ein »Starter« ist also eine kleine Bibliothek, die uns den »Start« mit einem bestimmten Feature oder einer Integration vereinfacht. Wir setzen uns mit Startern in Kapitel 32 noch näher auseinander.

4.6Produktionsbetrieb

Neben dem Bootstrapping und dem Embedded Webserver bringt Spring Boot noch einige Features mit, die es uns deutlich erleichtern, eine Anwendung in Produktion zu betreiben.

Eines der wichtigsten Features für den Produktionsbetrieb ist Logging. Wir haben bereits die automatische Konfiguration der Log-Ausgaben gesehen, sodass zum Beispiel das Datum und der Name des Threads in jeder Logzeile auftauchen, was uns das Debugging erleichtert. Spring Boot setzt hierfür auf den De-facto-Standard SLF4J, sodass wir lediglich ein Logger-Objekt definieren müssen und dieses dann für Log-Ausgaben nutzen können. Wir schauen uns das Thema Logging noch detaillierter in Kapitel 29 an.

Auch das Profilfeature haben wir bereits gesehen. Wir können Konfigurationsparameter definieren, die je nach Profil einen unterschiedlichen Wert besitzen. Beim Start der Anwendung können wir dann angeben, welche Profile (also welche Konfigurationswerte) aktiviert werden sollen. So können wir dieselbe Anwendung in unterschiedlichen Umgebungen betreiben und müssen lediglich die Konfiguration pro Umgebung anpassen. Dieses Feature ist aus der modernen Softwareentwicklung nicht mehr wegzudenken, da es eine ganze Klasse von umgebungsspezifischen Bugs verhindert. Wir gehen auf Profile und Konfigurationsparameter in Kapitel 5 ein.

Mit dem sogenannten »Actuator«-Modul bringt Spring Boot auch ein Modul mit, das uns Einblicke in eine laufende Anwendung gibt1. Sobald wir das Modul als Dependency hinzufügen, werden diverse Metriken wie freier/belegter Hauptspeicher und freie/belegte Rechenkapazität über den Endpoint/actuator zugänglich gemacht. Von dort können diese Metriken dann zum Beispiel von Observability-Tools abgefragt und in Dashboards verfügbar gemacht werden, um immer einen Blick auf die Produktionsumgebung zu haben. Wir schauen uns das Thema Observability und Actuator im Detail in Kapitel 29 an.

Dies war nur ein Ausschnitt der Features, die Spring Boot mitbringt. Spring Boot bietet eine Fülle von anderen Features, die uns die Entwicklung und den Betrieb einer Anwendung erleichtern und die wir im Rest dieses Buches beschreiben.

5Konfiguration

Serveranwendungen besitzen oft eine Vielzahl an Konfigurationsparametern, die wir in eine Konfigurationsdatei oder in Umgebungsvariablen auslagern möchten. In diesem Kapitel schauen wir uns an, wie wir eine solche externalisierte Konfiguration mit Spring Boot umsetzen können.

5.1Warum Konfiguration?

Serveranwendungen sind häufig auf externe Services angewiesen. Sie müssen beispielsweise eine Verbindung zu einer Datenbank, einem Cache oder einem Messaging-System aufbauen oder sie sprechen zur Laufzeit mit einem anderen (Micro-)Service. Damit diese Verbindungen erfolgreich aufgebaut werden können, muss die Anwendung wissen, wie sie diese externen Services erreichen kann. Üblicherweise muss hierfür die URL des externen Service hinterlegt werden und ein Passwort oder Token, um sich gegenüber diesem Service zu authentifizieren.