Jar Ejecutable con Gradle
Los archivos .jar
son empaquetados de clases de Java, se podría comparar con un simple archivo .zip
que contiene todos los .class
de nuestra aplicación. En el pueden o no haber una o varias clases principales.
Las clases principales son las que contienen un método con la firma public static void main(String[] args)
, por ejemplo:
package com.example.executable;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
La manera tradicional de ejecutar esta clase principal es:
$ java -cp example.jar com.example.executable.Main
Hello World
Si damos doble clic o ejecutamos un java -jar nombre-archivo-jar
obtendremos un error:
$ java -jar example.jar
no hay ningún atributo de manifiesto principal en example.jar
Para poder ejecutarlo con doble clic o java -jar nombre-archivo-jar
se debe incluir dentro del JAR el archivo de manifiesto, localizado en META-INF/MANIFEST.MF
y en su contenido el atributo Main-Class
indicar el nombre de la clase principal que se debe ejecutar.
NOTA: El archivo
META-INF/MANIFEST.MF
tiene que terminar con una línea en blanco para que sea correctamente interpretado por Java.
Adicionalmente, el proyecto puede requerir dependencias de terceros, estas dependencias se debe agregar también en el archivo de manifiesto, en el contenido del atributo Class-Path
con la ruta de los JARs requeridos, separando cada uno con espacio.
Por ejemplo, si agregamos dependencias a SLF4J con Log4j en el build.gradle
dependencies {
implementation 'org.slf4j:slf4j-log4j12:1.7.30'
}
Modificamos la clase principal com.example.executable.Main
:
package com.example.executable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger LOG = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
LOG.info("Hello World");
}
}
Y luego modificamos el archivo META-INF/MANIFEST.MF
:
Manifest-Version: 1.0
Class-Path: libs/slf4j-log4j12-1.7.30.jar libs/slf4j-api-1.7.30.jar libs/log4j-1.2.17.jar
Main-Class: com.example.executable.Main
Se deben copiar todas las dependencias en una carpeta llamada libs junto al archivo JAR.
Ya con esto podemos ejecutar la aplicación dando doble clic en ella o ejecutando java -jar nombre-archivo-jar
sin problemas.
Copiando Dependencias
Gradle cuenta con un plugin llamado Gradle Distribution Plugin, que nos crea al momento de compilar un empaquetado para distribución, el mismo lo podemos localizar en build/distributions
(tanto en .zip
como .tar
), dentro del archivo comprimido se encuentran todas las dependencias en la carpeta lib
y scripts para ejecutar en Windows/Linux en la carpeta bin
.
example.zip
├── bin
│ ├── example
│ └── example.bat
└── lib
├── example.jar
├── log4j-1.2.17.jar
├── slf4j-api-1.7.30.jar
└── slf4j-log4j12-1.7.30.jar
Con Gradle Java Plugin podemos indicar en la tarea jar
los atributos de archivo de manifiesto, por ejemplo el atributo Main-Class
:
plugins {
id 'java'
}
jar {
manifest {
attributes(
'Main-Class': 'com.example.executable.Main'
)
}
}
Por su parte, si requerimos copiar las dependencias ocupamos crear una tarea para ello, adicionalmente se debe agregar el atributo Class-Path
en el archivo de manifiesto con las dependencias separadas por espacio.
plugins {
id 'java'
}
task copyToLib(type: Copy) {
into "${buildDir}/libs/libs"
from configurations.runtimeClasspath
}
jar {
manifest {
attributes(
'Main-Class': 'com.example.executable.Main',
"Class-Path": configurations.runtimeClasspath.collect { 'libs/' + it.name }.join(' ')
)
}
dependsOn(copyToLib)
}
En el ejemplo anterior la tarea copyToLib
se encarga de copiar las dependencias en la carpeta libs y en la tarea jar
indicamos que dependen de copyToLib
, adicionalmente actualizamos en el archivo de manifiesto el atributo Class-Path
.
Generar un FatJar
Un FatJar (también conocido como UberJar) es un archivo JAR que contiene no solo sus clases, sino que también contiene dentro de él mismo todas sus dependencias. Su ventaja consiste en ser un único archivo (.jar) para distribuir la aplicación (no se requiere copiar la carpeta libs como en el caso anterior).
Existen varias formas en Gradle para generar un FatJar.
Usando el plugin de Java
Se debe contar con Gradle Java Plugin y agregar la siguiente configuración en la tarea jar
del proyecto:
plugins {
id 'java'
}
jar {
manifest {
attributes(
'Main-Class': 'com.example.executable.Main'
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
De esta forma al compilar el proyecto con gradle build
se construirá un FatJar.
Creando una tarea propia
Igual que el anterior, se debe contar con Gradle Java Plugin y creamos una tarea propia, en este ejemplo la tarea se llama fatJar
:
plugins {
id 'java'
}
task fatJar(type: Jar) {
manifest {
attributes(
'Main-Class': 'com.example.executable.Main'
)
}
archiveBaseName = project.name + '-all'
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}
Para crear el FatJar en este caso, se requiere llamar a la tarea con gradle fatJar
.
Usando un plugin de terceros
En el repositorio de Gradle existen varios plugins para crear FatJar’s, uno de los más populares es el siguiente:
plugins {
id 'com.github.johnrengelman.shadow' version '7.0.0'
}
Con este plugin, no requerimos realizar ninguna configuración adicional, basta con compilar el proyecto para generar el JAR original y el FatJar.