Avvio di un'applicazione Spring Boot in modo programmatico

Pubblicato: 2022-03-11

Questo articolo mostrerà come avviare un'applicazione Spring Boot da un altro programma Java. Un'applicazione Spring Boot è in genere incorporata in un singolo archivio JAR eseguibile. Contiene tutte le dipendenze all'interno, impacchettate come JAR nidificati.

Allo stesso modo, un progetto Spring Boot viene solitamente creato come file JAR eseguibile da un plug-in Maven fornito che fa tutto il lavoro sporco. Il risultato è un comodo file JAR singolo che è facile da condividere con altri, distribuire su un server e così via.

Avviare un'applicazione Spring Boot è facile come digitare java -jar mySpringProg.jar e l'applicazione stamperà sulla console alcuni messaggi informativi ben formattati.

Ma cosa succede se uno sviluppatore Spring Boot vuole eseguire un'applicazione da un altro programma Java, senza l'intervento umano?

Come funzionano i JAR nidificati

Per comprimere un programma Java con tutte le dipendenze in un unico file JAR eseguibile, le dipendenze che sono anche file JAR devono essere fornite e in qualche modo memorizzate all'interno del file JAR eseguibile finale.

"Ombreggiatura" è un'opzione. L'ombreggiatura delle dipendenze è il processo di inclusione e ridenominazione delle dipendenze, riposizionamento delle classi e riscrittura del bytecode e delle risorse interessati per creare una copia in bundle insieme al codice (progetto) di un'applicazione.

L'ombreggiatura consente agli utenti di decomprimere tutte le classi e le risorse dalle dipendenze e di ricomprimerle in un file JAR eseguibile. Questo potrebbe funzionare per scenari semplici, tuttavia, se due dipendenze contengono lo stesso file di risorse o classe con lo stesso nome e percorso, si sovrapporranno e il programma potrebbe non funzionare.

Spring Boot adotta un approccio diverso e comprime i JAR di dipendenza all'interno di JAR eseguibili, come JAR nidificati.

 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

Un archivio JAR è organizzato come un file JAR standard eseguibile da Java. Le classi del caricatore Spring Boot si trovano in org/springframework/boot/loader path, mentre le classi utente e le dipendenze si trovano in BOOT-INF/classes e BOOT-INF/lib .

Nota: se non conosci la primavera, puoi anche dare un'occhiata al nostro articolo I 10 errori più comuni del framework di primavera .

Un tipico file JAR Spring Boot contiene tre tipi di voci:

  • Classi di progetto
  • Librerie JAR nidificate
  • Classi di caricatori Spring Boot

Spring Boot Classloader imposterà prima le librerie JAR nel percorso di classe e quindi le classi del progetto, il che fa una leggera differenza tra l'esecuzione di un'applicazione Spring Boot dall'IDE (Eclipse, IntelliJ) e dalla console.

Per ulteriori informazioni sulle sostituzioni delle classi e sul caricatore di classi, puoi consultare questo articolo.

Avvio di applicazioni Spring Boot

Avviare manualmente un'applicazione Spring Boot dalla riga di comando o dalla shell è facile come digitare quanto segue:

 java -jar example.jar

Tuttavia, l'avvio di un'applicazione Spring Boot a livello di codice da un altro programma Java richiede uno sforzo maggiore. È necessario caricare il org/springframework/boot/loader/*.class , utilizzare un po' di riflessione Java per creare un'istanza di JarFileArchive , JarLauncher e invocare il metodo launch(String[]) .

Daremo uno sguardo più dettagliato a come ciò viene realizzato nelle sezioni seguenti.

Caricamento delle classi Spring Boot Loader

Come abbiamo già sottolineato, un file JAR Spring Boot è proprio come qualsiasi archivio JAR. È possibile caricare org/springframework/boot/loader/*.class , creare oggetti Class e usarli per avviare le applicazioni Spring Boot in un secondo momento.

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

Qui possiamo vedere che classMap conterrà gli oggetti Class mappati ai rispettivi nomi di pacchetto, ad esempio, il valore String org.springframework.boot.loader.JarLauncher sarà mappato all'oggetto 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();

Il risultato finale del ciclo while è una mappa popolata con oggetti di classe del caricatore Spring Boot.

Automatizzare il lancio effettivo

Terminato il caricamento, possiamo procedere a finalizzare l'avvio automatico e utilizzarlo per avviare effettivamente la nostra app.

La riflessione Java consente la creazione di oggetti da classi caricate, il che è abbastanza utile nel contesto del nostro tutorial.

Il primo passaggio consiste nel creare un oggetto 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));

Il costruttore dell'oggetto JarFileArchive accetta un oggetto File(String) come argomento, quindi deve essere fornito.

Il passaggio successivo consiste nel creare un oggetto JarLauncher , che richiede Archive nel suo costruttore.

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

Per evitare confusione, tieni presente che Archive è in realtà un'interfaccia, mentre JarFileArchive è una delle implementazioni.

L'ultimo passaggio del processo consiste nel chiamare il metodo launch(String[]) sul nostro oggetto jarLauncher appena creato. Questo è relativamente semplice e richiede solo poche righe di codice.

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

Il invoke(jarLauncer, new Object[]{new String[0]}) avvierà finalmente l'applicazione Spring Boot. Si noti che il thread principale si fermerà e attenderà qui che l'applicazione Spring Boot termini.

Una parola sul Classloader Spring Boot

L'esame del nostro file JAR Spring Boot rivelerà la seguente struttura:

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

Nota i tre tipi di voci:

  • Classi di progetto
  • Librerie JAR nidificate
  • Classi di caricatori Spring Boot

Entrambe le classi di progetto ( BOOT-INF/classes ) e JAR nidificati ( BOOT-INF/lib ) sono gestite dallo stesso caricatore di classi LaunchedURLClassLoader . Questo caricatore risiede nella radice dell'applicazione Spring Boot JAR.

LaunchedURLClassLoader caricherà il contenuto della classe ( BOOT-INF/classes ) dopo il contenuto della libreria ( BOOT-INF/lib ), che è diverso dall'IDE. Ad esempio, Eclipse inserirà prima il contenuto della classe nel percorso di classe e poi le librerie (dipendenze).

LaunchedURLClassLoader estende java.net.URLClassLoader , che viene creato con una serie di URL che verranno utilizzati per il caricamento della classe. L'URL potrebbe puntare a una posizione come un archivio JAR o una cartella classi. Quando si esegue il caricamento della classe, tutte le risorse specificate dagli URL verranno attraversate nell'ordine in cui sono stati forniti gli URL e verrà utilizzata la prima risorsa contenente la classe cercata.

Avvolgendo

Una classica applicazione Java richiede che tutte le dipendenze siano enumerate nell'argomento classpath, rendendo la procedura di avvio alquanto macchinosa e complicata.

Al contrario, le applicazioni Spring Boot sono pratiche e facili da avviare dalla riga di comando. Gestiscono tutte le dipendenze e l'utente finale non deve preoccuparsi dei dettagli.

Tuttavia, l'avvio di un'applicazione Spring Boot da un altro programma Java rende la procedura più complicata, poiché richiede il caricamento delle classi del caricatore di Spring Boot, la creazione di oggetti specializzati come JarFileArchive e JarLauncher e quindi l'utilizzo della riflessione Java per invocare il metodo di launch .

In conclusione : Spring Boot può occuparsi di molte attività umili sotto il cofano, consentendo agli sviluppatori di liberare tempo e concentrarsi su lavori più utili come la creazione di nuove funzionalità, test e così via.