Tabla de Contenidos

07 - Gestión de proyectos en Java: Paquetes, librerías y Maven

En la programación en Java, el reuso de código se articula mediante librerías y paquetes. Una librería es un conjunto de clases e interfaces organizadas en paquetes que proveen métodos y funcionalidades listas para usar, evitando la necesidad de reimplementarlas desde cero.

Por ejemplo, al invocar IO.println(), se está haciendo uso de la clase IO dentro del paquete java.lang (perteneciente a la librería estándar de Java, importada por defecto). Si deseamos utilizar estructuras como List o ArrayList, debemos importar explícitamente el paquete java.util.

package ejemplos.T09;

import java.util.ArrayList;
import java.util.List;

public class Librerias {
    
    static void main() {
        List<Integer> numeros = new ArrayList<>();

        IO.println("Hola mundo");
    }
}

1. Paquetes

Los paquetes (package) son la estructura organizativa básica de Java para evitar conflictos de nombres y gestionar el espacio de nombres.

Un paquete se corresponde directamente con una estructura de directorios en el sistema de archivos. Los niveles jerárquicos se separan mediante puntos. Por ejemplo, la declaración:

package es.cesguiro.foo;

exige que el archivo .java correspondiente esté ubicado en la ruta (dentro del proyecto):

es/cesguiro/foo

Importar paquetes

Para utilizar las clases públicas definidas en un paquete externo dentro de nuestro archivo de código, empleamos la sentencia import. Existen distintas variantes según el nivel de precisión o el elemento que se desee importar:

Importación de una clase o interfaz concreta

Es la opción recomendada. Se especifica de forma explícita el paquete y el nombre exacto del elemento que se va a utilizar:

import java.util.Optional;

Importación completa

Permite importar todas las clases e interfaces públicas pertenecientes a un mismo paquete utilizando el operador asterisco (*). No afecta negativamente al rendimiento en tiempo de ejecución, pero puede provocar colisiones si dos paquetes importados contienen clases con el mismo nombre (como java.util.Date y java.sql.Date):

import java.util.*;

Importación estática

Permite acceder a miembros estáticos (métodos o constantes de clase) sin necesidad de anteponer el nombre de la clase que los contiene. Simplifica la sintaxis en operaciones matemáticas o constantes globales:

import java.lang.Math.PI;

Importación de módulos completa

A partir de Java 23+, se introdujo la posibilidad de importar un módulo completo en una sola línea mediante import module. Esto pone a disposición del archivo de código todos los paquetes exportados por dicho módulo, evitando declarar múltiples sentencias import individuales:

import module java.base;

Introducido en Java 9 como el JPMS (Java Platform Module System), un módulo es un nivel de abstracción por encima de los paquetes.

  • Paquete: Agrupa clases e interfaces relacionadas dentro del código.

  • Módulo: Es un contenedor con nombre que agrupa uno o más paquetes, junto con un archivo de descriptor (module-info.java) que especifica qué paquetes exporta para que otros los usen y qué otros módulos requiere para funcionar.

Por ejemplo, el módulo estándar java.base incluye internamente los paquetes java.util, java.io, java.lang, java.math, etc. Mientras que la cláusula import java.util.*; solo importa las clases de ese paquete concreto, import module java.base; importa todos los paquetes expuestos por ese módulo.

2. Librerías externas y archivos JAR

Una librería en Java es una colección de paquetes compilados que implementan funcionalidades específicas para que puedan ser reutilizadas en múltiples aplicaciones sin necesidad de volver a escribir el código desde cero.

Existen dos grandes categorías de librerías:

El formato JAR (Java ARchive)

Las librerías en Java se distribuyen y consumen habitualmente empaquetadas en archivos con extensión .jar.

Un archivo JAR es un contenedor comprimido (basado en el formato estándar ZIP) que agrupa dentro de una misma estructura:

El Classpath (Ruta de clases)

Para que la Máquina Virtual de Java (JVM) o el compilador (javac) puedan utilizar los tipos y métodos contenidos en una librería externa .jar, dicho archivo debe ser incluido explícitamente en el Classpath.

El Classpath es una lista de rutas de carpetas o archivos .jar en la que Java busca las definiciones de clases cuando procesa las sentencias import de nuestro código.

En proyectos tradicionales (sin gestor de dependencias), los archivos .jar de terceros suelen colocarse en una carpeta llamada lib/ en la raíz del proyecto y añadirse a la configuración de compilación de la aplicación.

Por estas razones, el desarrollo profesional con librerías externas se gestiona mediante gestores de proyectos y dependencias como Apache Maven.

Aquí tienes el apartado completo sobre Gestores de Proyectos adaptado al formato DokuWiki, manteniendo la distinción clara entre la gestión completa del proyecto y la gestión de dependencias, y libre de cualquier referencia a entornos de desarrollo específicos como VSCode.

3. Gestores de Proyectos

Un gestor de proyectos (o herramienta de construcción / build tool) es una herramienta de software que automatiza y estandariza todo el ciclo de vida del desarrollo de una aplicación: desde la compilación del código fuente y la ejecución de pruebas unitarias, hasta el empaquetado final y la distribución del software.

Una de las funcionalidades más importantes (y conocidas) dentro de un gestor de proyectos es la gestión de dependencias, la cual se encarga de localizar, descargar, actualizar y resolver de forma automática todas las librerías externas que el proyecto necesita para su compilación y ejecución.

En proyectos reales, la gestión manual de archivos .jar resulta insostenible debido a:

En el ecosistema Java conviven principalmente tres gestores de proyectos:

4. Apache Maven

Maven es un gestor de proyectos integral desarrollado por la Apache Software Foundation.

Estructura Estándar de Carpetas

Maven impone la regla de Convención sobre Configuración: si respetas la organización predefinida de archivos, no necesitas configurar las rutas de compilación manualmente.

El directorio target/ se genera automáticamente durante el proceso de construcción para almacenar las clases compiladas (.class) y el artefacto final (.jar).

El archivo POM (Project Object Model)

El archivo pom.xml ubicado en la raíz del proyecto es el corazón de Maven. Contiene toda la información del proyecto, incluyendo metadatos, propiedades de compilación y la lista de dependencias externas.

Encabezado XML y Coordenadas GAV

Todo documento POM comienza con la declaración del estándar XML y la definición del modelo (modelVersion 4.0.0). A continuación se declaran las coordenadas únicas del artefacto:

<groupId>es.cesguiro</groupId>
<artifactId>ejemplo-maven</artifactId>
<version>0.0.1-SNAPSHOT</version>

Metadatos del Proyecto (Opcionales)

Es buena práctica incluir información descriptiva de la aplicación. Esta información es fundamental cuando publicamos librerías para que otros desarrolladores conozcan su propósito, licencia y autoría. Los elementos opcionales más comunes son:

<name>: Nombre legible del proyecto para mostrar en documentación o informes.\ \

<description>: Resumen o descripción del propósito del proyecto.\ \

<url>: Sitio web oficial o página de documentación del proyecto.\ \

<licenses>: Licencia bajo la cual se distribuye el software (ej: Apache 2.0, MIT).\ \

<developers>: Información sobre los autores o mantenedores del código.\ \

<scm>: Configuración del control de versiones (Software Configuration Management), indicando la ruta del repositorio Git.\ \

<licenses>
    <license>
        <name>Apache License, Version 2.0</name>
        <url>[https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt)</url>
    </license>
</licenses>

<developers>
    <developer>
        <id>cesguiro</id>
        <name>César Guijarro</name>
        <email>cesar.guijarrorosalney@edu.gva.es</email>
    </developer>
</developers>
<scm>
    <connection>scm:git:git://[github.com/cesguiro/ejemplo-maven.git](https://github.com/cesguiro/ejemplo-maven.git)</connection>
    <url>[https://github.com/cesguiro/ejemplo-maven](https://github.com/cesguiro/ejemplo-maven)</url>
</scm>

Propiedades del Proyecto (<properties>)

El apartado properties permite definir constantes globales para ser reutilizadas a lo largo del fichero pom.xml, así como configurar variables internas de Maven (como la versión de Java a compilar o la codificación de caracteres):

...
<properties>
    <java.version>25</java.version>
    <gson.version>2.10.1</gson.version>
</properties>

Gestión de Dependencias (<dependencies>)

En esta sección se listan los archivos .jar externos que requiere la aplicación. Maven se encarga de descargarlos desde el repositorio remoto y resolver sus dependencias transitivas. Para referenciar una variable declarada en <properties>, se utiliza la sintaxis ${nombre.variable}:

<dependencies>
    <!-- Dependencia para pruebas unitarias (limitada al ambito de test) -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>${gson.version}</version>
    </dependency>
</dependencies>

La etiqueta <scope> define en qué momentos del ciclo de vida del proyecto estará disponible la librería:

Configuración de Construcción (<build>)

En la etiqueta <build> se configura el comportamiento del proceso de empaquetado, la personalización del nombre del archivo resultante y el uso de plugins que extienden el ciclo de vida de compilación (como ajustar el plugin del compilador o empaquetar un archivo JAR ejecutable con todas sus dependencias incorporadas):

    <plugins>
        <!-- Plugin para asegurar la version correcta del compilador de Java -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <release>${maven.compiler.release}</release>
            </configuration>
        </plugin>
    </plugins>
</build>

Sistema de Repositorios

Maven automatiza la descarga de librerías organizando los recursos en tres tipos de repositorios:

Cuando compilas un proyecto, Maven busca cada dependencia declarada en el pom.xml dentro del repositorio local (.m2). Si no la encuentra, la descarga automáticamente desde Maven Central, la guarda en el disco local para uso futuro y la inyecta en el classpath.

Ciclo de Vida del Proyecto (Build Lifecycle)

Maven organiza el proceso de construcción en tres ciclos de vida independientes: clean (limpieza), default (construcción) y site (documentación).

Dentro de un mismo ciclo de vida, al ejecutar una fase, Maven ejecutará automáticamente todas las fases anteriores de dicho ciclo.

Las fases más importantes del ciclo default (en orden de ejecución) son:

Además, el ciclo clean cuenta con la fase:

Las órdenes de construcción se invocan desde la interfaz de línea de comandos indicando las fases a ejecutar:

mvn clean install

Aunque las fases pueden ejecutarse desde la terminal, la mayoría de entornos de desarrollo como IntelliJ IDEA, Eclipse o VS Code incorporan paneles integrados de Maven. Desde estas herramientas es posible ejecutar cualquier fase (como clean o package) simplemente haciendo doble clic sobre el nombre del ciclo de vida en la barra lateral del IDE, sin necesidad de escribir comandos de terminal.

Ejercicios

Ejercicio 1

Crea un proyecto Maven que contenga 2 paquetes (dentro del paquete original):

En el paquete front crea dos clases:

1.- Listado clientes
2.- Buscar cliente
0.- Salir
----------------------
Opción: 

Ejercicio 2

En el paquete back crea una clase llamada App (mismo nombre que en el front). Esta clase tendrá un método estático response() que devolverá la frase “Respuesta del servidor…”.

Modifica el método request() de la clase front.App para que muestre por pantalla el resultado de la función back.App.response().

Como verás, Java da un error al usar nombres de clases iguales (App). Averigua como solucionar el conflicto entre las clases front.App y front.back

Ejercicio 3

Crea la clase back.controller.CustomerController. Dentro de esta clase, crea el método findAll(), el cuál devolverá la frase “Listado de todos los clientes”.

Haz que el método back.App.response() llame al método anterior si la opción elegida por el usuario es el 1 y la frase “404 .- Recurso no encontrado” si se le pasa cualquier otra opción.

Ejercicio 4

Crea la clase back.domain.Customer con tres propiedades privadas: id, name y nif, los getters correspondientes y el método toString().

Crea una segunda clase back.domain.CustomerService. Esta clase tendrá un método findAll() que creará un listado de clientes (List<Customer>) y lo devolverá (crea tres o cuatro clientes en el listado para probar).

Haz que el método back.controller.CustomerController.findAll() devuelva el listado de clientes generado por el método back.domain.CustomerService.findAll().

El resultado deberá ser algo similar a ésto:

Opción: 1
Clientes:[ {1, Cliente 1, A11111111}, {2, Cliente 2, B22222222}, {3, Cliente 3, C22222222}, {4, Cliente 4, D22222222} ]
1.- Listado clientes

Ejercicio 5

Crea una última clase back.persistence.CustomerDao con el método findAll(). Traslada la creación del listado de clientes (básicamente la implementación del método back.domain.CustomerService.findAll()) a este método y haz que la clase CustomerService llame al método back.persistence.CustomerDao.findAll() y devuelva el resultado.

Ejercicio 6

Implementa la funcionalidad de buscar un cliente por id siguiendo la misma filosofía que la anterior (devolver el listado de todos los clientes).