W3docs

Script de construcción Gradle en Java

Anatomía de un script de construcción Gradle para Java: plugins, dependencias, tareas y conceptos básicos del DSL.

Un script de construcción Gradle describe cómo compilar, probar y empaquetar un proyecto — no como un documento XML rígido, sino como código. Gradle lee un archivo build.gradle (DSL de Groovy) o build.gradle.kts (DSL de Kotlin), convierte las declaraciones en un grafo de tareas y ejecuta solo las tareas que tu comando necesita, en el orden correcto. Donde Maven te ofrece un ciclo de vida fijo, Gradle te ofrece uno programable: aplicar un plugin, declarar una dependencia y definir una tarea son sentencias ordinarias en un lenguaje real.

Esta página asume que ya sabes qué es Gradle a un nivel general — si no es así, comienza con la introducción a Gradle. Aquí desmontamos un build.gradle real bloque a bloque: plugins, repositorios, dependencias y tareas.

La anatomía de build.gradle

Un script de construcción Java mínimo tiene cuatro bloques: qué plugins aplicar, de dónde obtener las dependencias, cuáles son esas dependencias y algo de configuración. Aquí hay uno completo e idiomático en DSL de Kotlin:

plugins {
    java
    application
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.google.guava:guava:32.1.3-jre")
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}

application {
    mainClass = "com.example.App"
}

tasks.test {
    useJUnitPlatform()
}

El plugin java solo ya te da compileJava, test, jar y una docena de tareas más de forma gratuita. El plugin application añade run e installDist. La línea tasks.test { useJUnitPlatform() } conecta la tarea test para ejecutar pruebas de JUnit 5 — sin ella, Gradle usa de forma predeterminada el motor legado de JUnit 4 y no ejecuta nada en silencio. Todo lo demás es configuración de lo que hacen esas tareas.

Plugins, repositorios y el ciclo de vida de construcción

Casi nada en Gradle está integrado — las capacidades llegan como plugins. El plugin java es la base para el trabajo en JVM; otros se añaden encima:

PluginLo que añade
javacompileJava, test, jar, conjuntos de fuentes, las configuraciones de dependencies
applicationrun y una distribución empaquetada con scripts de inicio
java-libraryLa distinción api vs implementation para bibliotecas
org.springframework.bootbootJar, bootRun para aplicaciones Spring Boot
jacocoInformes de cobertura de código conectados a test

repositories { } le dice a Gradle de dónde descargar las dependencias — mavenCentral() es la elección habitual. Sin un repositorio, ninguna dependencia externa puede resolverse.

Declarar dependencias y sus alcances

Las dependencias se declaran con una configuración que controla dónde aparecen en el classpath. Elegir la correcta mantiene tu classpath de compilación limpio y tus construcciones rápidas:

dependencies {
    // On the compile and runtime classpath, but NOT exposed to consumers
    implementation("org.apache.commons:commons-lang3:3.14.0")

    // Part of this library's public API — leaks to consumers (java-library only)
    api("com.google.guava:guava:32.1.3-jre")

    // Needed to compile, but provided at runtime by the environment
    compileOnly("org.projectlombok:lombok:1.18.30")

    // Only on the test classpath
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")

    // Only at runtime (e.g. a JDBC driver loaded by name)
    runtimeOnly("org.postgresql:postgresql:42.7.1")
}

El formato de coordenadas es group:name:version — las mismas coordenadas que Maven usa en su pom.xml. Cuando dos dependencias traen el mismo módulo en diferentes versiones, la estrategia predeterminada de Gradle coloca una sola versión, la más alta, en el classpath — el ejemplo ejecutable a continuación modela exactamente esto.

Tareas: la unidad de trabajo

Cada acción que realiza Gradle es una tarea, y las tareas declaran dependencias de otras tareas. Ejecutar gradle build no hace una sola cosa; recorre un grafo y ejecuta cada requisito previo una vez. También puedes definir las tuyas propias:

tasks.register("printVersion") {
    group = "help"
    description = "Prints the project version."
    doLast {
        println("Project version is $version")
    }
}

// Make the jar task wait for our custom task
tasks.named("jar") {
    dependsOn("printVersion")
}

Dos hechos más hacen a Gradle rápido. Primero, es incremental: una tarea cuyos entradas y salidas no han cambiado se informa como UP-TO-DATE y se omite. Segundo, el Gradle Wrapper (./gradlew, respaldado por gradle/wrapper/gradle-wrapper.properties) fija una versión de Gradle por proyecto, de modo que cada desarrollador y máquina de CI construye con la misma cadena de herramientas — nunca instalas Gradle globalmente.

Un ejemplo práctico: una construcción, modelada en Java puro

Gradle en sí no está disponible en este ejecutor, así que el programa a continuación modela las tres ideas que hacen funcionar un script de construcción — el grafo de tareas y su orden de ejecución, el omitido incremental de tareas actualizadas, y la resolución de conflictos de versión de dependencias — usando únicamente el JDK. Es el modelo mental que gradle build ejecuta en realidad.

java— editable, runs on the server

Lo que se debe extraer de la ejecución:

  • La lista de tareas para gradle build se calcula mediante una ordenación topológica, no se escribe a mano. compileJava y processResources vienen antes que classes, que viene antes que jar y test, que vienen antes que build — exactamente el orden que Gradle imprime con cada prefijo :taskName, porque una tarea solo puede ejecutarse después de que todo aquello de lo que dependsOn haya terminado.
  • Un diamante en el grafo ejecuta un requisito previo compartido una vez, no dos. Tanto jar como test dependen de classes, y sin embargo classes aparece una sola vez en el orden — el conjunto done es lo que impide a Gradle recompilar el mismo código para cada tarea descendente.
  • La segunda ejecución muestra el comportamiento incremental de Gradle: compileJava, processResources y classes están UP-TO-DATE y se omiten, por lo que solo 3 tareas se ejecutan realmente. Por eso un proyecto sin cambios se reconstruye en milisegundos — Gradle compara las entradas y salidas de las tareas y no hace ningún trabajo que pueda evitar.
  • La resolución de dependencias colapsa un conflicto de versión a un único ganador: slf4j-api se solicita tanto en 2.0.9 (directa) como en 1.7.36 (transitiva a través de guava), pero el classpath resuelto lo lista una vez a 2.0.9. La estrategia predeterminada de Gradle es la versión más alta gana, por lo que un único jar consistente llega al classpath en lugar de dos copias en conflicto.
  • La línea final nombra la versión de Gradle 8.7 como si se leyera de gradle-wrapper.properties. En un proyecto real, el wrapper almacena esa versión en el control de versiones, de modo que ./gradlew build usa el mismo Gradle para todos — la construcción es reproducible independientemente de lo que esté (o no) instalado en la máquina.

Práctica

Práctica
Un proyecto Java de Gradle declara 'org.slf4j:slf4j-api:2.0.9' directamente, mientras que una dependencia transitiva trae 'org.slf4j:slf4j-api:1.7.36'. Con la estrategia de resolución predeterminada de Gradle, ¿qué termina en el classpath?
Un proyecto Java de Gradle declara 'org.slf4j:slf4j-api:2.0.9' directamente, mientras que una dependencia transitiva trae 'org.slf4j:slf4j-api:1.7.36'. Con la estrategia de resolución predeterminada de Gradle, ¿qué termina en el classpath?
Was this page helpful?