Programowe uruchamianie aplikacji Spring Boot

Opublikowany: 2022-03-11

Ten artykuł pokaże, jak uruchomić aplikację Spring Boot z innego programu Java. Aplikacja Spring Boot jest zwykle wbudowana w pojedyncze wykonywalne archiwum JAR. Zawiera wszystkie zależności wewnątrz, spakowane jako zagnieżdżone pliki JAR.

Podobnie projekt Spring Boot jest zwykle budowany jako plik wykonywalny JAR przez dostarczoną wtyczkę maven, która wykonuje całą brudną robotę. Rezultatem jest wygodny, pojedynczy plik JAR, który można łatwo udostępniać innym, wdrażać na serwerze i tak dalej.

Uruchomienie aplikacji Spring Boot jest tak proste, jak wpisanie java -jar mySpringProg.jar , a aplikacja wyświetli na konsoli ładnie sformatowane komunikaty informacyjne.

Ale co, jeśli programista Spring Boot chce uruchomić aplikację z innego programu Java bez interwencji człowieka?

Jak działają zagnieżdżone pliki JAR

Aby spakować program Java ze wszystkimi zależnościami w jeden uruchamialny plik JAR, należy dostarczyć zależności, które są również plikami JAR, i w jakiś sposób przechowywać je w końcowym uruchamialnym pliku JAR.

„Cieniowanie” to jedna z opcji. Zależności cieniowania to proces włączania i zmieniania nazw zależności, relokacji klas i ponownego zapisywania kodu bajtowego i zasobów, których dotyczy problem, w celu utworzenia kopii, która jest dołączona wraz z własnym kodem aplikacji (projektu).

Cieniowanie pozwala użytkownikom rozpakować wszystkie klasy i zasoby z zależności i spakować je z powrotem do uruchamialnego pliku JAR. Może to działać w przypadku prostych scenariuszy, jednak jeśli dwie zależności zawierają ten sam plik zasobów lub klasę o dokładnie tej samej nazwie i ścieżce, będą się nakładać i program może nie działać.

Spring Boot stosuje inne podejście i pakuje pliki JAR zależności w uruchamialnych plikach JAR jako zagnieżdżone pliki JAR.

 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

Archiwum JAR jest zorganizowane jako standardowy plik JAR uruchamiany w języku Java. Klasy programu ładującego Spring Boot znajdują się w org/springframework/boot/loader , podczas gdy klasy użytkownika i zależności znajdują się w BOOT-INF/classes i BOOT-INF/lib .

Uwaga: Jeśli jesteś nowy w Spring, możesz również zapoznać się z naszym artykułem 10 najczęstszych błędów Spring Framework .

Typowy plik JAR Spring Boot zawiera trzy typy wpisów:

  • Zajęcia projektowe
  • Zagnieżdżone biblioteki JAR
  • Klasy ładowaczy sprężynowych

Spring Boot Classloader najpierw ustawi biblioteki JAR w ścieżce klas, a następnie klasy projektowe, co powoduje niewielką różnicę między uruchamianiem aplikacji Spring Boot z IDE (Eclipse, IntelliJ) i z konsoli.

Aby uzyskać dodatkowe informacje na temat nadpisywania klas i modułu ładującego klasy, zapoznaj się z tym artykułem.

Uruchamianie aplikacji Spring Boot

Ręczne uruchamianie aplikacji Spring Boot z wiersza poleceń lub powłoki jest proste, wystarczy wpisać następujące polecenie:

 java -jar example.jar

Programowe uruchamianie aplikacji Spring Boot z innego programu Java wymaga jednak większego wysiłku. Konieczne jest załadowanie org/springframework/boot/loader/*.class , użycie odrobiny odbicia Java w celu utworzenia instancji JarFileArchive , JarLauncher i wywołanie metody launch(String[]) .

W kolejnych sekcjach przyjrzymy się dokładniej, jak to się robi.

Ładowanie klas ładowania sprężynowego

Jak już wspomnieliśmy, plik Spring Boot JAR jest jak każde archiwum JAR. Możliwe jest ładowanie org/springframework/boot/loader/*.class , tworzenie obiektów Class i używanie ich do późniejszego uruchamiania aplikacji Spring Boot.

 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);

Tutaj widzimy, że classMap będzie przechowywać obiekty Class zmapowane do odpowiednich nazw pakietów, np. wartość String org.springframework.boot.loader.JarLauncher zostanie zmapowana na obiekt 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();

Efektem końcowym pętli while jest mapa wypełniona obiektami klasy ładującej Spring Boot.

Automatyzacja rzeczywistego uruchomienia

Z pominięciem ładowania możemy przystąpić do sfinalizowania automatycznego uruchamiania i użyć go do faktycznego uruchomienia naszej aplikacji.

Odbicie w Javie umożliwia tworzenie obiektów z załadowanych klas, co jest całkiem przydatne w kontekście naszego samouczka.

Pierwszym krokiem jest utworzenie obiektu JarFileArchive .

 // 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));

Konstruktor obiektu JarFileArchive przyjmuje jako argument obiekt File(String) , więc musi być dostarczony.

Następnym krokiem jest utworzenie obiektu JarLauncher , który wymaga Archive w swoim konstruktorze.

 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);

Aby uniknąć nieporozumień, należy pamiętać, że Archive jest w rzeczywistości interfejsem, podczas gdy JarFileArchive jest jedną z implementacji.

Ostatnim krokiem w procesie jest wywołanie metody launch(String[]) na naszym nowo utworzonym obiekcie jarLauncher . Jest to stosunkowo proste i wymaga tylko kilku linijek kodu.

 // 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]});

Metoda invoke(jarLauncer, new Object[]{new String[0]}) w końcu uruchomi aplikację Spring Boot. Zauważ, że główny wątek zatrzyma się i poczeka na zakończenie działania aplikacji Spring Boot.

Słowo o Spring Boot Classloader

Analiza naszego pliku Spring Boot JAR ujawni następującą 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 \--- (...)

Zwróć uwagę na trzy rodzaje wpisów:

  • Zajęcia projektowe
  • Zagnieżdżone biblioteki JAR
  • Klasy ładowaczy sprężynowych

Obie klasy projektu ( BOOT-INF/classes ) i zagnieżdżone pliki JAR ( BOOT-INF/lib ) są obsługiwane przez ten sam program ładujący klasy LaunchedURLClassLoader . Ten program ładujący znajduje się w katalogu głównym aplikacji Spring Boot JAR.

LaunchedURLClassLoader załaduje zawartość klasy ( BOOT-INF/classes ) po zawartości biblioteki ( BOOT-INF/lib ), która różni się od IDE. Na przykład Eclipse najpierw umieści zawartość klasy w ścieżce klas, a następnie biblioteki (zależności).

LaunchedURLClassLoader rozszerza java.net.URLClassLoader , który jest tworzony z zestawem adresów URL, które będą używane do ładowania klas. Adres URL może wskazywać na lokalizację, taką jak archiwum JAR lub folder klas. Podczas ładowania klasy wszystkie zasoby określone przez adresy URL będą przeszukiwane w kolejności, w jakiej zostały podane adresy URL, i zostanie użyty pierwszy zasób zawierający przeszukiwaną klasę.

Zawijanie

Klasyczna aplikacja Java wymaga wyliczenia wszystkich zależności w argumencie classpath, co sprawia, że ​​procedura uruchamiania jest nieco uciążliwa i skomplikowana.

Natomiast aplikacje Spring Boot są poręczne i łatwe do uruchomienia z wiersza poleceń. Zarządzają wszystkimi zależnościami, a użytkownik końcowy nie musi martwić się o szczegóły.

Jednak uruchomienie aplikacji Spring Boot z innego programu Java sprawia, że ​​procedura jest bardziej skomplikowana, ponieważ wymaga załadowania klas ładujących Spring Boot, utworzenia wyspecjalizowanych obiektów, takich jak JarFileArchive i JarLauncher , a następnie użycia odbicia Java do wywołania metody launch .

Konkluzja : Spring Boot może zająć się wieloma drobnymi zadaniami pod maską, pozwalając programistom zwolnić czas i skupić się na bardziej przydatnej pracy, takiej jak tworzenie nowych funkcji, testowanie i tak dalej.