Ciclo de vida de compilación de Maven
Maven se basa en el concepto central de un ciclo de vida de construcción. Lo que esto significa es que el proceso para construir y…
Un requisito típico de los proyectos es agregar la salida junto con sus dependencias, módulos y otros archivos en un solo archivo distribuible.
Un uber-JAR, también conocido como fat-JAR o JAR con dependencias, es un archivo JAR que no solo contiene un programa Java o Scala, sino que también incorpora sus dependencias. Esto significa que el JAR funciona como una distribución «todo en uno» del software.
Cuando distribuimos una aplicación como un JAR simple, quien la ejecute necesita tener todas las dependencias disponibles en el classpath. Con un uber-JAR, en cambio, basta con tener el archivo JAR y una instalación de Java para ejecutar la aplicación. Esto simplifica enormemente el despliegue, especialmente en entornos como clústeres de Spark, servidores de producción o pipelines de CI/CD.
Existen tres métodos comunes para construir un JAR con dependencias:
META-INF/services).En este artículo vamos a ver el método Shaded. Para ello vamos a emplear el plugin Maven Shade. Este plugin brinda la capacidad de empaquetar el artefacto en un uber-jar, incluidas sus dependencias y cambiar el nombre de los paquetes de algunas de las dependencias.
El plugin Shade tiene un solo goal shade:shade que está vinculado a la fase package y se usa para crear un shaded JAR.
Para crear el JAR con dependencias será necesario incluir en el archivo pom.xml del proyecto dentro de la sección del build la siguiente configuración del Maven Shade Plugin:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Una vez que hayamos agregado el plugin a la sección del build podemos personalizar las diferentes configuraciones que deseemos dentro de la etiqueta <configuration>. Para obtener más detalles sobre esta configuración pueden dirigirse a la página oficial de Maven Shade Plugin.
pom.xmlPara tener una visión más clara, veamos un ejemplo completo de un archivo pom.xml que utiliza el Maven Shade Plugin:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ejemplo</groupId>
<artifactId>mi-aplicacion</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.ejemplo.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
En este ejemplo, además de la configuración básica del plugin, hemos añadido un ManifestResourceTransformer. Este transformer se encarga de establecer la clase principal (mainClass) en el archivo MANIFEST.MF del JAR resultante, lo que permite ejecutar el JAR directamente con java -jar.
Lo que resta ahora es ir desde un terminal a la carpeta del proyecto y ejecutar el comando:
mvn package
Lo que sucederá a continuación es que Maven comenzará a empaquetar nuestro proyecto y creará un JAR con dependencias, que para este caso será un shaded JAR.
Al finalizar el proceso, Maven genera dos archivos en el directorio target/:
mi-aplicacion-1.0.0.jar — El uber-JAR (shaded) con todas las dependencias incluidas.original-mi-aplicacion-1.0.0.jar — El JAR original sin dependencias, que Maven renombra automáticamente con el prefijo original-.Para verificar que el JAR contiene las dependencias, podemos inspeccionar su contenido con el siguiente comando:
jar tf target/mi-aplicacion-1.0.0.jar
Este comando listará todos los archivos empaquetados dentro del JAR. Deberías ver tanto las clases de tu proyecto como las clases de las dependencias (en este ejemplo, las clases de Guava).
Para ejecutar el uber-JAR directamente:
java -jar target/mi-aplicacion-1.0.0.jar
Los Resource Transformers son uno de los componentes más poderosos del Maven Shade Plugin. Cuando se combinan múltiples JARs en uno solo, es posible que ciertos archivos de recursos se sobrescriban entre sí. Los transformers permiten manejar estos conflictos de manera inteligente.
Algunos de los transformers más utilizados son:
ManifestResourceTransformer — Permite establecer entradas en el archivo MANIFEST.MF, como la clase principal (mainClass).AppendingTransformer — Concatena el contenido de archivos de recursos con el mismo nombre en lugar de sobrescribirlos. Es útil para archivos como META-INF/spring.handlers o META-INF/spring.schemas.ServicesResourceTransformer — Fusiona los archivos META-INF/services de las diferentes dependencias, lo cual es esencial cuando se trabaja con el patrón Service Provider Interface (SPI) de Java.Un ejemplo de configuración con múltiples transformers:
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.ejemplo.App</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
</transformers>
</configuration>
Al trabajar con el Maven Shade Plugin, es recomendable seguir estas buenas prácticas:
<excludes> dentro de <artifactSet> para evitar incluir dependencias que no sean necesarias en tiempo de ejecución, como librerías de testing (junit, mockito).*.SF, *.DSA, *.RSA) de los JARs de dependencias pueden causar errores de seguridad. Es recomendable excluirlos:<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
mainClass: Si el JAR se va a ejecutar directamente, asegúrate de configurar el ManifestResourceTransformer con la clase principal.Estos son algunos de los errores más frecuentes al trabajar con el Maven Shade Plugin:
SecurityException: Invalid signature file digest — Ocurre cuando el JAR contiene archivos de firma de las dependencias. Se soluciona excluyendo los archivos META-INF/*.SF, META-INF/*.DSA y META-INF/*.RSA como se muestra en la sección anterior.No main manifest attribute — Aparece al intentar ejecutar el JAR con java -jar sin haber configurado el ManifestResourceTransformer con la mainClass.<relocations> puede resolver este problema:<configuration>
<relocations>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>com.ejemplo.shaded.com.google.common</shadedPattern>
</relocation>
</relocations>
</configuration>
Hasta aquí hemos visto cómo poder crear un JAR con dependencias con el plugin Maven Shade Plugin, desde la configuración básica hasta el uso de transformers, filtros y relocations. El Maven Shade Plugin es una herramienta esencial para cualquier proyecto Java o Scala que necesite distribuirse como un único archivo ejecutable.