Lancer une application Spring Boot par programmation
Publié: 2022-03-11Cet article explique comment démarrer une application Spring Boot à partir d'un autre programme Java. Une application Spring Boot est généralement intégrée dans une seule archive JAR exécutable. Il contient toutes les dépendances à l'intérieur, emballées sous forme de fichiers JAR imbriqués.
De même, un projet Spring Boot est généralement construit sous la forme d'un fichier JAR exécutable par un plugin maven fourni qui fait tout le sale boulot. Le résultat est un fichier JAR unique et pratique, facile à partager avec d'autres, à déployer sur un serveur, etc.
Démarrer une application Spring Boot est aussi simple que de taper java -jar mySpringProg.jar , et l'application imprimera sur la console des messages d'information bien formatés.
Mais que se passe-t-il si un développeur Spring Boot souhaite exécuter une application à partir d'un autre programme Java, sans intervention humaine ?
Comment fonctionnent les fichiers JAR imbriqués
Pour regrouper un programme Java avec toutes les dépendances dans un seul fichier JAR exécutable, les dépendances qui sont également des fichiers JAR doivent être fournies et stockées d'une manière ou d'une autre dans le fichier JAR exécutable final.
« Ombrage » est une option. L'ombrage des dépendances est le processus d'inclusion et de renommage des dépendances, de déplacement des classes et de réécriture du bytecode et des ressources affectés afin de créer une copie qui est regroupée avec le propre code d'une application (projet).
L'ombrage permet aux utilisateurs de décompresser toutes les classes et ressources des dépendances et de les réintégrer dans un fichier JAR exécutable. Cela peut fonctionner pour des scénarios simples, cependant, si deux dépendances contiennent le même fichier de ressources ou la même classe avec exactement le même nom et le même chemin, elles se chevaucheront et le programme pourrait ne pas fonctionner.
Spring Boot adopte une approche différente et regroupe les JAR de dépendance dans un JAR exécutable, en tant que JAR imbriqués.
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
Une archive JAR est organisée comme un fichier JAR standard exécutable par Java. Les classes de chargeur Spring Boot sont situées dans org/springframework/boot/loader
, tandis que les classes d'utilisateurs et les dépendances se trouvent dans BOOT-INF/classes
et BOOT-INF/lib
.
Remarque : si vous débutez avec Spring, vous pouvez également consulter notre article Top 10 des erreurs de framework Spring les plus courantes .
Un fichier Spring Boot JAR typique contient trois types d'entrées :
- Cours de projet
- Bibliothèques JAR imbriquées
- Classes de chargeur de démarrage Spring
Spring Boot Classloader définira d'abord les bibliothèques JAR dans le chemin de classe, puis les classes de projet, ce qui fait une légère différence entre l'exécution d'une application Spring Boot à partir de l'IDE (Eclipse, IntelliJ) et de la console.
Pour plus d'informations sur les remplacements de classe et le chargeur de classe, vous pouvez consulter cet article.
Lancement des applications Spring Boot
Lancer une application Spring Boot manuellement à partir de la ligne de commande ou du shell est simple en tapant ce qui suit :
java -jar example.jar
Cependant, le démarrage d'une application Spring Boot par programme à partir d'un autre programme Java nécessite plus d'efforts. Il est nécessaire de charger le org/springframework/boot/loader/*.class
, d'utiliser un peu de réflexion Java pour instancier JarFileArchive
, JarLauncher
et d'appeler la méthode launch(String[])
.
Nous examinerons plus en détail comment cela est accompli dans les sections suivantes.
Chargement des classes Spring Boot Loader
Comme nous l'avons déjà souligné, un fichier Spring Boot JAR est comme n'importe quelle archive JAR. Il est possible de charger org/springframework/boot/loader/*.class
, de créer des objets Class et de les utiliser pour lancer ultérieurement des applications 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);
Ici, nous pouvons voir classMap
contiendra les objets Class mappés à leurs noms de package respectifs, par exemple, la valeur String org.springframework.boot.loader.JarLauncher
sera mappée à l'objet 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();
Le résultat final de la boucle while est une carte remplie d'objets de classe de chargeur Spring Boot.

Automatisation du lancement réel
Avec le chargement à l'écart, nous pouvons procéder à la finalisation du lancement automatique et l'utiliser pour démarrer réellement notre application.
La réflexion Java permet la création d'objets à partir de classes chargées, ce qui est assez utile dans le cadre de notre tutoriel.
La première étape consiste à créer un objet 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));
Le constructeur de l'objet JarFileArchive
prend un objet File(String)
comme argument, il doit donc être fourni.
L'étape suivante consiste à créer un objet JarLauncher
, qui nécessite Archive
dans son constructeur.
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);
Pour éviter toute confusion, veuillez noter que Archive
est en fait une interface, tandis que JarFileArchive
est l'une des implémentations.
La dernière étape du processus consiste à appeler la méthode launch(String[])
sur notre objet jarLauncher
nouvellement créé. C'est relativement simple et ne nécessite que quelques lignes de code.
// 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]});
La invoke(jarLauncer, new Object[]{new String[0]})
démarrera enfin l'application Spring Boot. Notez que le thread principal s'arrêtera et attendra ici que l'application Spring Boot se termine.
Un mot sur le chargeur de classe Spring Boot
L'examen de notre fichier Spring Boot JAR révélera la structure suivante :
+--- 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 \--- (...)
Notez les trois types d'entrées :
- Cours de projet
- Bibliothèques JAR imbriquées
- Classes de chargeur de démarrage Spring
Les classes de projet ( BOOT-INF/classes
) et les fichiers JAR imbriqués ( BOOT-INF/lib
) sont gérés par le même chargeur de classe LaunchedURLClassLoader
. Ce chargeur réside à la racine de l'application Spring Boot JAR.
Le LaunchedURLClassLoader
chargera le contenu de la classe ( BOOT-INF/classes
) après le contenu de la bibliothèque ( BOOT-INF/lib
), qui est différent de l'IDE. Par exemple, Eclipse placera d'abord le contenu de la classe dans le chemin de classe, puis les bibliothèques (dépendances).
LaunchedURLClassLoader
étend java.net.URLClassLoader
, qui est créé avec un ensemble d'URL qui seront utilisées pour le chargement de classe. L'URL peut pointer vers un emplacement tel qu'une archive JAR ou un dossier de classes. Lors du chargement de la classe, toutes les ressources spécifiées par les URL seront parcourues dans l'ordre dans lequel les URL ont été fournies, et la première ressource contenant la classe recherchée sera utilisée.
Emballer
Une application Java classique nécessite que toutes les dépendances soient énumérées dans l'argument classpath, ce qui rend la procédure de démarrage quelque peu lourde et compliquée.
En revanche, les applications Spring Boot sont pratiques et faciles à démarrer à partir de la ligne de commande. Ils gèrent toutes les dépendances et l'utilisateur final n'a pas à se soucier des détails.
Cependant, le démarrage d'une application Spring Boot à partir d'un autre programme Java rend la procédure plus compliquée, car elle nécessite le chargement des classes de chargeur de Spring Boot, la création d'objets spécialisés tels que JarFileArchive
et JarLauncher
, puis l'utilisation de la réflexion Java pour invoquer la méthode de launch
.
Conclusion : Spring Boot peut prendre en charge de nombreuses tâches subalternes sous le capot, permettant aux développeurs de libérer du temps et de se concentrer sur des travaux plus utiles tels que la création de nouvelles fonctionnalités, les tests, etc.