CodeGym /Kurse /Spring Boot /jarmode=tools: Jar-Ebenen

jarmode=tools: Jar-Ebenen

Spring Boot
Level 25, Lektion 2
Verfügbar

1. Spring Boot: Modus tools

Wenn Sie jemals versucht haben, eine jar manuell zu „betrachten“, kennen Sie dieses Gefühl: Von außen sieht sie wie eine einzelne Datei aus, innen steckt jede Menge Zeug, und zu verstehen, was wo liegt und wie alles zusammenhängt, ist ungefähr so verlockend wie ein fremdes Gradle-Build an einem Freitagabend. jarmode=tools wurde genau dafür erfunden, damit die jar keine Blackbox mehr ist, sondern ein beobachtbares Objekt.

Das Schema der Ebenen ist bereits vorhanden, aber das Schema allein ist wenig wert, solange man die jar nicht direkt danach fragen kann. Genau hier kommt der tools-Modus ins Spiel.

Die Idee ist einfach: Eine Spring Boot jar ist nicht nur ein Archiv mit Klassen. Sie enthält bereits einen „Launcher“ (spring-boot-loader), der die Struktur der jar, Indizes und Ebenen versteht. Daher kann Boot Ihnen ein kleines Werkzeug zur Verfügung stellen, das die jar als Boot-Artefakt liest und nicht als normale zip.

Wichtig ist, die Ebenen nicht zu verwechseln: Der tools-Modus hat nichts mit dem Betrieb Ihrer Anwendung zu tun, nichts mit ApplicationContext, nichts mit Web-Endpunkten. Es geht um die Verpackung des Artefakts. Und ja, es ist der Fall, dass „wir eine jar starten, damit sie uns sagt, was sie ist“.

Ein kleines Schema, um den Gedanken zu festigen:

flowchart TD
  A["bootJar -> catalog-service.jar"] --> B["java -jar ..."]
  A --> C["java -Djarmode=tools -jar ..."]
  B --> D["Anwendungsstart (Tomcat, Spring, Endpunkte)"]
  C --> E["Befehle: help / list-layers / extract"]

2. Dieselbe jar: Anwendungsmodus und Werkzeugmodus

Im Normalfall bedeutet java -jar „starte die Anwendung“. Eine Spring Boot jar funktioniert genau so: Sie startet den eingebetteten Server, erstellt den ApplicationContext, loggt den Startup, und Sie gehen im Browser auf http://localhost:8080. Doch Spring Boot fügt ein alternatives Szenario hinzu: „starte diese jar als Werkzeug“.

Technisch geschieht dies über die Java-Systemeigenschaft jarmode. Sie kennen bereits -Dsomething=value als Möglichkeit, der JVM eine Einstellung zu übergeben. Hier die gleiche Idee: Wir ändern die jar-Datei nicht, wir bauen das Projekt nicht neu, wir flüstern der JVM beim Start lediglich zu: „Bitte starte nicht die Anwendung; schalte in den Tooling-Modus“.

Der entscheidende Befehl lautet:

java -Djarmode=tools -jar build/libs/catalog-service.jar

Wenn Sie -Djarmode=tools vergessen, startet die jar brav die Anwendung. Wenn Sie -Djarmode=tools angeben, hört die jar auf, sich wie ein Dienst zu verhalten, und beginnt, sich wie ein Befehlszeilentool zu verhalten.

Man kann es sich so vorstellen: Dieselbe Datei kann sowohl „Wasserkocher“ als auch „Thermometer“ sein. Im Anwendungsmodus kocht sie Wasser, d. h. sie startet den Dienst. Im tools-Modus zeigt sie Ihnen die Temperatur, d. h. die Struktur der jar. Und ja, das ist der seltene Fall, in dem Multifunktionalität kein Übel, sondern eine Annehmlichkeit ist.

3. Vorbereiten von catalog-service.jar: Woher das richtige Artefakt nehmen

Bevor Sie die jar nach ihren Ebenen fragen, müssen Sie sicherstellen, dass Sie überhaupt die richtige jar betrachten. Im Projekt können verschiedene Artefakte liegen, darunter *-plain.jar, aber der tools-Modus ergibt nur Sinn für die Boot executable jar, die von der Aufgabe bootJar erstellt wurde.

Beginnen wir mit einer möglichst reproduzierbaren Sequenz: Build bereinigen, Jar bauen, Dateinamen ansehen, Pfad in einer Variable speichern – und erst dann starten.

./gradlew clean bootJar   # bereinigen und gezielt bootJar-Artefakt bauen
ls -lh build/libs         # prüfen, welche jars in build/libs vorhanden sind

Danach ist es praktisch, den Pfad in einer Umgebungsvariablen zu speichern, aber dabei genau die ausführbare jar zu nehmen, nicht *-plain.jar:

# Wir nehmen genau die executable jar aus bootJar, nicht *-plain.jar
JAR="$(find build/libs -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | head -n 1)"
echo "$JAR"               # Pfad ausgeben und prüfen, dass die Variable gefüllt ist
# build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Und eine kleine Sanity-Prüfung, ob die jar wirklich als Anwendung lebt – einfach damit das Gehirn nicht zweifelt:

java -jar "$JAR"          # als Anwendung starten (ohne tools-Modus)
# ... Spring Boot Startup-Logs ...
# Tomcat gestartet auf Port 8080 ...

Wenn die jar als Anwendung startet, haben Sie das richtige Artefakt, und der tools-Modus wird ebenfalls wie erwartet funktionieren.

4. help: Der schnellste Weg, um sicherzustellen, dass Sie im tools-Modus sind

Der tools-Modus ist großartig, weil er Ihnen sofort ein Befehlsmenü anzeigt. Und das ist super nützlich, denn bei Anfängern läuft das typische Szenario so ab: Sie starten java -Djarmode=tools -jar ..., sehen etwas Seltsames, denken „es ist kaputt“ und beginnen, etwas zu reparieren, das gar nicht kaputt ist. help ist Ihr Anker: Wenn help eine Liste der Befehle ausgibt, sind Sie definitiv im richtigen Modus.

Wir starten:

java -Djarmode=tools -jar "$JAR" help   # den tools-Modus bitten, die verfügbaren Befehle anzuzeigen

Die ungefähre Ausgabe sieht so aus, auch wenn das Format abweichen kann:

Verfügbare Befehle:
  help
  list-layers
  extract

Hier sind zwei Dinge wichtig. Erstens: Die jar hat tatsächlich als Dienstprogramm gearbeitet, nicht versucht, Spring zu starten. Zweitens: Die Befehle, die wir später verwenden werden, sind sofort sichtbar: list-layers und extract. Schon bei diesem Schritt kann man spüren, dass eine layered jar keine „abstrakte Theorie“ ist, sondern eine wirklich unterstützte Artefaktform.

Eine weitere häufige Frage: „Warum existiert der Befehl help überhaupt innerhalb der jar?“ Weil das Werkzeug, das die Schichten versteht, im spring-boot-loader-Teil des Artefakts lebt. Wir müssen nicht in seinen Quellcode eintauchen oder die Interna analysieren – es reicht zu wissen, dass es da ist und genau das tun kann, was für die Verpackung nötig ist.

5. list-layers: Schichten aus layers.idx auslesen

Wenn Sie „layers“ hören, besteht die Versuchung, sich etwas mit Docker und Infrastruktur vorzustellen. Für uns geht es aber erstmal nur um die jar-Datei: Welche logischen Gruppen sind darin definiert und wie heißen sie. Der Befehl list-layers ist eine Möglichkeit, layers.idx menschenlesbar zu machen, ohne manuelles Extrahieren und ohne „Ich habe die jar als zip geöffnet, und dann war alles wie im Nebel“.

Der Befehl lautet:

java -Djarmode=tools -jar "$JAR" list-layers   # die Namen der Schichten aus layers.idx ausgeben

Eine typische sinngemäße Ausgabe wäre:

dependencies
spring-boot-loader
snapshot-dependencies
application

Wenn Sie diese vier Schichten sehen – hervorragend. Das bedeutet, dass die jar tatsächlich stabile Abhängigkeiten, den Loader und die häufig wechselnde Anwendung trennt. Wenn Sie weniger Schichten sehen, ist das auch keine Katastrophe: Zum Beispiel kann snapshot-dependencies inhaltlich leer sein, wenn Sie keine SNAPSHOT-Abhängigkeiten haben. Aber die Schicht als Container bleibt normalerweise im Modell erhalten.

Um Schichten nicht mit Ordnern zu verwechseln, ist es hilfreich, eine kleine Tabelle im Kopf zu haben. Sie verbindet die Schicht mit dem Inhaltstyp und dem physischen Ort innerhalb der jar.

Schicht Bedeutung Wo in der jar Häufigkeit der Änderung
dependencies Release-Abhängigkeiten, Bibliotheken meist Teil von BOOT-INF/lib/*.jar selten
snapshot-dependencies SNAPSHOT-Abhängigkeiten auch BOOT-INF/lib/*.jar, aber separat ausgewiesen potenziell häufiger
spring-boot-loader Code zum Starten der executable jar, Loader Klassen des Loaders, nicht Ihr Code extrem selten
application Ihr Code und Ihre Ressourcen der Anwendung BOOT-INF/classes/** und Ressourcen häufig

Der entscheidende Effekt von list-layers ist, dass Sie aufhören zu raten. Vor diesem Befehl war eine layered jar für Sie eine Behauptung: „Boot kann irgendwas“. Danach – ist es eine konkrete Liste, die aus Ihrem Artefakt gewonnen wurde. Und genau deshalb ist diese Liste nicht um ihrer selbst willen wichtig: An ihr wird sichtbar, dass das Verpackungswerkzeug die stabilen Teile des Artefakts wiederverwenden kann, anstatt die jar wie ein einziges gesichtsloses Stück zu behandeln.

6. extract --layers: Jar in Schichtenverzeichnisse entpacken

Die Liste der Schichten ist nützlich, aber immer noch ein bisschen abstrakt. Wir möchten sehen, was wirklich in der application-Schicht liegt, wie dependencies als separate Einheit aussieht, welchen Umfang der loader hat. Der Befehl extract --layers macht die layered jar genau greifbar: Er legt den Inhalt der jar in Verzeichnisse pro Schicht ab.

Wir starten die Extraktion der Schichten in einen separaten Ordner, um die Dateien nicht im Arbeitsverzeichnis zu verteilen und ein lokales Mini-Chaos zu vermeiden:

rm -rf build/extracted    # alte Ergebnisse löschen, um Verwechslungen zu vermeiden
mkdir -p build/extracted  # Zielordner anlegen

java -Djarmode=tools -jar "$JAR" extract --layers --destination build/extracted  # nach Schichten extrahieren

Danach erwarten wir eine Struktur wie:

build/extracted/
|-- dependencies/
|-- spring-boot-loader/
|-- snapshot-dependencies/
`-- application/

Wenn Sie tree installiert haben, können Sie es schön anzeigen lassen. Wenn nicht – auch kein Beinbruch, ls tut es auch:

ls -1 build/extracted     # prüfen, dass die Schichten tatsächlich als Verzeichnisse erscheinen
# application
# dependencies
# snapshot-dependencies
# spring-boot-loader

Hier ist es wichtig, einen Gedanken zu formulieren, der vor traurigen Experimenten bewahrt. extract --layers baut das Projekt nicht neu und ändert Ihre jar nicht. Es legt lediglich das fertige Artefakt auf der Festplatte aus. Die Source of Truth bleibt build.gradle.kts, die Quellen und die Aufgabe bootJar. Die extrahierten Verzeichnisse sind eine Inspektion des Ergebnisses, keine neue Art, das Projekt zu speichern.

Wenn Sie eine Analogie möchten: Es ist, als würde man einen Koffer nach Organizern auspacken: Sie ändern nichts an den Sachen, aber sofort ist klar, wo die Socken sind, wo die T-Shirts und wo das Ladegerät, das immer verschwindet.

7. Schnellprüfung: Was in den Schichten liegt

Wenn die Schichten extrahiert sind, ist das Nützlichste, ein paar schnelle Sichtprüfungen zu machen, damit das Gehirn die Namen der Schichten mit den tatsächlichen Dateien verbindet. Es geht nicht um eine tiefgehende Diagnose, sondern um die Bildung einer Intuition: Wo suchen, falls etwas schiefgeht.

Beginnen wir mit der application-Schicht. Wir erwarten dort Ihren Code und Ihre Ressourcen. Zum Beispiel sollte die Hauptklasse CatalogServiceApplication irgendwo im BOOT-INF/classes-Baum liegen.

# Hauptklasse in der application-Schicht suchen (Name und Paket können abweichen)
find build/extracted/application -type f -name "*CatalogServiceApplication*"
# build/extracted/application/BOOT-INF/classes/com/example/catalogservice/CatalogServiceApplication.class

Als Nächstes prüfen wir die dependencies-Schicht. Dort erwarten wir logischerweise Jar-Dateien von Bibliotheken, Startern, Jackson, Logback usw. Tatsächlich liegt das normalerweise in BOOT-INF/lib.

# Einige der ersten Jar-Abhängigkeiten anzeigen, die in der Schicht dependencies gelandet sind
ls build/extracted/dependencies/BOOT-INF/lib | head -n 5
# spring-boot-4.0.3.jar
# spring-webmvc-7.0.5.jar
# jackson-databind-3.0.4.jar
# ... (Beispiel)

snapshot-dependencies kann leer oder fast leer sein, und das ist normal. Das ist kein Build-Fehler, sondern ein Abbild davon, dass das Projekt stabile Releases verwendet und keine SNAPSHOT-Artefakte.

# Versuchen, einige SNAPSHOT-Abhängigkeiten anzuzeigen (wenn es keine gibt, könnte keine Ausgabe erscheinen)
ls build/extracted/snapshot-dependencies/BOOT-INF/lib 2>/dev/null | head -n 5
# (könnte keine Ausgabe sein)

Schließlich ist spring-boot-loader der Ort, an dem die Loader-Klassen leben. Sie sind normalerweise unter org/springframework/boot/loader/... zu finden. Wir werden nicht tief in sie eintauchen, aber die bloße Existenz dieser Dateien ist nützlich: Sie ermöglichen der jar, sowohl als Anwendung als auch als Werkzeug zu starten.

# Prüfen, dass die Loader-Klassen tatsächlich extrahiert wurden
find build/extracted/spring-boot-loader -type f | head -n 5
# build/extracted/spring-boot-loader/org/springframework/boot/loader/...
# ...

Und noch ein sehr praktischer schneller Blick: die Größen der Schichten. Das hilft zu verstehen, was in Ihrem Dienst „schwer“ im Sinne der Verpackung ist.

du -sh build/extracted/*  # Größen der Schichten vergleichen (wer belegt mehr Platz)
#  38M  build/extracted/dependencies
#  20K  build/extracted/spring-boot-loader
#   0B  build/extracted/snapshot-dependencies
# 1.5M  build/extracted/application

Die Zahlen werden anders sein, aber die Verteilung ist oft ähnlich: Die Abhängigkeiten nehmen den meisten Platz ein, Ihr Code deutlich weniger. Und genau das ist der Grund, warum Schichten überhaupt existieren: Große, selten veränderte Teile möchte man wiederverwenden, kleine, häufig geänderte schnell austauschen.

8. Praktischer Nutzen des tools-Modus

In der Ingenieurspraxis treten Probleme meist nicht in dem Moment auf, in dem Sie Code schreiben, sondern in dem Moment, in dem dieser Code zu einem Artefakt wird und außerhalb der IDE zu leben beginnt. Für Anfänger wirkt das wie Magie: „In IntelliJ hat es funktioniert, aber beim Packen passiert etwas Seltsames.“ jarmode=tools ist eine der einfachsten Möglichkeiten, die Magie genau auf Artefaktebene zu entzaubern.

Stellen Sie sich ein Szenario vor: Sie haben eine layered jar aktiviert oder glauben, Sie hätten es, sind sich aber unsicher. list-layers gibt die Antwort in einer Sekunde, ohne Rätselraten. Oder ein anderes Szenario: Sie möchten sicherstellen, dass sich wirklich nur der application-Teil ändert, wenn Sie einen Controller bearbeiten. Sie bauen die jar neu, extrahieren die Schichten – und sehen, dass sich dependencies/ nicht geändert hat, wohl aber application/. Auch wenn Sie noch kein Container-Image bauen, diszipliniert Sie das bereits: Sie beginnen, das Artefakt als Produkt des Builds zu betrachten, nicht als Nebenprodukt der IDE.

Ein weiterer wichtiger Effekt ist die Diagnose „Was ist überhaupt in die jar gelangt“. Manchmal gibt es Probleme mit Ressourcen: application.yaml liegt nicht da, wo es soll, static/index.html fehlt, irgendeine Konfiguration ist nicht in BOOT-INF/classes gelandet. Statt zu raten, extrahieren Sie die application-Schicht und sehen die tatsächlichen Dateien. Das ist viel angenehmer, als mit einem Kollegen per Nachricht zu diskutieren: „Es funktioniert nicht, aber ich weiß nicht, was in der jar drin ist.“

Und schließlich hört die layered jar auf, ein dekoratives Feature zu sein. Das Verpackungswerkzeug muss nicht raten, wie Ihr Artefakt angeordnet werden soll: Es hat bereits eine Karte der Schichten. Und jarmode=tools ist der direkteste Weg, um sicherzustellen, dass diese Karte tatsächlich existiert und mit Ihrem eigenen Verständnis Ihres Dienstes übereinstimmt.

9. Typische Fehler bei der Arbeit mit jarmode=tools

Im tools-Modus scheinbar alles einfach: ein JVM-Befehl und ein paar Unterbefehle. Aber gerade wegen der Einfachheit tappen Anfänger oft in Fallen wie „falsch hingesehen, falsch gestartet, falsche Schlüsse gezogen“. Im Folgenden die häufigsten Hürden, auf die fast alle stoßen, auch Leute, die sicher Java schreiben.

Fehler Nr. 1: -Djarmode=tools vergessen und eine Befehlsliste erwarten.
Wenn Sie java -jar ... list-layers ausführen, versucht die Anwendung, als Dienst zu starten, und behandelt list-layers entweder als Anwendungsargument oder ignoriert es einfach. Infolgedessen sehen Sie die normalen Startup-Logs und denken, tools funktioniere nicht. Fast immer ist es nur die vergessene -D-Option.

Fehler Nr. 2: Den tools-Modus auf einem Nicht-bootJar-Artefakt ausführen.
Manchmal befinden sich in build/libs mehrere jar-Dateien, oder jemand versucht, den tools-Modus auf einer normalen, nicht mit dem Boot-Plugin erstellten jar zu starten. Der tools-Modus ist an die Struktur der Spring Boot executable jar gebunden, daher ist das richtige Artefakt dasjenige, das mit ./gradlew bootJar erstellt wurde.

Fehler Nr. 3: Die extrahierten Schichten für eine neue Arbeitsversion der Anwendung halten.
Nach extract --layers besteht die Versuchung, „irgendetwas in build/extracted/application zu korrigieren und zu testen“. Das führt zu seltsamen Effekten und Selbsttäuschung: Sie bearbeiten eine entpackte Kopie, aber die Source of Truth bleiben die Quellen und bootJar. Die Extraktion dient der Diagnose und Vorbereitung für die Verpackung, nicht als alternativer Build.

Fehler Nr. 4: Schicht und Verzeichnis verwechseln und falsche Schlüsse ziehen.
Zum Beispiel BOOT-INF/lib zu sehen und zu denken, das sei automatisch die Schicht dependencies. In Wirklichkeit ist BOOT-INF/lib ein physisches Verzeichnis in der jar, und die Schicht ist eine logische Gruppe. Der tools-Modus hilft genau dabei, diese Konzepte nicht zu verwechseln: list-layers zeigt die logischen Schichten, und extract --layers materialisiert sie in separaten Verzeichnissen.

Fehler Nr. 5: Erwarten, dass snapshot-dependencies immer gefüllt ist.
Wenn das Projekt keine SNAPSHOT-Abhängigkeiten verwendet, kann diese Schicht leer sein, und das ist ein völlig normales Ergebnis. Eine leere Schicht bedeutet keinen Fehler, sondern nur „In dieser Kategorie gibt es vorerst nichts zu speichern“.

1
Aufgabe
Spring Boot, Level 25, Lektion 2
Gesperrt
Schichtenliste über `jarmode=tools`
Schichtenliste über `jarmode=tools`
1
Aufgabe
Spring Boot, Level 25, Lektion 2
Gesperrt
Extrahieren von Layern in separate Verzeichnisse
Extrahieren von Layern in separate Verzeichnisse
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION