JAR minces avec botte à ressort

1. Introduction

Dans ce didacticiel, nous allons voir comment créer un projet Spring Boot dans un fichier JAR fin, à l'aide du projet spring-boot-thin-launcher .

Spring Boot est connu pour ses déploiements JAR «lourds», où un seul artefact exécutable contient à la fois le code de l'application et toutes ses dépendances.

Le démarrage est également largement utilisé pour développer des microservices. Cela peut parfois être en contradiction avec l'approche du «gros JAR», car inclure les mêmes dépendances à plusieurs reprises dans de nombreux artefacts peut devenir un important gaspillage de ressources.

2. Prérequis

Tout d'abord, nous avons besoin d'un projet Spring Boot, bien sûr. Dans cet article, nous examinerons les versions Maven et Gradle dans leurs configurations les plus courantes.

Il est impossible de couvrir tous les systèmes de construction et de créer des configurations, mais j'espère que nous verrons suffisamment de principes généraux pour que vous puissiez les appliquer à votre configuration spécifique.

2.1. Projets Maven

Dans un projet Boot construit avec Maven, nous devrions avoir le plugin Spring Boot Maven configuré dans le fichier pom.xml de notre projet , son parent ou l'un de ses ancêtres:

 org.springframework.boot spring-boot-maven-plugin 

La version des dépendances Spring Boot est généralement décidée en utilisant une nomenclature ou en héritant d'un POM parent comme dans notre projet de référence:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE  

2.2. Projets Gradle

Dans un projet Boot construit avec Gradle, nous aurons le plugin Boot Gradle:

buildscript { ext { springBootPlugin = 'org.springframework.boot:spring-boot-gradle-plugin' springBootVersion = '2.2.2.RELEASE' } repositories { mavenCentral() } dependencies { classpath("${springBootPlugin}:${springBootVersion}") } } // elided apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' springBoot { mainClassName = 'com.baeldung.DemoApplication' }

Notez que, dans cet article, nous ne considérerons que les projets Boot 2.x et ultérieurs. Le Thin Launcher prend également en charge les versions antérieures, mais il nécessite une configuration Gradle légèrement différente que nous omettons pour plus de simplicité. Veuillez consulter la page d'accueil du projet pour plus de détails.

3. Comment créer un JAR fin?

Le Spring Boot Thin Launcher est une petite bibliothèque qui lit les dépendances d'un artefact à partir d'un fichier intégré dans l'archive elle-même, les télécharge à partir d'un référentiel Maven et lance enfin la classe principale de l'application.

Ainsi, lorsque nous construisons un projet avec la bibliothèque, nous obtenons un fichier JAR avec notre code, un fichier énumérant ses dépendances et la classe principale de la bibliothèque qui effectue les tâches ci-dessus.

Bien sûr, les choses sont un peu plus nuancées que notre explication simplifiée; nous aborderons certains sujets en profondeur plus loin dans l'article.

4. Utilisation de base

Voyons maintenant comment créer un JAR «fin» à partir de notre application Spring Boot standard.

Nous allons lancer l'application avec le java -jar habituel , avec des arguments de ligne de commande supplémentaires facultatifs qui contrôlent le Thin Launcher. Nous en verrons quelques-uns dans les sections suivantes; la page d'accueil du projet contient la liste complète.

4.1. Projets Maven

Dans un projet Maven, nous devons modifier la déclaration du plugin Boot (voir section 2.1) pour inclure une dépendance sur la mise en page «fine» personnalisée:

 org.springframework.boot spring-boot-maven-plugin    org.springframework.boot.experimental spring-boot-thin-layout 1.0.11.RELEASE   

Le lanceur lira les dépendances du fichier pom.xml que Maven stocke dans le JAR généré dans le répertoire META-INF / maven .

Nous effectuerons la construction comme d'habitude, par exemple avec mvn install .

Si nous voulons être en mesure de produire à la fois des versions minces et épaisses (par exemple dans un projet avec plusieurs modules), nous pouvons déclarer la disposition personnalisée dans un profil Maven dédié.

4.2. Maven et dépendances: thin.properties

Nous pouvons également demander à Maven de générer un fichier thin.properties en plus de pom.xml . Dans ce cas, le fichier contiendra la liste complète des dépendances, y compris les dépendances transitives, et le lanceur la préférera au pom.xml .

Le mojo (plugin) pour cela est spring-boot-thin-maven-plugin: properties, et par défaut, il génère le fichier thin.properties dans src / main / resources / META-INF , mais nous pouvons spécifier son emplacement avec la propriété thin.output :

$ mvn org.springframework.boot.experimental:spring-boot-thin-maven-plugin:properties -Dthin.output=.

Veuillez noter que le répertoire de sortie doit exister pour que l'objectif réussisse, même si nous avons conservé celui par défaut.

4.3. Projets Gradle

Dans un projet Gradle, à la place, nous ajoutons un plugin dédié:

buildscript { ext { //... thinPlugin = 'org.springframework.boot.experimental:spring-boot-thin-gradle-plugin' thinVersion = '1.0.11.RELEASE' } //... dependencies { //... classpath("${thinPlugin}:${thinVersion}") } } //elided apply plugin: 'maven' apply plugin: 'org.springframework.boot.experimental.thin-launcher'

Pour obtenir une version fine, nous dirons à Gradle d'exécuter la tâche thinJar :

~/projects/baeldung/spring-boot-gradle $ ./gradlew thinJar

4.4. Gradle et dépendances: pom.xml

Dans l'exemple de code de la section précédente, nous avons déclaré le plugin Maven en plus du Thin Launcher (ainsi que les plugins Boot and Dependency Management que nous avions déjà vus dans la section Prerequisites).

C'est parce que, tout comme dans le cas Maven que nous avons vu précédemment, l'artefact contiendra et utilisera un fichier pom.xml énumérant les dépendances de l'application. Le fichier pom.xml est généré par une tâche appelée thinPom , qui est une dépendance implicite de toute tâche jar.

Nous pouvons personnaliser le fichier pom.xml généré avec une tâche dédiée. Ici, nous allons simplement reproduire ce que le plugin léger fait déjà automatiquement:

task createPom { def basePath = 'build/resources/main/META-INF/maven' doLast { pom { withXml(dependencyManagement.pomConfigurer) }.writeTo("${basePath}/${project.group}/${project.name}/pom.xml") } }

To use our custom pom.xml file, we add the above task to the jar task's dependencies:

bootJar.dependsOn = [createPom]

4.5. Gradle and Dependencies: thin.properties

We can also have Gradle generate a thin.properties file rather than pom.xml, as we did earlier with Maven.

The task that generates the thin.properties file is called thinProperties, and it's not used by default. We can add it as a dependency of the jar task:

bootJar.dependsOn = [thinProperties]

5. Storing Dependencies

The whole point of thin jars is to avoid bundling the dependencies with the application. However, dependencies don't magically disappear, they're simply stored elsewhere.

In particular, the Thin Launcher uses the Maven infrastructure to resolve dependencies, so:

  1. it checks the local Maven repository, which by default lies in ~/.m2/repository but can be moved elsewhere;
  2. then, it downloads missing dependencies from Maven Central (or any other configured repository);
  3. finally, it caches them in the local repository, so that it won't have to download them again the next time we run the application.

Of course, the download phase is the slow and error-prone part of the process, because it requires access to Maven Central through the Internet, or access to a local proxy, and we all know how those things are generally unreliable.

Fortunately, there are various ways of deploying the dependencies together with the application(s), for example in a prepackaged container for cloud deployment.

5.1. Running the Application for Warm-up

The simplest way to cache the dependencies is to do a warm-up run of the application in the target environment. As we've seen earlier, this will cause the dependencies to be downloaded and cached in the local Maven repository. If we run more than one app, the repository will end up containing all the dependencies without duplicates.

Since running an application can have unwanted side effects, we can also perform a “dry run” that only resolves and downloads the dependencies without running any user code:

$ java -Dthin.dryrun=true -jar my-app-1.0.jar

Note that, as per Spring Boot conventions, we can set the -Dthin.dryrun property also with a –thin.dryrun command line argument to the application or with a THIN_DRYRUN system property. Any value except false will instruct the Thin Launcher to perform a dry run.

5.2. Packaging the Dependencies During the Build

Another option is to collect the dependencies during the build, without bundling them in the JAR. Then, we can copy them to the target environment as part of the deployment procedure.

This is generally simpler because it's not necessary to run the application in the target environment. However, if we're deploying multiple applications, we'll have to merge their dependencies, either manually or with a script.

The format in which the Thin Plugin for Maven and Gradle packages the dependencies during a build is the same as a Maven local repository:

root/ repository/ com/ net/ org/ ...

In fact, we can point an application using the Thin Launcher to any such directory (including a local Maven repository) at runtime with the thin.root property:

$ java -jar my-app-1.0.jar --thin.root=my-app/deps

We can also safely merge multiple such directories by copying them one over another, thus obtaining a Maven repository with all the necessary dependencies.

5.3. Packaging the Dependencies With Maven

To have Maven package the dependencies for us, we use the resolve goal of the spring-boot-thin-maven-plugin. We can invoke it manually or automatically in our pom.xml:

 org.springframework.boot.experimental spring-boot-thin-maven-plugin ${thin.version}    resolve  resolve  false   

After building the project, we'll find a directory target/thin/root/ with the structure that we've discussed in the previous section.

5.4. Packaging the Dependencies With Gradle

If we're using Gradle with the thin-launcher plugin, instead, we have a thinResolve task available. The task will save the application and its dependencies in the build/thin/root/ directory, similarly to the Maven plugin of the previous section:

$ gradlew thinResolve

Please note that, at the time of writing, the thin-launcher plugin has a bug that prevents the dependencies to be saved if thin.properties is used: //github.com/dsyer/spring-boot-thin-launcher/issues/53.

6. Conclusions and Further Reading

Dans cet article, nous avons examiné comment fabriquer notre pot fin. Nous avons également vu comment utiliser l'infrastructure Maven pour télécharger et stocker leurs dépendances.

La page d'accueil du lanceur léger contient quelques autres guides pratiques pour des scénarios tels que les déploiements cloud sur Heroku, ainsi que la liste complète des arguments de ligne de commande pris en charge.

L'implémentation de tous les exemples et extraits de code Maven se trouve dans le projet GitHub - en tant que projet Maven, il devrait donc être facile à importer et à exécuter tel quel.

De même, tous les exemples Gradle font référence à ce projet GitHub.