Lansarea unei aplicații Spring Boot în mod programatic

Publicat: 2022-03-11

Acest articol va demonstra cum să porniți o aplicație Spring Boot dintr-un alt program Java. O aplicație Spring Boot este de obicei construită într-o singură arhivă JAR executabilă. Conține toate dependențele în interior, ambalate ca JAR imbricate.

De asemenea, un proiect Spring Boot este de obicei construit ca un fișier JAR executabil de un plugin Maven furnizat care face toată munca murdară. Rezultatul este un fișier JAR unic, convenabil, care este ușor de partajat cu alții, de implementat pe un server și așa mai departe.

Pornirea unei aplicații Spring Boot este la fel de ușor ca și tastarea java -jar mySpringProg.jar , iar aplicația va tipări pe consolă câteva mesaje informative frumos formatate.

Dar dacă un dezvoltator Spring Boot dorește să ruleze o aplicație dintr-un alt program Java, fără intervenția umană?

Cum funcționează JAR-urile imbricate

Pentru a împacheta un program Java cu toate dependențele într-un singur fișier JAR rulabil, dependențele care sunt și fișiere JAR trebuie furnizate și stocate cumva în fișierul JAR rulabil final.

„Umbrirea” este o opțiune. Umbrirea dependențelor este procesul de includere și redenumire a dependențelor, relocarea claselor și rescrierea codului de octet și a resurselor afectate pentru a crea o copie care este inclusă împreună cu codul propriu al unei aplicații (proiect).

Umbrirea permite utilizatorilor să despacheteze toate clasele și resursele din dependențe și să le împacheteze înapoi într-un fișier JAR rulabil. Acest lucru ar putea funcționa pentru scenarii simple, totuși, dacă două dependențe conțin același fișier de resurse sau aceeași clasă cu exact același nume și cale, ele se vor suprapune și este posibil ca programul să nu funcționeze.

Spring Boot adoptă o abordare diferită și împachetează JAR de dependență în JAR rulabil, ca JAR imbricate.

 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

O arhivă JAR este organizată ca un fișier JAR standard rulabil de Java. Clasele de încărcare Spring Boot sunt situate la org/springframework/boot/loader , în timp ce clasele de utilizator și dependențele sunt la BOOT-INF/classes și BOOT-INF/lib .

Notă: dacă sunteți nou în Spring, vă recomandăm să aruncați o privire și la articolul nostru Top 10 Cele mai frecvente greșeli din Spring Framework .

Un fișier Spring Boot JAR tipic conține trei tipuri de intrări:

  • Clasele de proiect
  • Biblioteci JAR imbricate
  • Clasele de încărcare Spring Boot

Spring Boot Classloader va seta mai întâi biblioteci JAR în classpath și apoi clase de proiect, ceea ce face o mică diferență între rularea unei aplicații Spring Boot din IDE (Eclipse, IntelliJ) și din consolă.

Pentru informații suplimentare despre suprascrierile de clasă și încărcătorul de clasă, puteți consulta acest articol.

Lansarea aplicațiilor Spring Boot

Lansarea manuală a unei aplicații Spring Boot din linia de comandă sau din shell este ușoară ca și tastând următoarele:

 java -jar example.jar

Cu toate acestea, pornirea unei aplicații Spring Boot în mod programatic dintr-un alt program Java necesită mai mult efort. Este necesar să încărcați codul org/springframework/boot/loader/*.class , să folosiți un pic de reflecție Java pentru a instanția JarFileArchive , JarLauncher și invocați metoda launch(String[]) .

Vom arunca o privire mai detaliată asupra modului în care se realizează acest lucru în secțiunile următoare.

Se încarcă clasele Spring Boot Loader

După cum am subliniat deja, un fișier JAR Spring Boot este la fel ca orice arhivă JAR. Este posibil să încărcați org/springframework/boot/loader/*.class , să creați obiecte Class și să le utilizați pentru a lansa aplicațiile Spring Boot mai târziu.

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

Aici putem vedea classMap va deține obiectele Class mapate la numele lor de pachete respective, de exemplu, valoarea String org.springframework.boot.loader.JarLauncher va fi mapată la obiectul 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();

Rezultatul final al buclei while este o hartă populată cu obiecte de clasă Spring Boot loader.

Automatizarea lansării efective

Cu încărcarea oprită, putem continua să finalizăm lansarea automată și să o folosim pentru a începe efectiv aplicația noastră.

Reflecția Java permite crearea de obiecte din clasele încărcate, ceea ce este destul de util în contextul tutorialului nostru.

Primul pas este să creați un obiect 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));

Constructorul obiectului JarFileArchive ia ca argument un obiect File(String) , deci trebuie furnizat.

Următorul pas este să creați un obiect JarLauncher , care necesită Archive în constructorul său.

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

Pentru a evita confuzia, rețineți că Archive este de fapt o interfață, în timp ce JarFileArchive este una dintre implementări.

Ultimul pas al procesului este apelarea metodei launch(String[]) pe obiectul nostru jarLauncher nou creat. Acest lucru este relativ simplu și necesită doar câteva linii de cod.

 // 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]}) va porni în sfârșit aplicația Spring Boot. Rețineți că firul principal se va opri și aștepta aici ca aplicația Spring Boot să se termine.

Un cuvânt despre Spring Boot Classloader

Examinând fișierul nostru Spring Boot JAR va dezvălui următoarea structură:

 +--- 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 \--- (...)

Rețineți cele trei tipuri de intrări:

  • Clasele de proiect
  • Biblioteci JAR imbricate
  • Clasele de încărcare Spring Boot

Ambele clase de proiect ( BOOT-INF/classes ) și JAR imbricate ( BOOT-INF/lib ) sunt gestionate de același încărcător de clasă LaunchedURLClassLoader . Acest încărcător se află în rădăcina aplicației Spring Boot JAR.

LaunchedURLClassLoader va încărca conținutul clasei ( BOOT-INF/classes ) după conținutul bibliotecii ( BOOT-INF/lib ), care este diferit de IDE. De exemplu, Eclipse va plasa mai întâi conținutul clasei în classpath și apoi bibliotecile (dependențe).

LaunchedURLClassLoader extinde java.net.URLClassLoader , care este creat cu un set de adrese URL care vor fi folosite pentru încărcarea claselor. Adresa URL poate indica o locație, cum ar fi o arhivă JAR sau un folder de clase. La efectuarea încărcării clasei, toate resursele specificate de URL-uri vor fi parcurse în ordinea în care au fost furnizate URL-urile și va fi folosită prima resursă care conține clasa căutată.

Încheierea

O aplicație Java clasică necesită enumerarea tuturor dependențelor în argumentul classpath, făcând procedura de pornire oarecum greoaie și complicată.

În schimb, aplicațiile Spring Boot sunt la îndemână și ușor de pornit din linia de comandă. Ei gestionează toate dependențele, iar utilizatorul final nu trebuie să-și facă griji cu privire la detalii.

Cu toate acestea, pornirea unei aplicații Spring Boot dintr-un alt program Java face procedura mai complicată, deoarece necesită încărcarea claselor de încărcare Spring Boot, crearea de obiecte specializate precum JarFileArchive și JarLauncher și apoi utilizarea reflectării Java pentru a invoca metoda de launch .

Concluzie : Spring Boot se poate ocupa de o mulțime de sarcini ușoare sub capotă, permițând dezvoltatorilor să elibereze timp și să se concentreze pe lucrări mai utile, cum ar fi crearea de noi funcții, testare și așa mai departe.