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;
- 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:
- Librería estándar de Java (JDK): Incluida en el propio entorno de desarrollo (paquetes como
java.lang,java.util,java.io,java.netojava.math).
- Librerías de terceros (third-party): Desarrolladas por la comunidad o por empresas externas para resolver problemas específicos (procesamiento de JSON, persistencia de datos, pruebas unitarias, etc.).
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:
- Los archivos de bytecode compilado (
.class).
- Los recursos asociados (archivos de propiedades, esquemas XML, imágenes).
- Un archivo de metadatos obligatorio llamado
META-INF/MANIFEST.MF, que indica la versión de la librería, el compilador utilizado y, opcionalmente, la clase principal si se trata de un ejecutable.
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:
- Dependencias transitivas: Si una librería A necesita de la librería B para funcionar, la herramienta descarga automáticamente ambas sin intervención manual.
- Control de versiones dinámico: Cambiar la versión de una librería solo requiere modificar una línea en el archivo de configuración.
- Estandarización del entorno: Todo el equipo de desarrollo compila y construye el proyecto exactamente de la misma forma, independientemente del IDE o sistema operativo utilizado.
En el ecosistema Java conviven principalmente tres gestores de proyectos:
- Ant: El gestor histórico. Basado en archivos XML, no impone ninguna estructura ni convención previa. El desarrollador debe programar manualmente cada tarea de compilación.
- Maven: Evolución basada en el principio de Convención sobre Configuración. Impone una estructura de directorios estándar y automatiza el ciclo de vida del proyecto mediante repositorios centralizados.
- Gradle: Herramienta moderna que utiliza un lenguaje específico de dominio (DSL basado en Groovy o Kotlin) en lugar de XML. Muy extendido en entornos multiproyecto y desarrollo Android.
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: Identificador de la organización o paquete base (ej: es.cesguiro).
- artifactId: Nombre único del proyecto o módulo (ej: ejemplo-maven).
- version: Versión actual de desarrollo o despliegue (ej: 0.0.1-SNAPSHOT).
<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:
compile(valor por defecto si se omite): La dependencia está disponible en la compilación, ejecución y empaquetado del proyecto.
test: La librería solo se compila y ejecuta durante la fase de pruebas. No se incluirá en el archivo JAR final distribuido a producción.
provided: Se utiliza cuando se necesita la librería para compilar, pero no para empaquetar porque el entorno donde se ejecutará (servidor de aplicaciones como Tomcat) ya la incluye.
runtime: La dependencia no es necesaria para compilar, pero sí para la ejecución de la aplicación (común en controladores de bases de datos JDBC).
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:
- Repositorio Local: Ubicado por defecto en
<USER_HOME>/.m2/repository. Es la caché local de tu máquina.
- Repositorio Central (mvnrepository): Servidor público remoto administrado por Apache que aloja millones de librerías de código abierto.
- Repositorios Remotos Privados: Servidores empresariales (como Nexus) para alojar artefactos internos de una compañía.
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:
- validate: Comprueba que el proyecto es correcto y que toda la información necesaria está disponible.
- compile: Compila el código fuente del proyecto (
src/main/java) y deposita los.classentarget/classes.
- test: Compila y ejecuta las pruebas unitarias (
src/test/java).
- package: Toma el código compilado y lo empaqueta en su formato de distribución (como un archivo
.jar).
- verify: Ejecuta pruebas de integración y comprueba que el paquete cumple los criterios de calidad.
- install: Copia el archivo
.jargenerado en el repositorio local (~/.m2/repository), permitiendo que otros proyectos locales puedan usarlo como dependencia.
- deploy: Copia el paquete final a un repositorio remoto compartido para distribuirlo con otros desarrolladores.
Además, el ciclo clean cuenta con la fase:
- clean: Borra el directorio de salida (
target/) para garantizar una compilación limpia desde cero.
Las órdenes de construcción se invocan desde la interfaz de línea de comandos indicando las fases a ejecutar:
mvn clean install
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):
- back
- front
En el paquete front crea dos clases:
- Menu: Esta clase tendrá un solo método (show()), que mostrará por pantalla el menú principal de la aplicación:
1.- Listado clientes 2.- Buscar cliente 0.- Salir ---------------------- Opción:
- App: Será nuestra clase principal. El método main() mostrará el menú anterior hasta que se introduzca el 0. Después de leer la opción escogida por el usuario, llamará a otro método (request()) que, por ahora, mostrará la frase “haciendo petición al servidor…”.
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().
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).