Esecuzione di un'app Spring Boot con Maven contro un Executable War / Jar

1. Introduzione

In questo tutorial, esploreremo le differenze tra l'avvio di un'applicazione Web Spring Boot tramite il comando mvn spring-boot: run e l'esecuzione dopo che è stato compilato in un pacchetto jar / war tramite il comando java -jar .

Supponiamo che qui tu abbia già familiarità con la configurazione dell'obiettivo di riassemblaggio di Spring Boot . Per maggiori dettagli su questo argomento, leggi Creare un'app Fat Jar con Spring Boot.

2. Il plugin Spring Boot Maven

Quando si scrive un'applicazione Spring Boot, il plug-in Spring Boot Maven è lo strumento consigliato per creare, testare e impacchettare il nostro codice.

Questo plugin viene fornito con molte funzioni utili, come:

  • risolve le versioni corrette delle dipendenze per noi
  • può impacchettare tutte le nostre dipendenze (incluso un server di applicazioni incorporato se necessario) in un unico fat jar / war eseguibile e inoltre:
    • gestisci per noi la configurazione del percorso di classe, quindi possiamo saltare l' opzione lunga -cp nel nostro comando java -jar
    • implementare un ClassLoader personalizzato per individuare e caricare tutte le librerie jar esterne, ora nidificate all'interno del pacchetto
    • trova automaticamente il metodo main () e configuralo nel manifest, quindi non dobbiamo specificare la classe principale nel nostro comando java -jar

3. Eseguire il codice con Maven in forma esplosa

Quando lavoriamo su un'applicazione web, possiamo sfruttare un'altra caratteristica molto interessante del plugin Spring Boot Maven: la capacità di distribuire automaticamente la nostra applicazione web in un server di applicazioni incorporato.

Abbiamo solo bisogno di una dipendenza per far sapere al plugin che vogliamo usare Tomcat per eseguire il nostro codice:

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

Ora, quando si esegue il comando mvn spring-boot: run nella cartella principale del nostro progetto, il plugin legge la configurazione pom e capisce che abbiamo bisogno di un contenitore di applicazioni web.

L'esecuzione del comando mvn spring-boot: run avvia il download di Apache Tomcat e inizializza l'avvio di Tomcat.

Proviamolo:

$ 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)

Quando il log mostra la riga contenente "Applicazione avviata", la nostra applicazione web è pronta per essere interrogata tramite il browser all'indirizzo // localhost: 8080 /

4. Esecuzione del codice come applicazione autonoma in pacchetto

Una volta superata la fase di sviluppo e desideriamo progredire per portare la nostra applicazione in produzione, dobbiamo creare un pacchetto dell'applicazione.

Sfortunatamente, se stiamo lavorando con un pacchetto jar , l' obiettivo di base del pacchetto Maven non include nessuna delle dipendenze esterne.

Ciò significa che possiamo usarlo solo come libreria in un progetto più grande.

Per aggirare questa limitazione, dobbiamo sfruttare l' obiettivo di riassemblaggio del plug-in Maven Spring Boot per eseguire il nostro jar / war come applicazione autonoma.

4.1. Configurazione

Di solito, dobbiamo solo configurare il plug-in di compilazione:

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

Ma il nostro progetto di esempio contiene più di una classe principale, quindi dobbiamo dire a Java quale classe eseguire, configurando il plugin:

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

o impostando la proprietà della classe iniziale :

 com.baeldung.webjar.WebjarsdemoApplication 

4.2. Esecuzione dell'applicazione

Ora possiamo eseguire il nostro esempio di guerra con due semplici comandi:

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

Maggiori dettagli su come eseguire un file jar sono disponibili nel nostro articolo Esegui applicazione JAR con argomenti della riga di comando.

4.3. Dentro il file di guerra

Per capire meglio come il comando menzionato sopra possa eseguire un'applicazione server completa, possiamo dare un'occhiata al nostro spring-boot-ops.war .

Se lo decomprimiamo e sbirciamo all'interno, troviamo i soliti sospetti:

  • META-INF , con MANIFEST.MF generato automaticamente
  • WEB-INF / classes , contenente le nostre classi compilate
  • WEB-INF / lib , che contiene le nostre dipendenze di guerra e i file jar Tomcat incorporati

Non è tutto però, poiché ci sono alcune cartelle specifiche per la nostra configurazione del pacchetto fat:

  • Fornito da WEB-INF / lib , contenente le librerie esterne richieste durante l'esecuzione incorporata ma non richieste durante la distribuzione
  • org / springframework / boot / loader , che contiene il caricatore di classi personalizzato Spring Boot: questa libreria è responsabile del caricamento delle nostre dipendenze esterne e di renderle accessibili in runtime

4.4. Dentro il manifesto di guerra

Come accennato in precedenza, il plug-in Maven Spring Boot trova la classe principale e genera la configurazione necessaria per eseguire il comando 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

In questo tutorial, abbiamo esplorato le differenze tra l'esecuzione del nostro codice tramite Maven e tramite il comando java -jar . Abbiamo anche eseguito una rapida panoramica di alcuni scenari di casi pratici.

Il codice sorgente utilizzato in questo articolo è disponibile su GitHub.