Intro
Vous êtes confrontés à un problème ? Votre application SpringBoot est longue à démarrer ?
La plupart des systèmes d’informations que nous connaissons ont besoin d’être rapides et réactifs. Nous allons voir ensemble une technique qui pourrait vous aider à améliorer cet aspect sur vos jar, avec notamment l’utilisation du jarmode
tools au travers du plugin spring-boot-maven-plugin
(désolé pour les fans de gradle)
Qu’est-ce que le jarmode ?
À proprement parler, il s’agit d’un paramètre de lancement de java, applicable aux jar SpringBoot et plus particulièrement ceux qui utilisent le spring-boot-maven-plugin
. Il permet d’utiliser un principe Spring appelé “Layers”.
Les Layers dans Spring
Si vous êtes curieux, il vous est peut-être déjà arrivé d’ouvrir le contenu d’un .jar
SpringBoot. Historiquement ils étaient découpés de cette façon :
- Les classes de lancement du jar
- Les dépendances de votre projet (BOOT-INF/lib)
- Le code de votre projet (BOOT-INF/classes)
Ce découpage est propre à Spring, ils avaient donc la main pour le faire évoluer et y ajouter des layers. Spring propose les 4 layers suivantes par défaut :
dependencies
(pour les dépendances releasées)snapshot-dependencies
(pour les dépendances non releasées)spring-boot-loader
(pour le lancement du projet)application
(pour votre code et vos ressources)
Un fichier layers.idx
indique la répartition des fichiers du jar sous ces layers, voici un exemple d’un fichier :
- "dependencies":
- BOOT-INF/lib/library1.jar
- BOOT-INF/lib/library2.jar
- "spring-boot-loader":
- org/springframework/boot/loader/launch/JarLauncher.class
- ... <other classes>
- "snapshot-dependencies":
- BOOT-INF/lib/library3-SNAPSHOT.jar
- "application":
- META-INF/MANIFEST.MF
- BOOT-INF/classes/a/b/C.class
Ce nouveau découpage est intéressant et exploitable via le paramètre jarmode
évoqué plus tôt. Nous allons voir comment.
Prérequis de votre projet
Assurez-vous d’utiliser spring-boot-maven-plugin
pour builder votre projet, vous allez y ajouter le goal repackage
qui va vous permettre de recréer votre jar au cours du build. La configuration du plugin dans votre pom.xml
doit ressembler à ça :
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
Buildez votre projet (prenez un café aussi, selon les cas c’est jouable). Vous obtenez un magnifique .jar
comme vous avez l’habitude de voir, et au lancement rien ne change… Pas de panique, ce n’est que la première étape !
Utilisation du jarmode
Avec de simples lignes de commandes, nous allons extraire le layer application pour ne conserver que notre code au lancement, avec un référencement vers nos dépendances au besoin uniquement
$ java -Djarmode=tools -jar my-app.jar extract --destination application
Cette commande créera un dossier application
à l’endroit où vous l’avez lancé. Ce répertoire contiendra un sous-répertoire lib
et votre my-app.jar
allégé. En effet, lib
contient toutes les dépendances de votre projet.
Vous lancerez donc votre application avec la ligne suivante :
$ java -jar application/my-app.jar
Nous avons atteint l’objectif ! Votre application devrait se lancer beaucoup plus rapidement maintenant, bien sûr ce n’est pas la seule optimisation possible, mais c’est une piste qui m’a personnellement permis de passer d’une application qui se lançait en 2 minutes (oui) contre une dizaine de secondes aujourd’hui.
Pour aller plus loin…
Une idée à explorer, serait de réutiliser certaines dépendances communes à votre SI en les extrayant de cette manière. Il est possible de customiser les layers de spring.
Il est également possible d’intégrer cette utilisation du jarmode
dans des images docker pour qu’elles soient plus optimisées, voici un exemple de Dockerfile
qui utilise cette technique :
# Perform the extraction in a separate builder container
FROM bellsoft/liberica-openjre-debian:17-cds AS builder
WORKDIR /builder
# This points to the built jar file in the target folder
# Adjust this to 'build/libs/*.jar' if you're using Gradle
ARG JAR_FILE=target/*.jar
# Copy the jar file to the working directory and rename it to application.jar
COPY ${JAR_FILE} application.jar
# Extract the jar file using an efficient layout
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
# This is the runtime container
FROM bellsoft/liberica-openjre-debian:17-cds
WORKDIR /application
# Copy the extracted jar contents from the builder container into the working directory in the runtime container
# Every copy step creates a new docker layer
# This allows docker to only pull the changes it really needs
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
# Start the application jar - this is not the uber jar used by the builder
# This jar only contains application code and references to the extracted jar files
# This layout is efficient to start up and CDS friendly
ENTRYPOINT ["java", "-jar", "application.jar"]
On pourrait tout à fait imaginer un conteneur dédié à la construction des librairies communes à votre SI, qui sera chargé par les images de vos webservices de la même manière que dans l’exemple…