JAR sottili con Spring Boot

1. Introduzione

In questo tutorial, vedremo come creare un progetto Spring Boot in un file JAR sottile, utilizzando il progetto spring-boot-thin-launcher .

Spring Boot è noto per le sue distribuzioni JAR "pesanti", in cui un singolo artefatto eseguibile contiene sia il codice dell'applicazione che tutte le sue dipendenze.

L'avvio è anche ampiamente utilizzato per sviluppare microservizi. Questo a volte può essere in contrasto con l'approccio "fat JAR" perché includere le stesse dipendenze più e più volte in molti artefatti può diventare un importante spreco di risorse.

2. Prerequisiti

Prima di tutto, ovviamente, abbiamo bisogno di un progetto Spring Boot. In questo articolo, esamineremo le build Maven e le build Gradle nelle loro configurazioni più comuni.

È impossibile coprire tutti i sistemi di compilazione e creare configurazioni là fuori, ma, si spera, vedremo abbastanza dei principi generali che dovresti essere in grado di applicarli alla tua configurazione specifica.

2.1. Progetti Maven

In un progetto Boot creato con Maven, dovremmo avere il plug-in Spring Boot Maven configurato nel file pom.xml del nostro progetto , nel suo genitore o in uno dei suoi antenati:

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

La versione delle dipendenze di Spring Boot viene solitamente decisa utilizzando una BOM o ereditando da un POM genitore come nel nostro progetto di riferimento:

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

2.2. Progetti Gradle

In un progetto Boot creato con Gradle, avremo il plug-in 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' }

Nota che, in questo articolo, prenderemo in considerazione solo Boot 2.xe progetti successivi. Il Thin Launcher supporta anche le versioni precedenti, ma richiede una configurazione Gradle leggermente diversa che stiamo omettendo per semplicità. Si prega di guardare la homepage del progetto per maggiori dettagli.

3. Come creare un JAR sottile?

Spring Boot Thin Launcher è una piccola libreria che legge le dipendenze di un artefatto da un file in bundle nell'archivio stesso, le scarica da un repository Maven e infine avvia la classe principale dell'applicazione.

Quindi, quando costruiamo un progetto con la libreria, otteniamo un file JAR con il nostro codice, un file che enumera le sue dipendenze e la classe principale dalla libreria che esegue le attività precedenti.

Naturalmente, le cose sono un po 'più sfumate rispetto alla nostra spiegazione semplificata; discuteremo alcuni argomenti in profondità più avanti nell'articolo.

4. Utilizzo di base

Vediamo ora come creare un JAR "sottile" dalla nostra normale applicazione Spring Boot.

Lanceremo l'applicazione con il solito java -jar, con argomenti aggiuntivi opzionali della riga di comando che controllano il Thin Launcher. Ne vedremo un paio nelle sezioni seguenti; la homepage del progetto contiene l'elenco completo.

4.1. Progetti Maven

In un progetto Maven, dobbiamo modificare la dichiarazione del plug-in Boot (vedere la sezione 2.1) per includere una dipendenza dal layout personalizzato "sottile":

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

Il programma di avvio leggerà le dipendenze dal file pom.xml che Maven memorizza nel JAR generato nella directory META-INF / maven .

Eseguiremo la compilazione come al solito, ad esempio con mvn install .

Se vogliamo essere in grado di produrre build sia thin che fat (ad esempio in un progetto con più moduli) possiamo dichiarare il layout personalizzato in un profilo Maven dedicato.

4.2. Maven e dipendenze: thin.properties

Possiamo anche fare in modo che Maven generi un file thin.properties oltre a pom.xml . In tal caso, il file conterrà l'elenco completo delle dipendenze, comprese quelle transitive, e il programma di avvio lo preferirà rispetto a pom.xml .

The mojo (plugin) for doing so is spring-boot-thin-maven-plugin:properties, and by default, it outputs the thin.properties file in src/main/resources/META-INF, but we can specify its location with the thin.output property:

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

Please note that the output directory must exist for the goal to succeed, even if we've kept the default one.

4.3. Gradle Projects

In a Gradle project, instead, we add a dedicated plugin:

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'

To obtain a thin build, we'll tell Gradle to execute the thinJar task:

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

4.4. Gradle and Dependencies: pom.xml

In the code example in the previous section, we've declared the Maven plugin in addition to the Thin Launcher (as well as the Boot and Dependency Management plugins that we'd already seen in the Prerequisites section).

That's because, just like in the Maven case that we've seen earlier, the artifact will contain and make use of a pom.xml file enumerating the application's dependencies. The pom.xml file is generated by a task called thinPom, which is an implicit dependency of any jar task.

We can customize the generated pom.xml file with a dedicated task. Here, we'll just replicate what the thin plugin already does automatically:

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

In questo articolo abbiamo visto come realizzare il nostro barattolo sottile. Abbiamo anche visto come utilizzare l'infrastruttura Maven per scaricare e archiviare le loro dipendenze.

La homepage del thin launcher ha alcune altre guide HOW-TO per scenari come le distribuzioni cloud su Heroku, oltre all'elenco completo degli argomenti della riga di comando supportati.

L'implementazione di tutti gli esempi Maven e frammenti di codice può essere trovata nel progetto GitHub, come progetto Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.

Allo stesso modo, tutti gli esempi Gradle si riferiscono a questo progetto GitHub.