Développement

Améliorez vos performances avec le jarmode SpringBoot

Accélérez le démarrage de vos applications SpringBoot grâce au jarmode et à l’utilisation des layers avec le spring-boot-maven-plugin. Découvrez dans cet article comment optimiser vos jars, réduire le temps de lancement et gagner en performance, y compris en environnement Docker.

Par

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 :

  1. dependencies (pour les dépendances releasées)
  2. snapshot-dependencies (pour les dépendances non releasées)
  3. spring-boot-loader (pour le lancement du projet)
  4. 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…

Les autres articles à explorer