Programmgesteuertes Starten einer Spring Boot-Anwendung
Veröffentlicht: 2022-03-11In diesem Artikel wird gezeigt, wie Sie eine Spring Boot-Anwendung von einem anderen Java-Programm aus starten. Eine Spring Boot-Anwendung ist normalerweise in ein einzelnes ausführbares JAR-Archiv integriert. Es enthält alle Abhängigkeiten, verpackt als verschachtelte JARs.
Ebenso wird ein Spring Boot-Projekt normalerweise als ausführbare JAR-Datei von einem bereitgestellten Maven-Plugin erstellt, das die ganze Drecksarbeit erledigt. Das Ergebnis ist eine praktische, einzelne JAR-Datei, die einfach mit anderen geteilt, auf einem Server bereitgestellt und so weiter werden kann.
Das Starten einer Spring Boot-Anwendung ist so einfach wie das Eingeben von java -jar mySpringProg.jar , und die Anwendung druckt auf der Konsole einige schön formatierte Infomeldungen.
Was aber, wenn ein Spring Boot-Entwickler eine Anwendung ohne menschliches Eingreifen von einem anderen Java-Programm aus ausführen möchte?
Wie verschachtelte JARs funktionieren
Um ein Java-Programm mit allen Abhängigkeiten in eine einzige lauffähige JAR-Datei zu packen, müssen Abhängigkeiten, die auch JAR-Dateien sind, bereitgestellt und irgendwie in der endgültigen lauffähigen JAR-Datei gespeichert werden.
„Schatten“ ist eine Option. Das Schattieren von Abhängigkeiten ist der Prozess des Einschließens und Umbenennens von Abhängigkeiten, des Verschiebens der Klassen und des Neuschreibens des betroffenen Bytecodes und der betroffenen Ressourcen, um eine Kopie zu erstellen, die zusammen mit dem eigenen Code einer Anwendung (Projekt) gebündelt wird.
Shading ermöglicht es Benutzern, alle Klassen und Ressourcen aus Abhängigkeiten zu entpacken und sie wieder in eine lauffähige JAR-Datei zu packen. Dies kann für einfache Szenarien funktionieren, aber wenn zwei Abhängigkeiten dieselbe Ressourcendatei oder Klasse mit genau demselben Namen und Pfad enthalten, überschneiden sie sich und das Programm funktioniert möglicherweise nicht.
Spring Boot verfolgt einen anderen Ansatz und packt Abhängigkeits-JARs als verschachtelte JARs in ausführbare JARs.
example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-BOOT-INF +-classes | +-mycompany | +-project | +-YourClasses.class +-lib +-dependency1.jar +-dependency2.jar
Ein JAR-Archiv ist als standardmäßige Java-lauffähige JAR-Datei organisiert. Spring Boot Loader-Klassen befinden sich unter org/springframework/boot/loader
path, während sich Benutzerklassen und Abhängigkeiten unter BOOT-INF/classes
und BOOT-INF/lib
befinden.
Hinweis: Wenn Sie neu bei Spring sind, sollten Sie sich auch unseren Artikel Top 10 der häufigsten Spring-Framework-Fehler ansehen .
Eine typische Spring Boot JAR-Datei enthält drei Arten von Einträgen:
- Projektklassen
- Verschachtelte JAR-Bibliotheken
- Spring Bootloader-Klassen
Spring Boot Classloader legt zuerst JAR-Bibliotheken im Klassenpfad fest und projiziert dann Klassen, was einen kleinen Unterschied zwischen dem Ausführen einer Spring Boot-Anwendung von der IDE (Eclipse, IntelliJ) und von der Konsole aus macht.
Weitere Informationen zu Klassenüberschreibungen und dem Classloader finden Sie in diesem Artikel.
Starten von Spring Boot-Anwendungen
Das manuelle Starten einer Spring Boot-Anwendung über die Befehlszeile oder Shell ist einfach, da Sie Folgendes eingeben:
java -jar example.jar
Das programmgesteuerte Starten einer Spring Boot-Anwendung aus einem anderen Java-Programm erfordert jedoch mehr Aufwand. Es ist notwendig, den Code org/springframework/boot/loader/*.class
zu laden, ein bisschen Java-Reflektion zu verwenden, um JarFileArchive
, JarLauncher
zu instanziieren und die Methode launch(String[])
aufzurufen.
Wie dies bewerkstelligt wird, werden wir uns in den folgenden Abschnitten genauer ansehen.
Laden von Spring Boot Loader-Klassen
Wie bereits erwähnt, ist eine Spring Boot JAR-Datei genau wie jedes JAR-Archiv. Es ist möglich, org/springframework/boot/loader/*.class
Einträge zu laden, Klassenobjekte zu erstellen und sie später zum Starten von Spring Boot-Anwendungen zu verwenden.
import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; . . . public static void loadJar(final String pathToJar) throws IOException . . . { // Class name to Class object mapping. final Map<String, Class<?>> classMap = new HashMap<>(); final JarFile jarFile = new JarFile(pathToJar); final Enumeration<JarEntry> jarEntryEnum = jarFile.entries(); final URL[] urls = { new URL("jar:file:" + pathToJar + "!/") }; final URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);
Hier können wir sehen, dass classMap
, die ihren jeweiligen Paketnamen zugeordnet sind, z. B. wird der String-Wert org.springframework.boot.loader.JarLauncher
dem Objekt JarLauncher.class
.
while (jarEntryEnum.hasMoreElements()) { final JarEntry jarEntry = jarEntryEnum.nextElement(); if (jarEntry.getName().startsWith("org/springframework/boot") && jarEntry.getName().endsWith(".class") == true) { int endIndex = jarEntryName.lastIndexOf(".class"); className = jarEntryName.substring(0, endIndex).replace('/', '.'); try { final Class<?> loadedClass = urlClassLoader.loadClass(className); result.put(loadedClass.getName(), loadedClass); } catch (final ClassNotFoundException ex) { } } } jarFile.close();
Das Endergebnis der While-Schleife ist eine Map, die mit Spring Boot Loader-Klassenobjekten gefüllt ist.

Automatisierung des tatsächlichen Starts
Nachdem das Laden aus dem Weg geräumt ist, können wir mit dem Abschluss des automatischen Starts fortfahren und ihn verwenden, um unsere App tatsächlich zu starten.
Java Reflection ermöglicht die Erstellung von Objekten aus geladenen Klassen, was im Kontext unseres Tutorials sehr nützlich ist.
Der erste Schritt besteht darin, ein JarFileArchive
Objekt zu erstellen.
// Create JarFileArchive(File) object, needed for JarLauncher. final Class<?> jarFileArchiveClass = result.get("org.springframework.boot.loader.archive.JarFileArchive"); final Constructor<?> jarFileArchiveConstructor = jarFileArchiveClass.getConstructor(File.class); final Object jarFileArchive = jarFileArchiveConstructor.newInstance(new File(pathToJar));
Der Konstruktor des JarFileArchive
Objekts nimmt ein File(String)
-Objekt als Argument, also muss es bereitgestellt werden.
Der nächste Schritt besteht darin, ein JarLauncher
Objekt zu erstellen, das Archive
in seinem Konstruktor benötigt.
final Class<?> archiveClass = result.get("org.springframework.boot.loader.archive.Archive"); // Create JarLauncher object using JarLauncher(Archive) constructor. final Constructor<?> jarLauncherConstructor = mainClass.getDeclaredConstructor(archiveClass); jarLauncherConstructor.setAccessible(true); final Object jarLauncher = jarLauncherConstructor.newInstance(jarFileArchive);
Um Verwirrung zu vermeiden, beachten Sie bitte, dass Archive
eigentlich eine Schnittstelle ist, während JarFileArchive
eine der Implementierungen ist.
Der letzte Schritt in diesem Prozess besteht darin, die launch(String[])
Methode für unser neu erstelltes jarLauncher
Objekt aufzurufen. Dies ist relativ einfach und erfordert nur wenige Codezeilen.
// Invoke JarLauncher#launch(String[]) method. final Class<?> launcherClass = result.get("org.springframework.boot.loader.Launcher"); final Method launchMethod = launcherClass.getDeclaredMethod("launch", String[].class); launchMethod.setAccessible(true); launchMethod.invoke(jarLauncher, new Object[]{new String[0]});
Die invoke(jarLauncer, new Object[]{new String[0]})
startet schließlich die Spring Boot-Anwendung. Beachten Sie, dass der Haupt-Thread anhält und hier darauf wartet, dass die Spring Boot-Anwendung beendet wird.
Ein Wort zum Spring Boot Classloader
Die Untersuchung unserer Spring Boot JAR-Datei zeigt die folgende Struktur:
+--- mySpringApp1-0.0.1-SNAPSHOT.jar +--- META-INF +--- BOOT-INF | +--- classes # 1 - project classes | | | | | +--- com.example.mySpringApp1 | | \--- SpringBootLoaderApplication.class | | | +--- lib # 2 - nested jar libraries | +--- javax.annotation-api-1.3.1 | +--- spring-boot-2.0.0.M7.jar | \--- (...) | +--- org.springframework.boot.loader # 3 - Spring Boot loader classes +--- JarLauncher.class +--- LaunchedURLClassLoader.class \--- (...)
Beachten Sie die drei Arten von Einträgen:
- Projektklassen
- Verschachtelte JAR-Bibliotheken
- Spring Bootloader-Klassen
Sowohl Projektklassen ( BOOT-INF/classes
) als auch verschachtelte JARs ( BOOT-INF/lib
) werden vom selben Klassenlader LaunchedURLClassLoader
verarbeitet. Dieser Loader befindet sich im Stammverzeichnis der Spring Boot JAR-Anwendung.
Der LaunchedURLClassLoader
lädt den Klasseninhalt ( BOOT-INF/classes
) nach dem Bibliotheksinhalt ( BOOT-INF/lib
), der sich von der IDE unterscheidet. Beispielsweise platziert Eclipse zuerst Klasseninhalte im Klassenpfad und dann Bibliotheken (Abhängigkeiten).
LaunchedURLClassLoader
erweitert java.net.URLClassLoader
, das mit einer Reihe von URLs erstellt wird, die zum Laden von Klassen verwendet werden. Die URL kann auf einen Speicherort wie ein JAR-Archiv oder einen Klassenordner verweisen. Beim Laden von Klassen werden alle durch URLs angegebenen Ressourcen in der Reihenfolge durchlaufen, in der die URLs bereitgestellt wurden, und die erste Ressource, die die gesuchte Klasse enthält, wird verwendet.
Einpacken
Bei einer klassischen Java-Anwendung müssen alle Abhängigkeiten im Klassenpfadargument aufgeführt werden, was die Startprozedur etwas umständlich und kompliziert macht.
Im Gegensatz dazu sind Spring Boot-Anwendungen praktisch und einfach von der Befehlszeile aus zu starten. Sie verwalten alle Abhängigkeiten, und der Endbenutzer muss sich nicht um die Details kümmern.
Das Starten einer Spring Boot-Anwendung aus einem anderen Java-Programm macht das Verfahren jedoch komplizierter, da es das Laden der Ladeklassen von Spring Boot, das Erstellen spezialisierter Objekte wie JarFileArchive
und JarLauncher
und das anschließende Verwenden der Java-Reflexion zum Aufrufen der launch
erfordert.
Fazit : Spring Boot kann sich um viele einfache Aufgaben unter der Haube kümmern, sodass Entwickler Zeit gewinnen und sich auf nützlichere Arbeiten wie das Erstellen neuer Funktionen, Tests usw. konzentrieren können.