CodeGym /Kurse /Spring Boot /Eingebetteter Tomcat in Spring Boot

Eingebetteter Tomcat in Spring Boot

Spring Boot
Level 5, Lektion 3
Verfügbar

1. Die Rolle des Servers in Spring Boot

WebApplicationType hat uns das Bild bereits eingegrenzt: catalog-service lebt im SERVLET-Zweig. Das macht die Frage ganz konkret: Wer öffnet in diesem Modus wirklich den Port und nimmt HTTP-Anfragen entgegen?

Wenn du bisher hauptsächlich Konsolen-Java-Anwendungen geschrieben hast, klingt das Wort „Server" nach etwas aus der Welt von Admins, Rechenzentren und Leuten, die manchmal mit Hardware reden und traurig dabei sind. Aber in Spring Boot ist der Server keine „fremde Infrastruktur irgendwo da draußen", sondern ein Teil deines Prozesses. Und genau das ändert die Denkweise: Die Anwendung hört auf, ein „Programm, das etwas berechnet" zu sein, und wird zu einem „Prozess, der auf Anfragen lauscht und antwortet".

Wenn du catalog-service im SERVLET-Modus startest, hat die Anwendung eine Pflicht: HTTP-Anfragen entgegenzunehmen. Und um sie entgegenzunehmen, muss ein Port geöffnet und Netzwerkverbindungen müssen verarbeitet werden. Hier kommt der Webserver ins Spiel — genauer gesagt, der Servlet-Container. Boot löst das „professionell": Es zwingt dich nicht, einen externen Server manuell zu konfigurieren, sondern bringt einen eingebetteten Server als Bibliothek mit und startet ihn zusammen mit deiner Anwendung.

Zwei Java-Web-Modelle: extern und embedded

In der Java-Welt gab es historisch ein ziemlich klares — aber für Einsteiger nicht sehr freundliches — Modell: Du schreibst eine Webanwendung, packst sie in ein Artefakt, oft WAR, und deployest es dann in einen bereits installierten Anwendungsserver oder Container. Du hast getrennt „den Server" (Tomcat/WebSphere/was auch immer) und getrennt deinen Code, den der Server irgendwo in sich ausführt. Das ähnelt der Situation „Ich schreibe ein Theaterstück, aber es wird in einem fremden Theater nach fremden Regeln aufgeführt".

Spring Boot ändert dieses Bild. Im modernen Boot-Ansatz wird die Anwendung selbst zum „Theater": Du startest einen einzigen JVM-Prozess, in dem sowohl dein Code als auch der Server leben, der den Port lauscht. Das ist einfacher für die Entwicklung, einfacher für lokale Ausführung, einfacher für die Reproduzierbarkeit der Umgebung und ehrlicher im Sinne von „was du startest, das testest du auch".

Um nicht bei Metaphern zu bleiben, halten wir den Unterschied in einer sehr pragmatischen Tabelle fest:

Frage Externer Container (klassisch) Embedded Server (Boot-Ansatz)
Wer startet den Server? Du separat / über ein Skript Deine Anwendung beim Start
Wo lebt der Server? Separate Installation / separater Prozess Im selben JVM-Prozess
Was startest du lokal? Server + Deploy des Artefakts main() / java -jar (konzeptuell)
Woher kommt Tomcat? Separat installiert Kommt als Abhängigkeit (über Starter)
Was ist einfacher einem Einsteiger zu erklären? „Installiere zuerst den Server, dann deploye" „Starte die Anwendung — sie lauscht auf einem Port"

Der Punkt ist nicht, dass „das Klassische schlecht ist". Wichtig ist etwas anderes: Spring Boot geht genau vom Embedded-Modell aus, bei dem die Anwendung ihren eigenen Server hochfährt. Für die meisten kleinen und mittleren Services ist das der normale Arbeitsstandard.

2. Embedded Tomcat in einem Prozess

Die Worte „embedded server" klingen so, als ob sich in deinem Projekt ein Mini-Server in einer Schachtel versteckt hätte und du ihn „einbettest". In der Praxis ist alles prosaischer und, erfreulicherweise, erklärbarer. Embedded Server ist ein Satz von Bibliotheken, Jar-Dateien, die in deinen Classpath gelangen und einen HTTP-Listener hochfahren können. Und Spring Boot kann diese Bibliotheken finden, die nötigen Objekte erstellen und sie zum richtigen Zeitpunkt starten.

Der entscheidende mentale Umbruch hier: Beim Embedded-Ansatz ist der Server kein Ort, in den du deine Anwendung „ladest". Der Server ist Teil der Anwendung. Du hast nicht zwei selbstständige Welten, die manuell miteinander verbunden werden müssen, sondern eine Welt: Den JVM-Prozess, in dem Boot die Infrastruktur und deinen Code zusammenbaut.

Man kann den Start — sehr vereinfacht — als folgenden Ablauf vorstellen:

flowchart TD
    A["main()"] --> B["SpringApplication.run(...)"]
    B --> C["Boot bestimmt WebApplicationType = SERVLET"]
    C --> D["Servlet-Web ApplicationContext wird erstellt"]
    D --> E["refresh() des Kontexts + Auto-Konfiguration"]
    E --> F["Embedded Tomcat wird erstellt"]
    F --> G["Tomcat öffnet Port und beginnt zu lauschen"]
    G --> H["Anwendung ist bereit, HTTP-Anfragen entgegenzunehmen"]

Die Reihenfolge ist hier entscheidend. Zuerst erkennt Boot, dass es sich um eine SERVLET-Anwendung handelt, dann wählt es den Servlet-Web-Kontext, und erst innerhalb von dessen refresh() und Auto-Konfiguration wird der Server hochgefahren. Das heißt, Tomcat ist nicht die Ursache des Web-Modus, sondern eine Folge des bereits gewählten Start-Zweigs.

Das sind keine „Framework-Interna", die man auswendig lernen muss. Das ist eine Landkarte, damit du verstehst, warum nach dem Start einer Boot-Anwendung plötzlich ein Port erscheint und in den Logs Zeilen über Tomcat auftauchen.

3. Woher kommt Tomcat: Starter und Abhängigkeiten

Die ehrlichste Antwort auf die Frage „Woher kam Tomcat?" klingt etwas langweilig, rettet aber hervorragend vor Mythen: Tomcat kam aus den Abhängigkeiten. Nicht aus der Luft, nicht aus „Spring-Magie", sondern aus build.gradle.kts. Und hier kommt uns alles zugute, was wir bereits besprochen haben: Starter und transitive Abhängigkeiten.

Wenn das Projekt spring-boot-starter-webmvc enthält, hast du nicht einfach „MVC angebunden". Du hast einen abgestimmten Satz von Bibliotheken für eine Servlet-Webanwendung angebunden, zu denen standardmäßig ein eingebetteter Tomcat gehört.

Mini-Fragment von build.gradle.kts — die Idee, nicht die vollständige Datei — sieht so aus:

dependencies {
    // Servlet-Web-Stack anbinden: zusammen damit kommt embedded Tomcat (transitiv)
    implementation("org.springframework.boot:spring-boot-starter-webmvc")

    // Test-Starter: grundlegende Abhängigkeiten für Tests (JUnit, Assert-Bibliotheken usw.)
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Für Einsteiger ist es wichtig, eine Sache zu bemerken: Du hast nicht tomcat-embed-core manuell hinzugefügt. Und das ist normal. Der Starter zieht Abhängigkeiten transitiv mit, und das Boot-BOM fixiert kompatible Versionen. Deshalb bekommst du einen „funktionierenden Server-Build" ohne manuelles Versionsmanagement.

Wenn du nur den Basis-Starter ohne Web-Teil angebunden hättest, wäre das Bild anders:

dependencies {
    // Basis Spring Boot ohne Web-Stack: der Kontext fährt hoch, aber der Server wird keinen Port öffnen
    implementation("org.springframework.boot:spring-boot-starter")
}

In so einem Projekt bleibt Boot Boot, der Kontext bleibt Kontext, IoC/DI bleibt IoC/DI — aber es gibt niemanden, der den Port lauscht, weil kein Web-Stack vorhanden ist.

Um die „Zusammensetzung" des Web-Starters zu verstehen, ist es nützlich, die Namen einiger Bibliotheken zu kennen, die im Dependency-Tree üblicherweise sichtbar sind — in vereinfachter Form:

spring-boot-starter-webmvc
\--- spring-boot-starter-tomcat
     \--- tomcat-embed-core
     \--- tomcat-embed-websocket

Diese Artefakte muss man jetzt nicht auswendig lernen. Wichtig ist etwas anderes: Tomcat erscheint im Projekt genau deshalb, weil du den Web-Starter angebunden hast, und nicht weil Boot „Tomcat liebt und es allen aufzwingt".

4. Start und Verwaltung von Tomcat in Boot

Eine gute Einsteigerfrage: „Okay, Tomcat kam als Abhängigkeit. Aber wo ist der Code, der ihn startet? Warum sehe ich kein new Tomcat()?" Und hier hilft wieder die bereits aufgebaute Start-Kette: SpringApplication wählt zuerst WebApplicationType.SERVLET, erstellt daraufhin einen weborientierten Kontext, und dann, während dessen refresh() und Auto-Konfiguration, wird der Server hochgefahren.

Und hier stoßen wir wieder auf die fundamentale Idee von Spring: Infrastruktur-Objekte erstellt der Container, nicht du manuell.

Im Servlet-Modus fährt Boot einen speziellen Kontexttyp hoch, der mit dem Webserver umgehen kann. Auf diesem Level ist nicht das Verständnis der Klassennamen wichtig, sondern die Verantwortlichkeit: Boot erstellt eine „Server-Factory", erstellt den Server, startet ihn und verbindet ihn dann mit der Web-Schicht von Spring — zum Beispiel mit dem DispatcherServlet. Das passiert innerhalb jener großen „Start-Kette", die wir in der Vorlesung über SpringApplication.run(...) besprochen haben.

Das heißt, dein Programm startet nicht nach dem Schema „Ich habe den Server erstellt, dann habe ich Spring hinzugefügt". Es startet nach dem Schema „Spring Boot hat den Kontext zusammengebaut, den Web-Stack gesehen, den Server als Infrastruktur hochgefahren und die Anwendung anfragenbereit gemacht".

Und das ist übrigens eine sehr praktische Verantwortungsaufteilung: Dein Code bleibt anwendungsbezogen — der Kurskatalog —, und der Server bleibt Infrastruktur — wie Arbeitsspeicher, Netzwerk oder Dateien. Wenn du anfängst, new Tomcat() im Anwendungscode zu schreiben, sagst du damit: „Ich bin der Chefadmin dieser Welt". Gewöhnlich endet das damit, dass die Welt erwidert und an unerwarteten Stellen zu brechen beginnt.

5. Mini-Beweis: Server innerhalb der Anwendung

Jetzt kommt ein kleiner Trick „zum Verständnis", nicht als „so muss man immer schreiben". Wir wissen, dass SpringApplication.run(...) einen ApplicationContext zurückgibt. Im Servlet-Modus ist dieser Kontext weborientiert und enthält Informationen über den hochgefahrenen Server.

Wir können — nur zur Demo — in demselben CatalogServiceApplication, direkt nach dem normalen run(...), den Port ausgeben, auf dem der Server tatsächlich gestartet hat:

var ctx = SpringApplication.run(CatalogServiceApplication.class, args);

// Im Servlet-Modus ist der Kontext ein ServletWebServerApplicationContext,
// in dem man auf den hochgefahrenen Webserver zugreifen kann
var webCtx = (ServletWebServerApplicationContext) ctx;

// Den tatsächlichen Port holen, auf dem der embedded Server lauscht (normalerweise standardmäßig 8080)
System.out.println("Embedded server port = " + webCtx.getWebServer().getPort());
// Embedded server port = 8080

Beachte die Bedeutung: Wir „gehen nicht zu Tomcat als zu einer separaten Welt". Wir nehmen den Kontext, den Boot zusammengebaut hat, und erfahren daraus die Parameter des Servers. Das ist ein guter mentaler Anker: Der eingebettete Server ist keine externe Installation, sondern ein Teil des Runtimes, das Boot verwaltet.

Ja, in echtem Code wirst du normalerweise keine solchen println in main() schreiben. Aber als Weg, das Modell zu verstehen — funktioniert das ausgezeichnet.

6. Anzeichen und Besonderheiten des Server-Starts

Wie man den Tomcat-Start in den Logs sieht

Auch wenn du noch nicht alle Start-Logs vollständig lesen kannst — und das ist normal, niemand muss Logs beim ersten Blick lieben —, sind die Zeilen über Tomcat normalerweise sehr wiedererkennbar. Sie erfüllen die Rolle eines Indikators: Der Webserver hat wirklich gestartet und begonnen, auf einem Port zu lauschen.

Beispiel für typische Zeilen — vereinfacht, das Format kann leicht abweichen:

Tomcat initialized with port 8080 (http)
Starting service [Tomcat]
Starting Servlet engine: [Apache Tomcat/10.x]
Tomcat started on port 8080 (http) with context path ''

In dieser Vorlesung sind zwei Ideen wichtig. Erste: Der Port „erschien nicht von selbst", das ist das Ergebnis davon, dass der eingebettete Tomcat wirklich gestartet wurde. Zweite: Diese Zeilen erscheinen nur, wenn die Anwendung im Servlet-Modus startet und der Web-Stack wirklich in den Abhängigkeiten vorhanden ist.

Es ist nützlich, jetzt schon festzuhalten, was die Zeile über Tomcat bedeutet: Das ist eines der einfachsten Anzeichen dafür, dass du wirklich das Web-Runtime gestartet hast und nicht nur ein Java-Programm, das intern etwas macht.

Web-Starter und Start ohne Controller

Es gibt eine Falle, in die Einsteiger tappen: Sie denken, der Server „erscheint", wenn sie den ersten @RestController geschrieben haben. In Wirklichkeit erscheint der Server früher — in dem Moment, in dem Boot WebApplicationType.SERVLET bestimmt und den eingebetteten Container hochgefahren hat.

Das heißt, auch wenn du noch keinen einzigen Controller hast, kann der Server starten, der Port öffnet sich, und du kannst die Anwendung mit dem Browser aufrufen. Höchstwahrscheinlich bekommst du einen 404 — was logisch ist: „Keine Routen vorhanden" —, aber die Tatsache, dass der Server lebt, verschwindet deshalb nicht. Das ist psychologisch wichtig: Die Infrastruktur einer Webanwendung ist keine „Folge des Vorhandenseins von Controller-Code", sondern eine „Folge des gewählten Runtime-Modus und Classpaths".

Und in die umgekehrte Richtung funktioniert das auch. Wenn man den Web-Starter deaktiviert oder explizit WebApplicationType.NONE setzt, wie wir in der vorherigen Vorlesung besprochen haben, gibt es keinen Tomcat, auch wenn du versehentlich irgendwo Web-Klassen hinterlassen hast. In Boot werden zuerst Modus und Infrastruktur bestimmt, und dann alles andere.

Tomcat als Standard und die Wahl des Containers

Bei Einsteigern entsteht manchmal ein Verdacht: „Wenn im Projekt Tomcat ist, bedeutet das Spring Boot = Tomcat?" Nein. Boot ist nicht gleich Tomcat — genauso wie Java nicht gleich IntelliJ IDEA ist, obwohl es manchmal scheint, dass Java ohne sie nicht startet.

Tomcat ist der standardmäßige eingebettete Servlet-Container für Web-Starter in den meisten typischen Konfigurationen. Der Standard wurde nicht gewählt, weil „die anderen schlecht sind", sondern weil Tomcat weit verbreitet, gut dokumentiert ist und das eingebettete Modell bei ihm schon lange ausgereift ist.

Auf Wunsch kann Tomcat durch Jetty oder Undertow ersetzt werden — es ist nützlich zu wissen, dass diese Tür existiert. Aber zuerst ist es besser, das Modell selbst zu verstehen: Der Server kommt als Abhängigkeit und wird von Boot verwaltet, nicht manuell als „coolster Container der Woche" ausgewählt.

Wenn du in die Zukunft denken möchtest, behalte einen einfachen Gedanken im Kopf: Das Embedded-Modell bleibt erhalten, und die konkrete Container-Implementierung kann sich ändern. Aber heute brauchen wir die grundlegende Verbindung: Der Web-Starter bringt den Servlet-Stack, und der bringt den eingebetteten Tomcat.

7. Typische Fehler beim Umgang mit dem Embedded Server

Fehler Nr. 1: Auf „Tomcat-Startcode" in main() warten und versuchen, ihn manuell zu schreiben.
Das ist eine normale Reaktion eines Menschen, der an new und explizite Verwaltung gewöhnt ist. Aber in Spring Boot wird gerade die Infrastruktur — Server, Konverter, Teile der Web-Schicht — vom Container hochgefahren. Wenn man anfängt, den Server manuell zu erstellen und zu starten, zerstört man die Idee der Auto-Konfiguration und bekommt zwei konkurrierende Welten: „Mein Tomcat" und „Tomcat, den Boot erwartet".

Fehler Nr. 2: Denken, dass der Server erst nach dem Erscheinen von Controller-Klassen erscheint.
Controller sind die Anwendungsschicht. Der Server ist die Infrastrukturschicht. Boot fährt den Server hoch, weil es den Servlet-Web-Stack im Classpath gesehen und WebApplicationType.SERVLET gewählt hat. Deshalb kann der Server auch in einer „leeren" Anwendung ohne Endpunkte starten, und das ist kein Bug, sondern eine korrekt aufgebaute Infrastruktur.

Fehler Nr. 3: Den Server-Start nicht mit Abhängigkeiten in Verbindung bringen.
Wenn jemand Port 8080 und Zeilen über Tomcat sieht, scheint es, als hätte „Boot das selbst entschieden". In Wirklichkeit ist das die Folge von spring-boot-starter-webmvc und transitiven Abhängigkeiten. Wenn man den Web-Starter entfernt, verschwindet der Server. Wenn man ihn hinzufügt — erscheint er. Genau so muss man über die Boot-Plattform nachdenken: Classpath und Starter beeinflussen das Runtime.

Fehler Nr. 4: Embedded Server mit „einem separaten Prozess auf dem Computer" verwechseln.
Manchmal startet ein Einsteiger die Anwendung, sieht „Tomcat started on port 8080" und beginnt zu suchen, „wo im Task-Manager der separate Tomcat ist". Den gibt es nicht. Das ist derselbe Java-Prozess. Deshalb stoppt das Stoppen der Anwendung in der IDE sowohl den Server als auch alles andere. Und umgekehrt: Wenn der Port belegt ist, ist das ein Problem deines Prozesses und seiner Umgebung, kein „irgendwo im System falsch installierter Tomcat".

Fehler Nr. 5: Tomcat versuchen fein zu konfigurieren, ohne das Grundmodell zu verstehen.
An diesem Punkt ist es sehr leicht, nach „max threads konfigurieren" zu googeln und in ein Kaninchenloch zu fallen. Aber solange du nicht sicher bist, dass du verstehst, woher Tomcat kam, wer ihn startet, warum er zusammen mit der Anwendung startet und was von den Startern abhängt, wird jedes Tuning zu Konfiguration einer Blackbox. Erst das Modell — dann die Schrauben.

1
Aufgabe
Spring Boot, Level 5, Lektion 3
Gesperrt
Wo Tomcat im Abhängigkeitsbaum erscheint
Wo Tomcat im Abhängigkeitsbaum erscheint
1
Aufgabe
Spring Boot, Level 5, Lektion 3
Gesperrt
Port des eingebetteten Servers aus dem Kontext
Port des eingebetteten Servers aus dem Kontext
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION