Spring Initializr te ha entregado una carpeta con ficheros que probablemente no has abierto todavía. Entender qué hace cada uno no es un trámite: la mayoría de los problemas desconcertantes de los primeros días —un controlador que devuelve 404, una propiedad que no se lee, una dependencia que no aparece— se explican por la estructura del proyecto y por el lugar donde has puesto una clase. En esta lección recorreremos el árbol de directorios, leeremos el pom.xml línea a línea, veremos qué es realmente un starter, entenderemos por qué el paquete raíz es crítico y decidiremos la organización de paquetes definitiva de CicloUrbana.
Contenido
- El árbol de directorios generado
src/main/javaysrc/main/resourcessrc/test/java,target/y el wrapper de Maven- Anatomía del
pom.xml, línea a línea - Qué es un starter y cuáles usaremos en el curso
- El paquete raíz y el escaneo de componentes
- Organizar el código: por capas o por funcionalidad
- La estructura de paquetes de CicloUrbana
- El ciclo de vida de Maven
- Errores Comunes y Consejos
- Ejercicios
- El árbol de directorios generado
Este es el proyecto tal y como está tras la lección anterior:
ciclourbana/
├── .mvn/
│ └── wrapper/
│ └── maven-wrapper.properties
├── mvnw ← wrapper para Linux y macOS
├── mvnw.cmd ← wrapper para Windows
├── pom.xml ← definición del proyecto Maven
├── .gitignore
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/ciclourbana/
│ │ │ ├── CicloUrbanaApplication.java
│ │ │ └── estaciones/
│ │ │ ├── Estacion.java
│ │ │ └── EstacionController.java
│ │ └── resources/
│ │ ├── application.properties
│ │ ├── static/
│ │ └── templates/
│ └── test/
│ └── java/
│ └── com/ciclourbana/
│ └── CicloUrbanaApplicationTests.java
└── target/ ← generado por Maven, no se versionaEsta disposición no la inventa Spring Boot: es el layout estándar de Maven, respetado por todo el ecosistema Java. La consecuencia práctica es que cualquier desarrollador Java sabe orientarse en tu proyecto sin explicaciones.
La separación fundamental es entre main (lo que se empaqueta y se despliega) y test (lo que se ejecuta al construir pero nunca llega al JAR).
src/main/java y src/main/resources
src/main/java y src/main/resourcessrc/main/java
Contiene todo el código fuente de producción. La estructura de carpetas debe reflejar exactamente la estructura de paquetes: la clase com.ciclourbana.estaciones.EstacionController tiene que estar en src/main/java/com/ciclourbana/estaciones/EstacionController.java. No es una convención opcional; el compilador de Java lo exige.
src/main/resources
Contiene los ficheros no compilables que deben acabar dentro del JAR. Maven los copia tal cual a target/classes, lo que significa que en tiempo de ejecución están en la raíz del classpath.
| Carpeta o fichero | Qué contiene | Módulo del curso |
|---|---|---|
application.properties |
Configuración de la aplicación | 02-04, 02-05 |
application-dev.properties |
Configuración específica de un perfil | 07-02 |
static/ |
Recursos servidos tal cual: HTML, CSS, JS, imágenes | — |
templates/ |
Plantillas de servidor (Thymeleaf) | — |
banner.txt |
Banner ASCII de arranque | 01-05 |
db/migration/ |
Scripts SQL de Flyway | 04-08 |
Dos detalles importantes:
static/: cualquier fichero que pongas aquí se sirve automáticamente desde la raíz. Unstatic/logo.pnges accesible enhttp://localhost:8080/logo.png. Esto lo hace una autoconfiguración de Spring Web.templates/: solo tiene sentido si añades un motor de plantillas. CicloUrbana es una API REST pura, así que esta carpeta quedará vacía. Puedes borrarla sin consecuencias.
application.properties
Nace vacío. Vamos a darle contenido útil desde ya:
# src/main/resources/application.properties
# Nombre de la aplicación: aparece en los logs y en Actuator
spring.application.name=ciclourbana
# Puerto del servidor embebido (8080 es el valor por defecto)
server.port=8080
# Nivel de log del propio proyecto: DEBUG durante el desarrollo
logging.level.com.ciclourbana=DEBUGCada línea es un par clave=valor. Spring Boot define cientos de claves con valores por defecto sensatos; aquí solo declaras aquello en lo que quieres apartarte de esos valores. La lección 02-05 profundiza en las propiedades, incluida la alternativa YAML.
src/test/java, target/ y el wrapper de Maven
src/test/java, target/ y el wrapper de Mavensrc/test/java
Contiene las pruebas. Initializr genera una:
package com.ciclourbana;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class CicloUrbanaApplicationTests {
@Test
void contextLoads() {
}
}Aunque el cuerpo esté vacío, esta prueba no es inútil: @SpringBootTest arranca el contexto completo de Spring. Si una dependencia falta, un bean no se puede construir o una propiedad obligatoria no existe, la prueba falla. Es una comprobación de humo muy barata que detecta errores de configuración antes de desplegar. El módulo 6 se dedica por entero a las pruebas.
Las clases de test no se incluyen en el JAR final.
target/
Es la carpeta de salida de Maven. Se regenera entera con cada construcción, por eso está en el .gitignore y nunca debe versionarse.
target/
├── classes/ ← tus .class + los recursos copiados
├── test-classes/ ← pruebas compiladas
├── ciclourbana-0.0.1-SNAPSHOT.jar ← el fat jar
├── ciclourbana-0.0.1-SNAPSHOT.jar.original ← el JAR "normal"
└── surefire-reports/ ← informes de ejecución de pruebasCuando algo se comporta de forma inexplicable, ./mvnw clean borra target y elimina los restos de compilaciones anteriores. Es el primer remedio a probar.
mvnw, mvnw.cmd y .mvn/wrapper
Ya los conoces de la lección 01-02. El contenido relevante:
# .mvn/wrapper/maven-wrapper.properties
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jarEstos tres ficheros sí se versionan en Git: son los que garantizan que cualquiera construya el proyecto con la misma versión de Maven.
- Anatomía del
pom.xml, línea a línea
pom.xml, línea a líneaEl pom.xml (Project Object Model) es el corazón del proyecto. Vamos a leerlo entero.
<?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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- (1) HERENCIA: de dónde vienen las versiones y la configuración base -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/> <!-- buscar el parent en el repositorio, no en disco -->
</parent>
<!-- (2) IDENTIDAD del proyecto: las "coordenadas" Maven -->
<groupId>com.ciclourbana</groupId>
<artifactId>ciclourbana</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ciclourbana</name>
<description>Gestión de la red de bicicletas eléctricas de Ribalta</description>
<!-- (3) PROPIEDADES: variables reutilizables -->
<properties>
<java.version>21</java.version>
</properties>
<!-- (4) DEPENDENCIAS: qué librerías necesita el proyecto -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- (5) CONSTRUCCIÓN: plugins que participan en el empaquetado -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>(1) El parent spring-boot-starter-parent
Es, con diferencia, el bloque más importante y el peor entendido. Aporta cuatro cosas:
- Gestión de versiones (
dependencyManagement). Declara la versión adecuada de más de 250 librerías —Spring, Jackson, Hibernate, Tomcat, JUnit, Mockito, Log4j...— probadas y validadas para funcionar juntas en esa versión de Boot. Por eso tus dependencias no llevan<version>: la hereda de aquí. Esto elimina de un plumazo los conflictos de versiones que atormentaban a los proyectos Java. - Configuración de plugins. Deja preconfigurados el compilador, el plugin de recursos, Surefire (pruebas) y el
spring-boot-maven-plugin. - Valores por defecto sensatos. Codificación UTF-8 en fuentes y recursos, y la versión de Java tomada de la propiedad
java.version. - Filtrado de recursos. Permite usar
@propiedad@dentro deapplication.propertiespara inyectar valores delpom.xml.
Puedes ver la lista completa de versiones gestionadas:
Ese comando muestra el POM "efectivo": el tuyo fusionado con todo lo que hereda del parent. La primera vez impresiona ver cuánto trabajo te están ahorrando esas cinco líneas.
Alternativa sin parent: si tu organización ya usa su propio POM padre corporativo, puedes importar solo la gestión de versiones mediante el BOM (Bill of Materials):
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.3.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>(2) Las coordenadas del proyecto
Toda librería Java se identifica por tres valores:
| Coordenada | Valor en CicloUrbana | Significado |
|---|---|---|
groupId |
com.ciclourbana |
La organización, en notación de dominio invertido |
artifactId |
ciclourbana |
El nombre del artefacto |
version |
0.0.1-SNAPSHOT |
La versión |
El sufijo -SNAPSHOT significa "en desarrollo, puede cambiar". Maven trata los snapshots de forma especial: los vuelve a descargar periódicamente en lugar de cachearlos para siempre. Al publicar una versión estable se quita el sufijo (1.0.0).
Las tres coordenadas determinan el nombre del JAR: ciclourbana-0.0.1-SNAPSHOT.jar.
(3) Las propiedades
java.version la lee el parent para configurar el compilador con -source 21 -target 21. Aquí puedes definir tus propias variables y usarlas con la sintaxis ${nombre}:
<properties>
<java.version>21</java.version>
<springdoc.version>2.6.0</springdoc.version>
</properties>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>Esa dependencia lleva <version> porque no está gestionada por el parent de Spring Boot: es una librería de terceros. Es la excepción que confirma la regla.
(4) Los ámbitos de las dependencias
El elemento <scope> decide cuándo está disponible una dependencia:
| Scope | Compilación | Pruebas | Ejecución | ¿En el JAR? | Ejemplo |
|---|---|---|---|---|---|
compile (por defecto) |
Sí | Sí | Sí | Sí | spring-boot-starter-web |
runtime |
No | Sí | Sí | Sí | Driver JDBC de PostgreSQL |
test |
No | Sí | No | No | spring-boot-starter-test |
provided |
Sí | Sí | No | No | API de Servlet en un WAR |
spring-boot-devtools combina runtime con <optional>true</optional>: no se compila contra ella, no se propaga a proyectos que dependan del tuyo y Spring Boot la desactiva al detectar que se ejecuta desde un fat jar.
(5) El spring-boot-maven-plugin
Es el plugin que convierte un JAR normal en el fat jar ejecutable. Aporta:
- El objetivo
repackage, enlazado a la fasepackage, que reempaqueta el JAR conBOOT-INF/y elJarLauncher. - El objetivo
spring-boot:run, que arranca la aplicación sin empaquetar. - El objetivo
build-image, que construye una imagen Docker sin escribir un Dockerfile (módulo 7).
Sin este plugin, ./mvnw package produciría un JAR de 12 KB inservible por sí solo.
- Qué es un starter y cuáles usaremos en el curso
Un starter es una dependencia Maven sin código propio: solo un pom.xml que declara un conjunto coherente de dependencias. Su valor es que alguien ya ha decidido por ti qué librerías necesitas y en qué versiones.
Compruébalo:
Salida abreviada:
[INFO] com.ciclourbana:ciclourbana:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.3.5:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.3.5:compile
[INFO] | | +- org.springframework.boot:spring-boot:jar:3.3.5:compile
[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.3.5:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:3.3.5:compile
[INFO] | | \- org.yaml:snakeyaml:jar:2.2:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:3.3.5:compile
[INFO] | | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.2:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.3.5:compile
[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.31:compile
[INFO] | +- org.springframework:spring-web:jar:6.1.14:compile
[INFO] | \- org.springframework:spring-webmvc:jar:6.1.14:compileUna línea en tu pom.xml se ha convertido en más de treinta artefactos coordinados.
Fíjate en spring-boot-starter: es el starter base del que dependen todos los demás. Aporta el núcleo, la autoconfiguración y el sistema de logs. Siempre está presente aunque no lo declares.
Estos son los starters que irán apareciendo en CicloUrbana:
| Starter | Qué aporta | Módulo |
|---|---|---|
spring-boot-starter-web |
Spring MVC, Jackson, Tomcat embebido, validación | 1 y 3 |
spring-boot-devtools |
Reinicio automático, LiveReload | 1 |
spring-boot-starter-test |
JUnit 5, Mockito, AssertJ, Spring Test | 1 y 6 |
spring-boot-starter-validation |
Bean Validation con Hibernate Validator | 3 |
spring-boot-starter-data-jpa |
Spring Data JPA, Hibernate, HikariCP | 4 |
spring-boot-starter-security |
Spring Security, filtros, cifrado de contraseñas | 5 |
spring-boot-starter-actuator |
Salud, métricas, endpoints de gestión | 7 y 9 |
spring-boot-starter-aop |
Programación orientada a aspectos | 9 |
Existen también starters de terceros, que por convención invierten el orden del nombre: los oficiales son spring-boot-starter-* y los de terceros <nombre>-spring-boot-starter (por ejemplo mybatis-spring-boot-starter).
- El paquete raíz y el escaneo de componentes
Aquí está la causa de uno de los errores más frustrantes del principiante.
La anotación @SpringBootApplication incluye @ComponentScan, que le dice a Spring: "busca clases anotadas con @Component, @Service, @Repository, @Controller o @RestController a partir del paquete de esta clase y hacia abajo".
CicloUrbanaApplication está en com.ciclourbana, así que Spring escanea com.ciclourbana y todos sus subpaquetes. Lo que quede fuera es invisible.
flowchart TD
A["com.ciclourbana<br/>CicloUrbanaApplication"] --> B["com.ciclourbana.estaciones ✅"]
A --> C["com.ciclourbana.bicicletas ✅"]
A --> D["com.ciclourbana.alquileres ✅"]
A --> E["com.ciclourbana.comun ✅"]
F["com.empresa.utilidades ❌<br/>fuera del paquete raíz:<br/>Spring NO lo escanea"]
style F fill:#ffe0e0,stroke:#c00
El síntoma típico es un 404 en un endpoint cuyo código parece impecable: el controlador existe pero Spring nunca lo registró, porque estaba fuera del árbol escaneado.
Reglas prácticas:
- Coloca siempre
CicloUrbanaApplicationen el paquete raíz, por encima de todos los paquetes funcionales. - No la pongas en el paquete por defecto (sin
package). Spring escanearía el classpath entero, lo que dispara el tiempo de arranque y provoca errores impredecibles. Spring Boot avisa explícitamente de esto. - Si necesitas escanear un paquete externo —una librería compartida de tu empresa—, amplía el escaneo:
@SpringBootApplication(scanBasePackages = {"com.ciclourbana", "com.ribalta.comun"})
public class CicloUrbanaApplication {
public static void main(String[] args) {
SpringApplication.run(CicloUrbanaApplication.class, args);
}
}Esta es la razón concreta de por qué "la clase principal va en el paquete raíz" no es una manía estética, sino un requisito de funcionamiento.
- Organizar el código: por capas o por funcionalidad
Hay dos formas de estructurar los paquetes, y la elección condiciona el mantenimiento del proyecto durante años.
Por capas (layer-based)
com.ciclourbana
├── controller/
│ ├── EstacionController.java
│ ├── BicicletaController.java
│ └── AlquilerController.java
├── service/
│ ├── EstacionService.java
│ └── AlquilerService.java
├── repository/
│ ├── EstacionRepository.java
│ └── AlquilerRepository.java
└── model/
├── Estacion.java
└── Alquiler.javaPor funcionalidad (feature-based o package by feature)
com.ciclourbana
├── estaciones/
│ ├── EstacionController.java
│ ├── EstacionService.java
│ ├── EstacionRepository.java
│ └── Estacion.java
├── alquileres/
│ ├── AlquilerController.java
│ ├── AlquilerService.java
│ ├── AlquilerRepository.java
│ └── Alquiler.java
└── comun/Comparativa
| Criterio | Por capas | Por funcionalidad |
|---|---|---|
| Encontrar todo lo de "alquileres" | Hay que abrir 4 paquetes | Está en un solo paquete |
| Cohesión | Baja: el paquete service mezcla dominios sin relación |
Alta: cada paquete es un tema |
| Encapsulación | Nula: todo debe ser public para cruzar capas |
Se puede usar visibilidad de paquete |
| Escalabilidad | Los paquetes crecen sin límite | Crece el número de paquetes, cada uno acotado |
| Extraer un microservicio | Difícil: hay que rebuscar en todas las capas | Fácil: te llevas el paquete entero |
| Familiaridad | Muy extendida en tutoriales | Recomendada por la comunidad para proyectos reales |
CicloUrbana usará organización por funcionalidad. La razón decisiva es la última fila de la tabla: en el módulo 7 hablaremos de microservicios, y con esta estructura extraer "alquileres" a un servicio independiente es casi copiar una carpeta.
- La estructura de paquetes de CicloUrbana
Esta es la organización definitiva que el proyecto irá completando módulo a módulo:
flowchart TD
R["com.ciclourbana<br/>CicloUrbanaApplication"]
R --> EST["estaciones<br/>Estacion, EstacionController<br/>EstacionService, EstacionRepository"]
R --> BIC["bicicletas<br/>Bicicleta, EstadoBicicleta<br/>BicicletaController, BicicletaService"]
R --> ALQ["alquileres<br/>Alquiler, Tarifa, Incidencia<br/>AlquilerController, AlquilerService"]
R --> USU["usuarios<br/>Usuario, Rol<br/>UsuarioController, UsuarioService"]
R --> SEG["seguridad<br/>ConfiguracionSeguridad<br/>FiltroJwt, ServicioTokens"]
R --> COM["comun<br/>Excepciones, manejador global<br/>utilidades compartidas"]
ALQ -.usa.-> BIC
ALQ -.usa.-> EST
ALQ -.usa.-> USU
SEG -.usa.-> USU
Descripción de cada paquete y en qué módulo se llenará:
| Paquete | Contenido previsto | Se construye en |
|---|---|---|
estaciones |
Estaciones de anclaje, capacidad, ubicación | Módulos 1, 3 y 4 |
bicicletas |
Bicicletas eléctricas, estado y batería | Módulos 3 y 4 |
alquileres |
Alquileres, tarifas e incidencias | Módulos 3, 4 y 9 |
usuarios |
Usuarios de la plataforma y sus roles | Módulos 4 y 5 |
seguridad |
Configuración de Spring Security, filtros JWT | Módulo 5 |
comun |
Excepciones propias, manejador global de errores, utilidades | Módulo 3 en adelante |
Las flechas punteadas del diagrama muestran las dependencias legítimas entre paquetes. Una regla que conviene respetar desde el principio: el paquete comun no debe depender de ningún paquete funcional. Si comun importa algo de alquileres, deja de ser común y aparecen dependencias circulares difíciles de deshacer.
De momento solo existe estaciones, con Estacion y EstacionController. Es lo correcto: los paquetes se crean cuando hay algo que meter dentro, no antes.
- El ciclo de vida de Maven
Maven organiza la construcción en fases ordenadas. Al invocar una fase se ejecutan todas las anteriores.
| Comando | Qué hace | Cuándo usarlo |
|---|---|---|
./mvnw clean |
Borra la carpeta target |
Cuando sospechas de restos de compilaciones previas |
./mvnw compile |
Compila src/main/java a target/classes |
Comprobar rápidamente que compila |
./mvnw test |
Compila y ejecuta las pruebas de src/test/java |
Antes de cada commit |
./mvnw package |
Todo lo anterior + genera el fat jar en target |
Para obtener el artefacto desplegable |
./mvnw install |
Todo lo anterior + copia el JAR a ~/.m2/repository |
Cuando otro proyecto local depende de este |
./mvnw verify |
Todo lo anterior + comprobaciones de calidad | En integración continua |
flowchart LR
A["validate"] --> B["compile"] --> C["test"] --> D["package"] --> E["verify"] --> F["install"] --> G["deploy"]
H["clean"] -.independiente.-> A
clean pertenece a un ciclo distinto, por eso se combina explícitamente:
# La combinación más habitual: construcción limpia y completa
./mvnw clean package
# Saltar las pruebas (útil puntualmente, peligroso como costumbre)
./mvnw clean package -DskipTests
# Ejecutar solo una clase de prueba
./mvnw test -Dtest=CicloUrbanaApplicationTests
# Modo offline: usar solo la caché local
./mvnw -o clean packageSobre -DskipTests: compila las pruebas pero no las ejecuta. Existe también -Dmaven.test.skip=true, que ni siquiera las compila y por tanto oculta errores de compilación en el código de test. Prefiere el primero.
Errores Comunes y Consejos
- Poner el controlador fuera del paquete raíz. Es la causa número uno de "mi endpoint devuelve 404 y no entiendo por qué". Comprueba siempre que el paquete de la clase empieza por
com.ciclourbana. - Añadir
<version>a dependencias que gestiona el parent. Rompes la coherencia del conjunto y puedes provocarNoSuchMethodErroren ejecución, un error especialmente difícil de diagnosticar. - Versionar la carpeta
target. Ensucia el repositorio con megabytes de artefactos regenerables. El.gitignorede Initializr ya la excluye; no lo toques. - Confundir
src/main/resourcesconsrc/main/java. Unapplication.propertiescolocado ensrc/main/javano se copia al classpath y simplemente se ignora, sin ningún aviso. - No versionar
mvnwy.mvn/. Al clonar, nadie podría construir sin instalar Maven. Deben estar en Git. - Consejo — inspecciona el árbol de dependencias.
./mvnw dependency:treeresponde a "¿de dónde sale esta librería?" y a los conflictos de versiones. Es la herramienta de diagnóstico más útil de Maven. - Consejo — usa
help:effective-pomuna vez. Ver el POM efectivo aclara de golpe qué está haciendo el parent por ti. - Consejo — un paquete por concepto de negocio, no por tecnología. Si te encuentras creando un paquete
utilsque crece sin control, es señal de que falta identificar un concepto de dominio.
Ejercicios
Ejercicio 1
Ejecuta ./mvnw dependency:tree en tu proyecto y responde: ¿cuántos artefactos arrastra spring-boot-starter-web? ¿Qué versión de Tomcat embebido se está usando? ¿De qué starter viene Jackson?
Ejercicio 2
Crea deliberadamente el error del paquete raíz: mueve EstacionController al paquete com.otraempresa.web, arranca la aplicación y comprueba qué ocurre. Después arréglalo de dos formas distintas y explica cuál es preferible.
Ejercicio 3
Prepara la estructura de paquetes de CicloUrbana creando los paquetes vacíos previstos y un fichero package-info.java en cada uno que documente su responsabilidad. Justifica por qué comun no debe depender de ningún otro paquete.
Soluciones
Solución 1
Respuestas típicas con Spring Boot 3.3.5:
- Número de artefactos: alrededor de 30 dependencias transitivas. Puedes contarlas con:
- Tomcat embebido:
org.apache.tomcat.embed:tomcat-embed-core:10.1.31. Es Tomcat 10.1, la primera rama que usa el espacio de nombresjakarta.*, coherente con Spring Boot 3. - Jackson: llega a través de
spring-boot-starter-json, que a su vez es dependencia despring-boot-starter-web. La cadena es:
Esto explica que el endpoint de la lección anterior devolviera JSON sin que añadieras ninguna dependencia: venía incluida.
Solución 2
Al mover la clase:
package com.otraempresa.web; // fuera del árbol de com.ciclourbana
@RestController
@RequestMapping("/api/v1/estaciones")
public class EstacionController { /* ... */ }La aplicación arranca sin ningún error —esto es lo desconcertante— pero:
El controlador no se ha registrado porque el escaneo de componentes nunca visitó com.otraempresa.web.
Arreglo 1: ampliar el escaneo.
@SpringBootApplication(scanBasePackages = {"com.ciclourbana", "com.otraempresa.web"})
public class CicloUrbanaApplication { /* ... */ }Arreglo 2: devolver la clase a su sitio.
El segundo es claramente preferible. El primero funciona, pero introduce una excepción a la convención que hay que recordar y documentar; con el tiempo aparecen más paquetes sueltos y el escaneo se vuelve imprevisible. La ampliación de scanBasePackages está justificada solo cuando se integra una librería externa cuyo paquete no puedes cambiar.
Solución 3
Un ejemplo de package-info.java, un fichero especial de Java cuya única función es documentar un paquete:
/**
* Gestión de las estaciones de anclaje de la red de Ribalta.
*
* <p>Contiene el modelo de estación, su controlador REST bajo
* {@code /api/v1/estaciones}, la lógica de negocio asociada y,
* a partir del módulo 4, su repositorio de persistencia.</p>
*
* <p>Este paquete puede depender de {@code comun}, pero no de
* {@code alquileres} ni de {@code seguridad}.</p>
*/
package com.ciclourbana.estaciones;Y el del paquete común:
/**
* Código transversal compartido por el resto de paquetes:
* excepciones de negocio, manejador global de errores,
* utilidades de fecha y constantes de la API.
*
* <p>REGLA: este paquete NO debe importar nada de
* {@code estaciones}, {@code bicicletas}, {@code alquileres},
* {@code usuarios} ni {@code seguridad}.</p>
*/
package com.ciclourbana.comun;Justificación de la regla: comun es la base sobre la que se apoyan los demás paquetes. Si dependiera de alquileres, se crearía un ciclo (alquileres → comun → alquileres) con tres consecuencias graves: sería imposible razonar sobre el orden de inicialización, no se podría extraer comun a una librería reutilizable, y cualquier cambio en alquileres obligaría a recompilar y volver a probar prácticamente todo el proyecto. Las dependencias deben fluir siempre de lo específico a lo general, nunca al revés.
Conclusión
Ya no hay ficheros misteriosos en el proyecto. Sabes qué hay en src/main/java, src/main/resources, src/test/java y target; has leído el pom.xml entero y entiendes que el parent es quien gestiona las versiones de más de doscientas librerías, que un starter es una lista curada de dependencias sin código propio, y que el spring-boot-maven-plugin es quien fabrica el fat jar. Sobre todo, sabes por qué CicloUrbanaApplication debe vivir en el paquete raíz: el escaneo de componentes parte de ahí, y lo que quede fuera será invisible para Spring. Y has fijado la estructura por funcionalidad —estaciones, bicicletas, alquileres, usuarios, seguridad, comun— que acompañará al proyecto hasta el módulo 10.
En la siguiente lección, El Arranque y el Ciclo de Vida de la Aplicación, entraremos en SpringApplication.run(...) para ver paso a paso qué ocurre entre que pulsas Run y aparece "Started CicloUrbanaApplication": la creación del contexto, el escaneo, la autoconfiguración, el arranque de Tomcat y los eventos que puedes aprovechar para ejecutar tu propio código en el momento justo.
Curso de Spring Boot
Módulo 1: Introducción a Spring Boot
- ¿Qué es Spring Boot?
- Configuración de tu Entorno de Desarrollo
- Creando tu Primera Aplicación Spring Boot
- Entendiendo la Estructura del Proyecto
- El Arranque y el Ciclo de Vida de la Aplicación
Módulo 2: Conceptos Básicos de Spring Boot
- Anotaciones de Spring Boot
- Inyección de Dependencias en Spring Boot
- Ámbito y Ciclo de Vida de los Beans
- Configuración de Spring Boot
- Propiedades de Spring Boot
- Autoconfiguración y Starters por Dentro
Módulo 3: Construyendo Servicios Web RESTful
- Introducción a los Servicios Web RESTful
- Creando Controladores REST
- Manejo de Métodos HTTP
- Validación de Datos de Entrada
- DTOs y Mapeo entre Capas
- Manejo de Excepciones en REST
- Documentar la API con OpenAPI
Módulo 4: Acceso a Datos con Spring Boot
- Introducción a Spring Data JPA
- Configuración de Fuentes de Datos
- Creación de Entidades JPA
- Relaciones entre Entidades
- Uso de Repositorios de Spring Data
- Métodos de Consulta en Spring Data JPA
- Transacciones y Gestión de la Persistencia
- Migraciones de Esquema con Flyway
Módulo 5: Seguridad en Spring Boot
- Introducción a Spring Security
- Configuración de Spring Security
- Autenticación y Autorización de Usuarios
- Implementación de Autenticación JWT
- Seguridad a Nivel de Método y Endurecimiento de la API
Módulo 6: Pruebas en Spring Boot
- Introducción a las Pruebas
- Pruebas Unitarias con JUnit
- Simulación con Mockito
- Pruebas de Integración
- Pruebas con Testcontainers
Módulo 7: Funciones Avanzadas de Spring Boot
- Spring Boot Actuator
- Perfiles de Spring Boot
- Tareas Programadas y Ejecución Asíncrona
- Spring Boot con Docker
- Spring Boot y Microservicios
- Comunicación entre Servicios y Tolerancia a Fallos
Módulo 8: Despliegue de Aplicaciones Spring Boot
- Introducción al Despliegue
- Desplegando en Heroku
- Desplegando en AWS
- Desplegando en Kubernetes
- Integración y Entrega Continua
Módulo 9: Rendimiento y Monitoreo
- Ajuste de Rendimiento
- Caché con Spring Cache
- Monitoreo con Spring Boot Actuator
- Uso de Prometheus y Grafana
- Gestión de Registros y Logs
- Trazabilidad Distribuida
