Frascos finos con Spring Boot

1. Introducción

En este tutorial, veremos cómo construir un proyecto Spring Boot en un archivo JAR delgado, usando el proyecto spring-boot-thin-launcher .

Spring Boot es conocido por sus implementaciones JAR “gordas”, donde un único artefacto ejecutable contiene tanto el código de la aplicación como todas sus dependencias.

Boot también se usa ampliamente para desarrollar microservicios. A veces, esto puede estar en desacuerdo con el enfoque de “fat JAR” porque incluir las mismas dependencias una y otra vez en muchos artefactos puede convertirse en un importante desperdicio de recursos.

2. Requisitos previos

En primer lugar, necesitamos un proyecto Spring Boot, por supuesto. En este artículo, veremos las compilaciones de Maven y las compilaciones de Gradle en sus configuraciones más comunes.

Es imposible cubrir todos los sistemas de compilación y las configuraciones de compilación que existen, pero, con suerte, veremos suficientes principios generales para que pueda aplicarlos a su configuración específica.

2.1. Proyectos Maven

En un proyecto de arranque creado con Maven, deberíamos tener el complemento Spring Boot Maven configurado en el archivo pom.xml de nuestro proyecto , su padre o uno de sus antepasados:

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

La versión de las dependencias de Spring Boot generalmente se decide utilizando una lista de materiales o heredando de un POM principal como en nuestro proyecto de referencia:

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

2.2. Proyectos Gradle

En un proyecto de arranque creado con Gradle, tendremos el complemento 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' }

Tenga en cuenta que, en este artículo, consideraremos solo proyectos de Boot 2.xy posteriores. Thin Launcher también es compatible con versiones anteriores, pero requiere una configuración de Gradle ligeramente diferente que omitimos por simplicidad. Consulte la página de inicio del proyecto para obtener más detalles.

3. ¿Cómo crear un JAR delgado?

Spring Boot Thin Launcher es una pequeña biblioteca que lee las dependencias de un artefacto de un archivo incluido en el propio archivo, las descarga de un repositorio de Maven y finalmente lanza la clase principal de la aplicación.

Entonces, cuando construimos un proyecto con la biblioteca, obtenemos un archivo JAR con nuestro código, un archivo que enumera sus dependencias y la clase principal de la biblioteca que realiza las tareas anteriores.

Por supuesto, las cosas son un poco más matizadas que nuestra explicación simplificada; analizaremos algunos temas en profundidad más adelante en el artículo.

4. Uso básico

Veamos ahora cómo construir un JAR "delgado" desde nuestra aplicación Spring Boot regular.

Lanzaremos la aplicación con el java -jar habitual , con argumentos de línea de comando adicionales opcionales que controlan el Thin Launcher. Veremos un par de ellos en las siguientes secciones; La página de inicio del proyecto contiene la lista completa.

4.1. Proyectos Maven

En un proyecto de Maven, tenemos que modificar la declaración del complemento de arranque (ver sección 2.1) para incluir una dependencia en el diseño "delgado" personalizado:

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

El lanzador leerá las dependencias del archivo pom.xml que Maven almacena en el JAR generado en el directorio META-INF / maven .

Realizaremos la compilación como de costumbre, por ejemplo, con mvn install .

Si queremos poder producir compilaciones tanto delgadas como gordas (por ejemplo, en un proyecto con varios módulos) podemos declarar el diseño personalizado en un perfil de Maven dedicado.

4.2. Maven y dependencias: thin.properties

También podemos hacer que Maven genere un archivo thin.properties además de pom.xml . En ese caso, el archivo contendrá la lista completa de dependencias, incluidas las transitivas, y el lanzador lo preferirá al pom.xml .

El mojo (complemento) para hacerlo es spring-boot-thin-maven-plugin: properties y , de forma predeterminada, genera el archivo thin.properties en src / main / resources / META-INF , pero podemos especificar su ubicación con la propiedad thin.output :

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

Tenga en cuenta que el directorio de salida debe existir para que el objetivo tenga éxito, incluso si hemos mantenido el predeterminado.

4.3. Proyectos Gradle

En un proyecto de Gradle, en cambio, agregamos un complemento dedicado:

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'

Para obtener una compilación delgada, le diremos a Gradle que ejecute la tarea thinJar :

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

4.4. Gradle y dependencias: 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

En este artículo, hemos visto cómo hacer nuestro frasco delgado. También hemos visto cómo usar la infraestructura de Maven para descargar y almacenar sus dependencias.

La página de inicio del lanzador delgado tiene algunas guías prácticas más para escenarios como implementaciones en la nube para Heroku, así como la lista completa de argumentos de línea de comando compatibles.

La implementación de todos los ejemplos y fragmentos de código de Maven se puede encontrar en el proyecto GitHub, como un proyecto de Maven, por lo que debería ser fácil de importar y ejecutar como está.

Del mismo modo, todos los ejemplos de Gradle se refieren a este proyecto de GitHub.