En los entornos de producción, el despliegue de aplicaciones Spring Boot mediante el formato tradicional "Fat JAR" puede resultar ineficiente. Cada modificación mínima en el código fuente obliga a regenerar y transferir un artefacto de gran tamaño, lo que consume ancho de banda y ralentiza los ciclos de despliegue. Para optimizar este proceso, es posible desacoplar las bibliotecas de terceros y los archivos de configuración del JAR ejecutable principal. Esta estrategia reduce drásticamente el peso del artefacto a transferir, permitiendo actualizar únicamente el código de negocio.
Estrategia 1: Uso de spring-boot-maven-plugin con cargador externo
Este método aprovecha el PropertiesLauncher de Spring Boot para cargar dependencias desde un directorio externo. Primero, extraemos los recursos de configuración.
1. Externalización de archivos de configuración
Configuramos el plugin de recursos de Maven para mover los archivos de configuración a un directorio externo durante la fase de compilación.
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<targetPath>${project.build.directory}/external-config</targetPath>
<includes>
<include>**/*.yaml</include>
<include>**/*.properties</include>
<include>**/logback*.xml</include>
</includes>
</resource>
</resources>
2. Generación del JAR ejecutable sin dependencias
Modificamos el plugin oficial de Spring Boot para excluir todas las dependencias, generando un JAR ligero. Se utiliza un identificador ficticio en la sección de inclusiones para forzar la exclusión total de librerías de terceros.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<includes>
<include>
<groupId>com.placeholder</groupId>
<artifactId>dummy-artifact</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
3. Extracción de dependencias a un directorio lib
Utilizamos el plugin de dependencisa para copiar todos los JARs reqeuridos a una carpeta externa.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>extract-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/external-libs</outputDirectory>
<includeScope>runtime</includeScope>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution>
</executions>
</plugin>
Para ejecutar la aplicación resultante, se debe indicar al lanzador de Spring Boot dónde encontrar las librerías externas. Spring Boot buscará automáticamente la configuración en el directorio external-config si se encuentra en la raíz de ejecución.
java -Dloader.path=./external-libs -jar mi-aplicacion.jar
Estrategia 2: Configuración de Classpath con maven-jar-plugin
Esta alternativa utiliza el empaquetado estándar de Java, modificando el manifiesto del JAR para incluir las rutas de las dependencias y configuraciones externas.
1. Copia de recursos de configuración
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>export-config</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/release/config</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
2. Configuración del manifiesto y exclusión de recursos
Ajustamos el maven-jar-plugin para definir la clase principal, el prefijo del classpath y la inclusión del directorio de configuración en el classpath de ejecución.
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<outputDirectory>${project.build.directory}/release</outputDirectory>
<excludes>
<exclude>**/*.yaml</exclude>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
</excludes>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>com.empresa.proyecto.Application</mainClass>
</manifest>
<manifestEntries>
<Class-Path>config/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
3. Agrupación de librerías
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>collect-libs</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/release/libs</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
La ejecución es directa, ya que el classpath está definido en el manifiesto:
java -jar release/mi-aplicacion.jar
La principal limitación de este enfoque es su rigidez: cualquier cambio en la estructura de directorios o en los nombres de los JARs de terceros requerirá una recompilación para actualizar el manifiesto.
Estrategia 3: Distribución personalizada con maven-assembly-plugin
El plugin maven-assembly-plugin ofrece el mayor nivel de control, permitiendo crear archivos de distribución completos (como ZIP o TAR.GZ) que incluyen scripts de inicio, configuraciones y binarios en una estructura de directorios predefinida.
1. Filtrado de recursos y configuración de Spring Boot
Se aplican las mismas configuraciones de recursos y del plugin de Spring Boot mostradas en la Estrategia 1 para generar el JAR base sin dependencias.
2. Definición del plugin de ensamblaje
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/assembly/distribution.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>build-distribution</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
3. Descriptor de ensamblaje (distribution.xml)
Este archivo define la topología exacta del paquete de distribución, asignando permisos de ejecución a los scripts y organizando los componentes en sus respectivos directorios.
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0">
<id>release-package</id>
<formats>
<format>zip</format>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!-- Scripts de arranque -->
<fileSet>
<directory>${basedir}/scripts</directory>
<outputDirectory>/</outputDirectory>
<fileMode>0755</fileMode>
<lineEnding>unix</lineEnding>
<includes>
<include>*.sh</include>
</includes>
</fileSet>
<!-- Archivos de configuración -->
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>/config</outputDirectory>
<fileMode>0644</fileMode>
<includes>
<include>**/*.yaml</include>
<include>**/*.properties</include>
</includes>
</fileSet>
<!-- JAR principal de la aplicación -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
<!-- Dependencias del proyecto -->
<dependencySets>
<dependencySet>
<outputDirectory>/lib</outputDirectory>
<useProjectArtifact>false</useProjectArtifact>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
Este enfoque genera un archivo comprimido listo para ser extraído en el servidor de producción. La aplicación se inicia de manera similar a la primera estrategia, utilizando los scripts incluidos en el paquete o ejecutando directamente el JAR con la ruta del cargador apuntando al directorio lib extraído.