Exécuter une application Spring Boot avec Maven vs un exécutable War / Jar

1. Introduction

Dans ce didacticiel, nous explorerons les différences entre le démarrage d'une application Web Spring Boot via la commande mvn spring-boot: run et son exécution après sa compilation dans un package jar / war via la commande java -jar .

Supposons ici que vous soyez déjà familiarisé avec la configuration de l' objectif de reconditionnement de Spring Boot . Pour plus de détails sur ce sujet, veuillez lire Créer une application Fat Jar avec Spring Boot.

2. Le plugin Spring Boot Maven

Lors de l'écriture d'une application Spring Boot, le plugin Spring Boot Maven est l'outil recommandé pour créer, tester et empaqueter notre code.

Ce plugin est livré avec de nombreuses fonctionnalités pratiques, telles que:

  • il résout les versions de dépendance correctes pour nous
  • il peut regrouper toutes nos dépendances (y compris un serveur d'applications intégré si nécessaire) dans un seul fat jar / war exécutable et va également:
    • gérer pour nous la configuration du chemin de classe, afin que nous puissions ignorer cette option longue -cp dans notre commande java -jar
    • implémenter un ClassLoader personnalisé pour localiser et charger toutes les bibliothèques JAR externes, maintenant imbriquées dans le package
    • trouver automatiquement la méthode main () et la configurer dans le manifeste, donc nous n'avons pas à spécifier la classe principale dans notre commande java -jar

3. Exécution du code avec Maven sous forme éclatée

Lorsque nous travaillons sur une application Web, nous pouvons tirer parti d'une autre fonctionnalité très intéressante du plugin Spring Boot Maven: la possibilité de déployer automatiquement notre application Web dans un serveur d'applications embarqué.

Nous n'avons besoin que d'une seule dépendance pour faire savoir au plugin que nous voulons utiliser Tomcat pour exécuter notre code:

 org.springframework.boot spring-boot-starter-web 

Maintenant, lors de l'exécution de la commande mvn spring-boot: run dans le dossier racine de notre projet, le plugin lit la configuration de pom et comprend que nous avons besoin d'un conteneur d'application Web.

L'exécution de la commande mvn spring-boot: run déclenche le téléchargement d'Apache Tomcat et initialise le démarrage de Tomcat.

Essayons:

$ mvn spring-boot:run ... ... [INFO] ---------------------------------------- [INFO] Building spring-boot-ops 0.0.1-SNAPSHOT [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] >>> spring-boot-maven-plugin:2.1.3.RELEASE:run (default-cli) > test-compile @ spring-boot-ops >>> Downloading from central: //repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/9.0.16/tomcat-embed-core-9.0.16.pom Downloaded from central: //repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/9.0.16/tomcat-embed-core-9.0.16.pom (1.8 kB at 2.8 kB/s) ... ... [INFO] --- spring-boot-maven-plugin:2.1.3.RELEASE:run (default-cli) @ spring-boot-ops --- ... ... 11:33:36.648 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat] 11:33:36.649 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.16] ... ... 11:33:36.952 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext ... ... 11:33:48.223 [main] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"] 11:33:48.289 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path '' 11:33:48.292 [main] INFO org.baeldung.boot.Application - Started Application in 22.454 seconds (JVM running for 37.692)

Lorsque le journal affiche la ligne contenant 'Application démarrée', notre application Web est prête à être interrogée via le navigateur à l'adresse // localhost: 8080 /

4. Exécution du code en tant qu'application packagée autonome

Une fois que nous avons passé la phase de développement et que nous voulons progresser vers la mise en production de notre application, nous devons emballer notre application.

Malheureusement, si nous travaillons avec un package jar , l' objectif de base du package Maven n'inclut aucune des dépendances externes.

Cela signifie que nous ne pouvons l'utiliser que comme bibliothèque dans un projet plus important.

Pour contourner cette limitation, nous devons tirer parti de l' objectif de reconditionnement du plugin Maven Spring Boot pour exécuter notre jar / war en tant qu'application autonome.

4.1. Configuration

Habituellement, nous n'avons besoin que de configurer le plugin de construction:

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

Mais notre exemple de projet contient plus d'une classe principale, nous devons donc indiquer à Java quelle classe exécuter, soit en configurant le plugin:

 org.springframework.boot spring-boot-maven-plugin    com.baeldung.webjar.WebjarsdemoApplication    

ou en définissant la propriété start-class :

 com.baeldung.webjar.WebjarsdemoApplication 

4.2. Exécution de l'application

Maintenant, nous pouvons exécuter notre exemple de guerre avec deux commandes simples:

$ mvn clean package spring-boot:repackage $ java -jar target/spring-boot-ops.war

Vous trouverez plus de détails sur la façon d'exécuter un fichier JAR dans notre article Exécuter une application JAR avec des arguments de ligne de commande.

4.3. À l'intérieur du fichier de guerre

Pour mieux comprendre comment la commande mentionnée ci-dessus peut exécuter une application serveur complète, nous pouvons jeter un œil à notre spring-boot-ops.war .

Si nous le décompressons et jetons un coup d'œil à l'intérieur, nous trouvons les suspects habituels:

  • META-INF , avec le MANIFEST.MF auto-généré
  • WEB-INF / classes , contenant nos classes compilées
  • WEB-INF / lib , qui contient nos dépendances de guerre et les fichiers jar Tomcat intégrés

Ce n'est pas tout, car il existe des dossiers spécifiques à notre configuration de gros paquet:

  • WEB-INF / lib-provided , contenant les bibliothèques externes requises lors de l'exécution intégrée mais non requises lors du déploiement
  • org / springframework / boot / loader , qui contient le chargeur de classe personnalisé Spring Boot - cette bibliothèque est chargée de charger nos dépendances externes et de les rendre accessibles à l'exécution

4.4. À l'intérieur du manifeste de guerre

Comme mentionné précédemment, le plugin Maven Spring Boot trouve la classe principale et génère la configuration nécessaire pour exécuter la commande java .

The resulting MANIFEST.MF has some additional lines:

Start-Class: com.baeldung.webjar.WebjarsdemoApplication Main-Class: org.springframework.boot.loader.WarLauncher

In particular, we can observe that the last one specifies the Spring Boot class loader launcher to use.

4.5. Inside a Jar File

Due to the default packaging strategy, our war packaging scenario doesn't differ much, whether we use the Spring Boot Maven Plugin or not.

To better appreciate the advantages of the plugin, we can try changing the pom packaging configuration to jar and run mvn clean package again.

We can now observe that our fat jar is organized a bit differently from our previous war file:

  • All our classes and resources folders are now located under BOOT-INF/classes
  • BOOT-INF/lib holds all the external libraries

Without the plugin, the lib folder would not exist, and all the content of BOOT-INF/classes would be located in the root of the package.

4.6. Inside the Jar Manifest

Also the MANIFEST.MF has changed, featuring these additional lines:

Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Spring-Boot-Version: 2.1.3.RELEASE Main-Class: org.springframework.boot.loader.JarLauncher

Spring-Boot-Classes and Spring-Boot-Lib are particularly interesting, as they tell us where the class loader is going to find classes and external libraries.

5. How to Choose

When analyzing tools, it's imperative to take account of the purpose these tools are created for. Do we want to ease the development or ensure smooth deployment and portability? Let's have a look at the phases most affected by this choice.

5.1. Development

As developers, we often spend most of our time coding without needing to spend a lot of time setting up our environment to run the code locally. In simple applications, that's usually not a concern. But, for more complex projects, we may need to set environment variables, start servers, and populate databases.

Configuring the right environment every time we want to run the application would be very impractical, especially if more than one service has to run at the same time.

That's where running the code with Maven helps us. We already have the entire codebase checked out locally, so we can leverage the pom configuration and resource files. We can set environment variables, spawn an in-memory database, and even download the correct server version and deploy our application with one command.

Even in a multi-module codebase, where each module needs different variables and server versions, we can easily run the right environment via Maven profiles.

5.2. Production

The more we move towards production, the more the conversation shifts towards stability and security. That is why we cannot apply the process used for our development machine to a server with live customers.

Running the code through Maven at this stage is bad practice for multiple reasons:

  • First of all, we would need to install Maven
  • Then, just because we need to compile the code, we need the full Java Development Kit (JDK)
  • Next, we have to copy the codebase to our server, leaving all our proprietary code in plain text
  • The mvn command has to execute all phases of the life cycle (find sources, compile, and run)
  • Thanks to the previous point, we would also waste CPU and, in the case of a cloud server, money
  • Maven spawns multiple Java processes, each using memory (by default, they each use the same memory amount as the parent process)
  • Finally, if we have multiple servers to deploy, all the above is repeated on each one

These are just a few reasons why shipping the application as a package is more practical for production.

6. Conclusion

Dans ce tutoriel, nous avons exploré les différences entre l'exécution de notre code via Maven et via la commande java -jar . Nous avons également présenté un bref aperçu de certains scénarios de cas pratiques.

Le code source utilisé dans cet article est disponible à l'adresse over sur GitHub.