El módulo anterior terminó con una promesa: «Has escrito versiones en miniatura de casi todos ellos con tus propias manos. Ahora vas a usar los de verdad.»
Esta lección es el puente. Antes de escribir la primera anotación de Spring o la primera entidad de Hibernate, hay que entender qué es exactamente un framework, en qué se diferencia de una librería, qué te da y qué te quita, y —sobre todo— cómo funciona por dentro, porque la respuesta a esa última pregunta ya la sabes: anotaciones, reflexión, proxies dinámicos y generación de código. Exactamente lo que construiste en el módulo 10.
Esto importa por una razón muy práctica. La diferencia entre un desarrollador que usa Spring y uno que entiende Spring no está en cuántas anotaciones se sabe de memoria: está en que cuando algo falla —y va a fallar— el primero busca en un foro y el segundo razona sobre qué está haciendo el contenedor. Cuando veas que una llamada a un método @Transactional desde otro método de la misma clase no abre transacción, tú sabrás por qué: porque el proxy dinámico que escribiste en 10-03 tiene exactamente ese mismo agujero, y lo viste con tus ojos.
Además, hay una decisión profesional que nadie te enseña y que vas a tomar decenas de veces en tu carrera: añadir o no una dependencia. Es una decisión con consecuencias de seguridad, de mantenimiento y de peso. Esta lección te da criterios reales para tomarla.
Al terminar sabrás distinguir librería de framework y explicar la inversión de control con un ejemplo que escribiste tú, tendrás un mapa del ecosistema Java por categorías, sabrás cómo se evalúa una dependencia antes de meterla en tu pom.xml, entenderás qué es Maven Central y qué significan las coordenadas groupId:artifactId:version, y conocerás el riesgo de la cadena de suministro de software con un caso real que paralizó la industria durante semanas.
Contenido
- El punto de partida: BiblioTech hecho a mano
- Librería frente a framework: quién llama a quién
- La inversión del control, explicada con tu propio
ContenedorSimple - Qué aporta un framework
- Qué cuesta un framework
- Cómo se sostiene la magia: los cuatro mecanismos
- El mapa: lo que escribiste a mano frente a lo que hace el framework de verdad
- Mapa del ecosistema Java por categorías
- Cómo se elige una dependencia
- La pregunta previa: ¿lo necesito de verdad?
- Maven Central y las coordenadas
- Versionado semántico
- El coste de la seguridad de la cadena de suministro
- Log4Shell: qué pasó de verdad
- Auditar dependencias en la práctica
- Qué framework aprender primero
- Por qué este curso elige Spring Boot
- El plan del módulo 11
- Errores Comunes y Consejos
- Ejercicios
- El punto de partida: BiblioTech hecho a mano
Antes de mirar hacia adelante, mira una vez más lo que tienes. BiblioTech, al terminar el módulo 10, es un proyecto respetable: dominio sellado, repositorios genéricos, streams, java.time, hilos virtuales, un contenedor de inyección de dependencias casero y tres proxies dinámicos.
Y tiene seis deudas declaradas:
| Deuda | Estado actual | Qué falta |
|---|---|---|
| Inyección de dependencias | ContenedorSimple, 150 líneas |
Ámbitos, ciclo de vida, configuración por entorno, transacciones |
| Persistencia | Ficheros CSV | Consultas, índices, transacciones, integridad referencial |
| JSON | indexOf a mano (09-06) |
Un parseador de verdad |
| Código repetitivo | Getters, equals, hashCode, toString a mano |
Generación automática |
| Logging | java.util.logging |
Una fachada estándar del ecosistema |
| Construcción | javac a mano |
Dependencias, fases, empaquetado, reproducibilidad |
| Pruebas | Ninguna | Todo |
Cada una de esas filas es una lección de este módulo. Pero fíjate en algo: ninguna de esas deudas es de lógica de negocio. Ninguna tiene que ver con préstamos, multas, reservas o catálogos. Todas son fontanería: cosas que toda aplicación necesita y que ninguna aplicación quiere escribir.
Esa observación es, literalmente, la definición del problema que resuelven los frameworks.
- Librería frente a framework: quién llama a quién
La distinción se suele explicar mal. No es una cuestión de tamaño ni de complejidad. Hay librerías enormes y frameworks minúsculos. La diferencia es de dirección de las llamadas.
- Una librería es código que tú llamas. Tú tienes el control del flujo: decides cuándo, dónde y con qué argumentos.
java.util.Collections, Jackson o Apache Commons son librerías: llamas aCollections.sort(lista)cuando te conviene. - Un framework es código que te llama a ti. El framework tiene el control del flujo: define la estructura de la aplicación, arranca, y en determinados puntos invoca tu código. Tú rellenas huecos.
Esta inversión tiene un nombre clásico, el Principio de Hollywood: "No nos llames, ya te llamaremos nosotros."
graph LR
subgraph "LIBRERÍA"
A1["Tu código<br/>(controla el flujo)"] -->|llama| B1["Jackson<br/>Commons<br/>Guava"]
B1 -->|devuelve| A1
end
subgraph "FRAMEWORK"
A2["Spring<br/>JUnit<br/>Servlet API"] -->|llama| B2["Tu código<br/>(rellena huecos)"]
B2 -->|devuelve| A2
end
Un ejemplo concreto que ya conoces, de 09-06. Cuando escribiste el ClienteMetadatos con HttpClient:
// LIBRERÍA: tú decides cuándo llamar
HttpResponse<String> respuesta = cliente.send(peticion, BodyHandlers.ofString());
String cuerpo = respuesta.body();Tú controlas todo. HttpClient es una librería.
Y ahora, un ejemplo de framework que verás en 11-04:
¿Dónde está el main? ¿Quién llama a multaDeCincoDiasSonCincoEuros()? Tú no. Lo llama JUnit. Tú solo has escrito un método y le has puesto una etiqueta. JUnit escanea tus clases, encuentra los métodos anotados con @Test, crea una instancia por cada uno y los invoca. Eso es un framework.
Compara los dos modelos:
| Aspecto | Librería | Framework |
|---|---|---|
| Control del flujo | Tuyo | Del framework |
| Quién llama a quién | Tú → librería | Framework → tú |
| Estructura de tu código | Libre | Impuesta (convenciones, anotaciones, interfaces) |
| Coste de sustituirla | Bajo (aíslas la llamada) | Alto (impregna la arquitectura) |
| Ejemplos | Jackson, Guava, Commons, SLF4J | Spring, JUnit, Hibernate, Quarkus |
| Punto de entrada | Métodos que invocas | Hooks: anotaciones, interfaces, ficheros de configuración |
Un matiz honesto: la frontera no siempre es nítida. Spring es un framework, pero RestTemplate dentro de Spring es una librería. Hibernate es un framework cuando gestiona el ciclo de vida de tus entidades, y una librería cuando llamas a entityManager.find(...). Lo útil no es clasificar, sino preguntarse en cada punto: ¿quién controla el flujo aquí?
- La inversión del control, explicada con tu propio
ContenedorSimple
ContenedorSimpleLa inversión de control (IoC, Inversion of Control) es el principio general: ceder el control del flujo a otro componente. La inyección de dependencias (DI, Dependency Injection) es la forma concreta de IoC que más vas a usar: en vez de que un objeto cree sus propias dependencias, se las dan hechas.
Esto lo escribiste tú en 10-03. Antes del ContenedorSimple, GestorPrestamos hacía esto:
// ANTES: el objeto crea sus propias dependencias. Control NO invertido.
public class GestorPrestamos {
private final Repositorio<Prestamo> repositorio;
private final CalculadoraMultas calculadora;
private final ServicioAvisos avisos;
public GestorPrestamos() {
// El propio gestor decide QUÉ implementación usar y CÓMO construirla.
this.repositorio = new AlmacenPrestamos(Path.of("datos/prestamos.csv"));
this.calculadora = new CalculadoraMultas(Clock.systemDefaultZone());
this.avisos = new ServicioAvisos("smtp.nexussoftware.com", 587);
}
}Problemas de ese código, todos reales:
- No se puede probar. Para probar el cálculo de una multa necesitas un fichero CSV en disco y un servidor SMTP.
- No se puede cambiar de implementación. Si mañana el almacén es una base de datos, hay que editar
GestorPrestamos. - No se puede configurar por entorno. El host SMTP de desarrollo y el de producción son distintos, y están escritos a fuego.
- El reloj es el del sistema. Ese
Clock.systemDefaultZone()hace imposible probar un vencimiento sin esperar 15 días. Justo lo que 10-05 quería evitar.
Después del ContenedorSimple:
// DESPUÉS: el objeto DECLARA lo que necesita. Control invertido.
public class GestorPrestamos {
private final Repositorio<Prestamo> repositorio;
private final CalculadoraMultas calculadora;
private final ServicioAvisos avisos;
public GestorPrestamos(Repositorio<Prestamo> repositorio,
CalculadoraMultas calculadora,
ServicioAvisos avisos) {
this.repositorio = repositorio;
this.calculadora = calculadora;
this.avisos = avisos;
}
}GestorPrestamos ya no sabe de dónde salen sus colaboradores. Solo sabe qué necesita. Alguien de fuera —el contenedor— resuelve el grafo de dependencias, construye todo en el orden correcto y le pasa las piezas.
Visualmente, el cambio es este:
graph TD
subgraph "SIN inversión de control"
G1["GestorPrestamos"] -->|new| R1["AlmacenPrestamos"]
G1 -->|new| C1["CalculadoraMultas"]
G1 -->|new| A1["ServicioAvisos"]
end
subgraph "CON inversión de control"
CT["Contenedor IoC"] -->|construye| R2["AlmacenPrestamos"]
CT -->|construye| C2["CalculadoraMultas"]
CT -->|construye| A2["ServicioAvisos"]
CT -->|construye e inyecta| G2["GestorPrestamos"]
R2 -.->|inyectado| G2
C2 -.->|inyectado| G2
A2 -.->|inyectado| G2
end
Tu ContenedorSimple hacía esto: escaneaba las clases anotadas con @Componente, miraba sus constructores con reflexión, resolvía recursivamente cada parámetro, detectaba ciclos y cacheaba los singletons. Ciento cincuenta líneas.
El ApplicationContext de Spring hace exactamente lo mismo. Y unas cuantas cosas más:
| Capacidad | Tu ContenedorSimple |
ApplicationContext de Spring |
|---|---|---|
| Escanear componentes | Sí, @Componente |
Sí, @Component y estereotipos |
| Inyección por constructor | Sí | Sí (recomendada) |
| Inyección por setter y campo | No | Sí |
| Detección de ciclos | Sí, con excepción | Sí, con mensaje mucho mejor |
| Singleton | Sí, un Map |
Sí, más 5 ámbitos adicionales |
| Ámbito prototipo / petición / sesión | No | Sí |
Ciclo de vida (@PostConstruct / @PreDestroy) |
No | Sí |
| Configuración por entorno (perfiles) | No | Sí, @Profile |
| Propiedades externalizadas | No | Sí, @Value, @ConfigurationProperties |
| Resolución de ambigüedad | No (fallaba con 2 implementaciones) | Sí, @Qualifier, @Primary |
Inyectar todas las implementaciones en una List<T> |
No | Sí |
| Transacciones declarativas | No | Sí, @Transactional |
| Programación orientada a aspectos | Tres proxies escritos a mano | Sí, integrada |
| Creación perezosa | No | Sí, @Lazy |
| Eventos entre componentes | No | Sí, ApplicationEventPublisher |
| Arranque de aplicación web | No | Sí |
Ninguna de esas filas adicionales es lógica de negocio. Todas son cosas que necesitarías escribir si tu aplicación creciera. Eso es lo que compras.
- Qué aporta un framework
Cuatro cosas concretas, por orden de importancia real.
1. Código no diferenciador ya resuelto. Hay una distinción útil entre código diferenciador (el que resuelve el problema por el que existe tu empresa: en BiblioTech, las reglas de préstamos, multas y reservas) y código de fontanería (lo que necesita cualquier aplicación: arrancar, configurarse, hablar con una base de datos, atender HTTP, registrar trazas). El código de fontanería no aporta valor a nadie y sin embargo se lleva la mayor parte del tiempo cuando lo escribes tú. Un framework te devuelve ese tiempo.
2. Convenciones compartidas. Si contratas a un desarrollador Java con experiencia en Spring, entiende la estructura de tu proyecto en una tarde. Si BiblioTech usara tu ContenedorSimple casero, tardaría una semana en entender un contenedor que solo existe en tu empresa y del que no hay documentación, cursos, ni respuestas en foros. Las convenciones son un activo económico.
3. Ecosistema. Cuando eliges Spring, no eliges solo un contenedor: eliges Spring Data (persistencia), Spring Security (autenticación), Spring Cloud (sistemas distribuidos), Actuator (observabilidad) y cientos de integraciones que ya funcionan juntas. La integración ya está hecha y probada, y eso vale más que cualquier característica individual.
4. Seguridad mantenida. Cuando aparece una vulnerabilidad en el manejo de peticiones HTTP, el equipo de Spring publica un parche y tú actualizas una versión. Si esa lógica es tuya, la vulnerabilidad la descubres cuando te la explotan. Este punto se suele infravalorar y es probablemente el más importante en producción.
- Qué cuesta un framework
Un framework no es gratis. Cuatro costes, también reales.
1. Curva de aprendizaje. Spring tiene más de veinte años de superficie acumulada. Ser productivo lleva semanas; ser competente, meses. Y buena parte de ese aprendizaje no es transferible: aprender @Transactional no te enseña nada sobre bases de datos, te enseña sobre Spring.
2. Acoplamiento. Un framework impregna la arquitectura. Migrar una aplicación de Spring a Quarkus no es cambiar una dependencia: es reescribir la capa de configuración, la de arranque, la de pruebas y probablemente parte del dominio. Por eso conviene mantener el dominio limpio de anotaciones del framework siempre que sea posible — una idea que se desarrolla en 12-02.
3. Magia difícil de depurar. Cuando un @Autowired no inyecta, o una transacción no hace rollback, o una entidad se guarda sola sin que llames a save, la causa está en código que tú no escribiste, invocado por reflexión desde un punto que no aparece en tu traza. Esta es exactamente la razón de que el módulo 10 fuera antes que este: si sabes cómo funciona la magia, puedes depurarla.
4. Peso. Un jar ejecutable de Spring Boot con web y JPA ronda los 40-50 MB y arranca en 1-3 segundos. Para un servicio de larga vida es irrelevante. Para una función serverless que arranca en cada petición o un CLI que debe responder en 50 ms, es descalificador — y ahí es donde entran Quarkus, Micronaut o la compilación nativa con GraalVM que se mencionó en 10-07.
Resumido:
| Ganas | Pagas |
|---|---|
| Semanas de fontanería resuelta | Semanas de curva de aprendizaje |
| Convenciones que cualquiera entiende | Acoplamiento a una arquitectura concreta |
| Un ecosistema integrado y probado | Comportamiento implícito difícil de depurar |
| Parches de seguridad mantenidos | Superficie de ataque de terceros |
| Menos código propio que mantener | Más peso, más arranque, más memoria |
La conclusión profesional no es "los frameworks son buenos" ni "son malos": es que la decisión depende del contexto, y que quien no entiende lo que hay debajo no puede tomarla.
- Cómo se sostiene la magia: los cuatro mecanismos
Aquí es donde el módulo 10 se cobra por completo. Todo lo que un framework Java hace "por arte de magia" se apoya en cuatro mecanismos, y los cuatro los conoces.
Mecanismo 1: anotaciones (10-02)
Las anotaciones son metadatos: etiquetas que no hacen nada por sí solas. @Service no arranca nada. @Entity no crea ninguna tabla. @Test no ejecuta nada. Son marcas inertes que alguien tiene que leer.
Lo aprendiste construyendo @CampoCsv y @Auditable. Y aprendiste la condición imprescindible:
@Retention(RetentionPolicy.RUNTIME) // sin esto, la anotación NO existe en ejecución
@Target(ElementType.TYPE)
public @interface Componente { }Toda anotación de framework que se lea en ejecución lleva @Retention(RUNTIME). Ábrete el código fuente de @Service de Spring o de @Entity de Jakarta Persistence: está ahí.
Mecanismo 2: reflexión (10-03)
Alguien tiene que leer las etiquetas. Ese alguien usa reflexión: Class.forName, getDeclaredFields, isAnnotationPresent, getDeclaredConstructor().newInstance(), invoke, setAccessible(true).
Tu ExportadorAnotado recorría los campos de cualquier objeto buscando @CampoCsv. Hibernate recorre los campos de cualquier entidad buscando @Column. Es el mismo bucle.
Mecanismo 3: proxies dinámicos (10-03)
Cuando el framework necesita añadir comportamiento alrededor de tu método sin tocar tu código —transacciones, seguridad, caché, reintentos, métricas—, no modifica tu clase: la envuelve. Escribiste tres:
// Tu ProxyAuditoria de 10-03
Object proxy = Proxy.newProxyInstance(
interfaz.getClassLoader(),
new Class<?>[] { interfaz },
(p, metodo, args) -> {
if (metodo.isAnnotationPresent(Auditable.class)) {
registro.anotarInicio(metodo.getName());
}
Object resultado = metodo.invoke(objetivo, args); // llamada real
// ... post-procesado
return resultado;
});Cuando en 11-02 veas @Transactional, no verás nada nuevo: verás ese mismo InvocationHandler con iniciar transacción antes y confirmar o revertir después. Y verás también el mismo agujero: si el objetivo se llama a sí mismo internamente, la llamada no pasa por el proxy y el aspecto no se aplica. Ese detalle es la causa de un porcentaje enorme de los "mi @Transactional no funciona" del mundo.
Nota técnica: los proxies JDK que escribiste solo funcionan sobre interfaces. Cuando la clase no implementa ninguna, Spring usa CGLIB, que genera en tiempo de ejecución una subclase de tu clase y sobrescribe sus métodos. De ahí una consecuencia práctica: una clase o un método final no se puede proxiar con CGLIB, y por eso a veces tu @Transactional sobre un método final no hace absolutamente nada.
Mecanismo 4: generación de código
Hay dos momentos posibles.
- En tiempo de compilación, mediante un procesador de anotaciones (
javax.annotation.processing, visto en 10-02). Es lo que hace Lombok: no hay magia en ejecución, el.classya contiene los getters. También MapStruct y Micronaut. - En tiempo de ejecución, generando bytecode al vuelo con ASM, ByteBuddy o CGLIB. Es lo que hacen Spring, Hibernate y Mockito.
Cada opción tiene sus consecuencias:
| Mecanismo | Cuándo actúa | Coste en arranque | Depurable | Ejemplos |
|---|---|---|---|---|
| Anotaciones + reflexión | Ejecución | Medio (escaneo) | Regular | Spring, Hibernate, JUnit, Jackson |
| Proxy dinámico JDK | Ejecución | Bajo | Regular (trazas con $Proxy0) |
@Transactional, Spring Data |
| Generación de subclase (CGLIB/ByteBuddy) | Ejecución | Medio | Difícil | Spring AOP sobre clases, Mockito |
| Procesador de anotaciones | Compilación | Ninguno | Fácil (el código existe) | Lombok, MapStruct, Micronaut |
La tendencia actual, precisamente por el coste de arranque y por la compatibilidad con la compilación nativa de GraalVM, es mover trabajo de la ejecución a la compilación. Quarkus y Micronaut nacieron con esa idea, y Spring 6 la ha adoptado parcialmente con AOT.
- El mapa: lo que escribiste a mano frente a lo que hace el framework de verdad
Esta tabla es el resumen del módulo 10 leído desde el módulo 11. Es, probablemente, la tabla más importante de esta lección.
| Lo que escribiste a mano | Lección | Lo que hace el framework de verdad | Se ve en |
|---|---|---|---|
ContenedorSimple con @Componente e @Inyectar |
10-03 | ApplicationContext de Spring con @Component y @Autowired |
11-02 |
ProxyAuditoria, ProxyReintentos, ProxyCronometro |
10-03 | Spring AOP, @Transactional, @Retryable, @Cacheable |
11-02 |
ExportadorAnotado que lee @CampoCsv por reflexión |
10-03 | Hibernate leyendo @Column, Jackson leyendo @JsonProperty |
11-03, 11-07 |
ValidadorAnotado con @Validar |
10-03 | Jakarta Bean Validation (@NotNull, @Size, @Email) |
11-02 |
LectorCsv / EscritorCsv a mano |
07-07 | OpenCSV, Commons CSV — y, mejor aún, una base de datos | 11-03, 11-07 |
AlmacenPrestamos sobre ficheros |
07-06 | Hibernate / JPA sobre una base de datos relacional | 11-03 |
Repositorio<T extends Identificable> genérico |
10-01 | JpaRepository<T, ID> de Spring Data, implementado con proxies |
11-03 |
Parseo de JSON con indexOf |
09-06 | Jackson ObjectMapper |
11-07 |
Getters, equals, hashCode, toString a mano |
03-09 | Lombok (o record, que ya usas) |
11-07 |
ConfiguracionLog con java.util.logging |
06-07 | SLF4J + Logback | 11-07 |
Configuracion / ReglasNegocio con Properties |
07-07 | application.yml, @ConfigurationProperties, perfiles |
11-02 |
main que imprime y un humano que mira |
todas | JUnit 5 + AssertJ + Mockito | 11-04, 11-06 |
javac con classpath a mano |
01-02 | Maven | 11-05 |
ApagadoOrdenado con shutdown hooks |
08-05 | Ciclo de vida del contenedor, @PreDestroy |
11-02 |
Léela en las dos direcciones. De izquierda a derecha, es tu hoja de ruta. De derecha a izquierda, es la respuesta a la pregunta «¿qué está haciendo Spring aquí?».
- Mapa del ecosistema Java por categorías
El ecosistema Java es enorme y a un recién llegado le resulta abrumador. En realidad se ordena bien en ocho categorías, y en cada una hay dos o tres opciones dominantes.
Construcción y dependencias
| Herramienta | Qué es | Cuándo | Se ve en |
|---|---|---|---|
| Maven | Estándar de facto. XML declarativo, ciclo de vida fijo | Por defecto en empresa | 11-05 |
| Gradle | DSL en Groovy o Kotlin, caché e incremental | Proyectos grandes, Android | 11-05 (comparado) |
| Ant | Histórico, imperativo | Solo legado | — |
Inyección de dependencias y aplicaciones
| Framework | Qué es | Fuerte en | Se ve en |
|---|---|---|---|
| Spring / Spring Boot | El ecosistema dominante | Todo; comunidad enorme | 11-02 |
| Quarkus | Nativo en la nube, arranque en milisegundos | Contenedores, serverless | mencionado |
| Micronaut | DI resuelta en compilación | Arranque y memoria mínimos | mencionado |
| Jakarta EE | El estándar (CDI, JAX-RS, JPA) | Servidores de aplicaciones | 11-03 (JPA) |
Persistencia
| Librería | Enfoque | Cuándo | Se ve en |
|---|---|---|---|
| JPA / Hibernate | ORM: objetos ↔ tablas | Dominio rico, CRUD | 11-03 |
| Spring Data JPA | Repositorios por convención sobre JPA | Con Spring | 11-03 |
| jOOQ | SQL con tipos verificados en compilación | SQL complejo y control total | mencionado |
| MyBatis | SQL en XML o anotaciones, mapeo explícito | Consultas a medida | mencionado |
| JDBC / JdbcTemplate | Directo, sin ORM | Consultas puntuales, rendimiento | 11-03 (línea base) |
Pruebas
| Librería | Para qué | Se ve en |
|---|---|---|
| JUnit 5 | Marco de pruebas | 11-04 |
| Mockito 5 | Dobles de prueba | 11-06 |
| AssertJ | Aserciones fluidas y legibles | 11-04, 11-06 |
| Testcontainers | Base de datos y servicios reales en Docker | 11-06, 12-05 |
| WireMock | Simular APIs HTTP | mencionado |
| JaCoCo | Cobertura | 12-05 |
Serialización
| Librería | Notas | Se ve en |
|---|---|---|
| Jackson 2.x | El estándar; integrado en Spring | 11-07 |
| Gson | Google; más simple, menos potente | 11-07 (mencionado) |
| JSON-B | Estándar de Jakarta | 11-07 (mencionado) |
Logging
| Pieza | Papel | Se ve en |
|---|---|---|
| SLF4J | Fachada: la API contra la que programas | 11-07 |
| Logback | Implementación por defecto de Spring Boot | 11-07 |
| Log4j2 | Implementación alternativa, muy rápida | 11-07 (y §14) |
java.util.logging |
El del JDK; sin dependencias, limitado | 06-07 |
Utilidades
| Librería | Qué aporta | Se ve en |
|---|---|---|
| Lombok | Genera código repetitivo en compilación | 11-07 |
| Guava | Colecciones, caché, utilidades (Google) | 11-07 (panorama) |
| Apache Commons Lang / IO | Utilidades de String, ficheros, reflexión |
11-07 (panorama) |
| MapStruct | Mapeadores entidad ↔ DTO en compilación | 11-07 (panorama) |
| Caffeine | Caché en memoria de alto rendimiento | 11-07 (panorama) |
Documentación y operación
| Librería | Qué aporta | Se ve en |
|---|---|---|
| springdoc-openapi | Documentación OpenAPI/Swagger automática | 11-07, 12-04 |
| Micrometer | Métricas | 11-07, 12-07 |
| Resilience4j | Reintentos, cortocircuitos, limitadores | 11-07, 12-07 |
| Flyway / Liquibase | Migraciones de esquema versionadas | 11-03, 12-06 |
Con este mapa delante, el ecosistema deja de ser una lista infinita de nombres y pasa a ser ocho decisiones, cada una con una opción por defecto razonable.
- Cómo se elige una dependencia
Añadir una línea al pom.xml cuesta cinco segundos y compromete a tu equipo durante años. Estos son los criterios que se usan de verdad.
| Criterio | Qué mirar | Señal de alarma |
|---|---|---|
| Mantenimiento activo | Fecha de la última versión, incidencias abiertas y cerradas, ritmo de publicación | Sin versiones desde hace >2 años; incidencias sin respuesta |
| Licencia | Apache 2.0, MIT, BSD son seguras. GPL/AGPL contaminan. Revísalo siempre | GPL en producto comercial cerrado; licencia ausente |
| Adopción | Número de usos en Maven Central, estrellas, preguntas resueltas en foros | Diez usuarios en el mundo; nadie ha tenido tu problema |
| Tamaño y transitividad | ¿Cuántos jars arrastra? mvn dependency:tree |
Una utilidad de 20 KB que arrastra 30 MB |
| Alternativa en el JDK | ¿Lo hace ya java.util, java.time, java.net.http? |
Sí, y aun así añades la dependencia |
| Compatibilidad | Versión mínima de Java, jakarta.* frente a javax.* |
Requiere Java 8 y no ha migrado a jakarta |
| Seguridad | ¿Tiene CVE abiertos? ¿Publica avisos? ¿Con qué rapidez parchea? | Vulnerabilidades conocidas sin corregir |
| Documentación | ¿Hay guía de inicio, javadoc, ejemplos? | Solo un README de tres líneas |
| Coste de salida | Si mañana desaparece, ¿cuánto cuesta sustituirla? | Impregna todo el código |
Una lista de comprobación práctica antes de escribir la dependencia:
[ ] ¿Lo necesito de verdad? ¿No lo hace ya el JDK o algo que ya tengo?
[ ] ¿La licencia es compatible con mi producto?
[ ] ¿Ha publicado versión en los últimos 12 meses?
[ ] ¿Tiene CVE abiertos sin corregir?
[ ] ¿Cuántas dependencias transitivas arrastra?
[ ] ¿Es compatible con mi versión de Java y con jakarta.*?
[ ] ¿Hay una alternativa más pequeña o más estándar?
[ ] ¿Sé cómo la quitaría si hiciera falta?Si dudas en más de dos casillas, no la añadas todavía.
- La pregunta previa: ¿lo necesito de verdad?
Antes de todos los criterios anteriores hay una pregunta que se salta demasiada gente.
El JDK moderno es mucho más rico de lo que la gente recuerda. Muchísimas dependencias históricas ya no hacen falta:
| Se solía añadir | Hoy ya está en el JDK | Desde |
|---|---|---|
| Joda-Time | java.time |
Java 8 |
| Apache HttpClient (para lo básico) | java.net.http.HttpClient |
Java 11 |
| Commons IO (para lo básico) | java.nio.file.Files |
Java 7 |
Guava Lists.newArrayList |
List.of, new ArrayList<>() |
Java 9 |
Commons Lang StringUtils.isBlank |
String.isBlank() |
Java 11 |
| Una librería para leer un fichero entero | Files.readString(path) |
Java 11 |
record-like generado por Lombok |
record |
Java 16 |
| Base64 de Commons Codec | java.util.Base64 |
Java 8 |
Y el contrapeso: tampoco escribas tú lo que es notoriamente difícil de hacer bien. Criptografía, parseo de fechas con zonas horarias, parseo de JSON, parseo de CSV con comillas y saltos de línea embebidos (recuerda tu LectorCsv de 07-07 y todos los casos límite que no cubría), y cualquier cosa relacionada con seguridad. Ahí la dependencia madura es siempre mejor que tu implementación.
La regla práctica: escribe tú lo que es específico de tu negocio; delega lo que es genérico y difícil.
- Maven Central y las coordenadas
Cuando añades una dependencia, ¿de dónde sale el jar?
Maven Central es el repositorio público de artefactos Java, mantenido por Sonatype. Contiene millones de versiones de cientos de miles de librerías, y es el destino por defecto de Maven y Gradle. Cuando escribes una dependencia y ejecutas mvn package, Maven la descarga de Central y la guarda en tu repositorio local (~/.m2/repository) para no volver a bajarla.
Cada artefacto se identifica con tres coordenadas:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.1</version>
</dependency>| Coordenada | Qué es | Convención |
|---|---|---|
groupId |
La organización o proyecto | Dominio invertido: org.springframework.boot |
artifactId |
El módulo concreto | Nombre corto: spring-boot-starter-web |
version |
La versión publicada | Versionado semántico: 3.3.2 |
Se escriben abreviadas como groupId:artifactId:version, por ejemplo com.fasterxml.jackson.core:jackson-databind:2.17.1. Verás esa notación en las trazas de Maven, en los informes de seguridad y en toda la documentación.
Y la ruta física es predecible: groupId con los puntos convertidos en carpetas, luego artifactId, luego version. El jar anterior vive en:
Saber esto es útil de verdad el día que Maven te diga que no encuentra un artefacto: puedes ir a esa carpeta y ver si está, si está corrupto, o si solo hay un fichero .lastUpdated (síntoma clásico de descarga fallida que se arregla borrando la carpeta y ejecutando mvn -U).
Todo esto se desarrolla en 11-05. Aquí solo necesitas el vocabulario.
- Versionado semántico
La mayoría del ecosistema Java sigue el versionado semántico (SemVer), con el formato MAYOR.MENOR.PARCHE:
| Parte | Se incrementa cuando | Compatibilidad | Ejemplo |
|---|---|---|---|
| MAYOR | Hay cambios que rompen | Rompe | 2.7.18 → 3.0.0 |
| MENOR | Se añade funcionalidad compatible | Compatible | 3.2.0 → 3.3.0 |
| PARCHE | Se corrigen errores | Compatible | 3.3.1 → 3.3.2 |
Los sufijos habituales, de menos a más estable:
| Sufijo | Significado |
|---|---|
-SNAPSHOT |
En desarrollo, cambia sin avisar. Nunca en producción |
-M1, -M2 |
Hito (milestone), muy preliminar |
-RC1 |
Candidata a versión, casi definitiva |
| (sin sufijo) | Versión liberada (GA), inmutable para siempre |
Dos consecuencias prácticas:
- Subir de PARCHE o MENOR debería ser seguro, y es lo que haces continuamente para incorporar correcciones de seguridad.
- Subir de MAYOR requiere leer las notas de migración. El ejemplo canónico y muy actual: Spring Boot 2.x → 3.x cambió todos los paquetes
javax.*porjakarta.*y exige Java 17. Es el motivo de que en este curso todo lo de persistencia seajakarta.persistencey nojavax.persistence. Si copias un ejemplo de internet conjavax.persistence, es de Spring Boot 2 y no te va a compilar.
- El coste de la seguridad de la cadena de suministro
Aquí viene la parte incómoda.
Cuando añades una dependencia, no añades un jar: añades un árbol. Un spring-boot-starter-web trae del orden de treinta artefactos transitivos. Un proyecto empresarial típico tiene entre 100 y 400 jars de los cuales tú has escrito cero.
Cada uno de esos jars ejecuta código con los mismos permisos que tu aplicación. Puede leer tus ficheros, abrir conexiones de red y acceder a tus variables de entorno. Y ninguno lo has revisado.
Esto se llama riesgo de la cadena de suministro de software (software supply chain), y tiene tres formas:
| Riesgo | En qué consiste | Ejemplo |
|---|---|---|
| Vulnerabilidad conocida | Un fallo publicado (CVE) en una versión que tú usas | Log4Shell, Spring4Shell |
| Dependencia abandonada | Nadie parchea; la vulnerabilidad no se corregirá nunca | Librerías sin versión desde 2018 |
| Compromiso del artefacto | Alguien publica código malicioso en una librería legítima | Casos documentados en npm y PyPI |
- Log4Shell: qué pasó de verdad
En diciembre de 2021 se publicó CVE-2021-44228, apodada Log4Shell, en Apache Log4j 2, una de las librerías de logging más usadas del mundo Java.
El resumen técnico, sin dramatismo: Log4j 2 admitía en los mensajes de log una sustitución de variables, y una de las formas admitidas permitía hacer una búsqueda JNDI contra un servidor remoto. Consecuencia: si una aplicación registraba en el log un texto controlado por el atacante —una cabecera HTTP, un nombre de usuario, un User-Agent— el atacante podía provocar que el servidor descargara y ejecutara código suyo. Ejecución remota de código con una línea de texto.
Por qué fue tan grave:
- Trivial de explotar: bastaba con enviar una cadena en un campo cualquiera que acabara en un log.
- Omnipresente: Log4j 2 estaba en decenas de miles de productos, muchas veces como dependencia transitiva que el equipo ni sabía que tenía.
- Difícil de inventariar: la pregunta «¿tenemos Log4j?» resultó no ser fácil de responder en la mayoría de las organizaciones. Y esa fue la lección real.
Lo que la industria aprendió, y que a ti te afecta directamente:
- Tienes que saber qué tienes. De ahí el auge del SBOM (Software Bill of Materials), el inventario de componentes de una aplicación.
- Tienes que poder actualizar rápido. Si actualizar una dependencia requiere tres semanas de pruebas manuales porque no hay pruebas automáticas, tu ventana de exposición son tres semanas. Esto conecta directamente con el módulo 11 al completo: sin Maven no puedes cambiar una versión con una línea, y sin JUnit no puedes verificar que el cambio no rompió nada.
- Tienes que vigilar continuamente. Un proyecto seguro hoy tiene vulnerabilidades dentro de seis meses sin cambiar una línea de código, porque las vulnerabilidades se descubren en código que ya existía.
Aviso importante. Lo que se cuenta aquí es lo que un desarrollador debe saber para no tomar decisiones ingenuas. En un proyecto real, la política de dependencias, el análisis de vulnerabilidades y la respuesta ante incidentes son responsabilidad del equipo o el responsable de seguridad de la organización, con herramientas y procesos propios. Tu papel es mantener el árbol de dependencias bajo control, actualizar cuando te lo indiquen y no introducir dependencias sin criterio. Las prácticas de seguridad de la aplicación se tratan en 12-07.
- Auditar dependencias en la práctica
Tres herramientas que usarás desde 11-05.
1. Ver el árbol completo. Esta es la primera y la que más se usa:
Salida (recortada) de un proyecto Spring Boot con web:
[INFO] com.nexussoftware:bibliotech:jar:1.0.0-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.3.2:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.3.2:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:3.3.2:compile
[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.5.6:compile
[INFO] | | | | \- ch.qos.logback:logback-core:jar:1.5.6:compile
[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:2.0.13:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:3.3.2:compile
[INFO] | | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.2:compile
[INFO] | \- org.springframework.boot:spring-boot-starter-tomcat:jar:3.3.2:compileAhí ves de golpe tres cosas que este módulo va a explicar: que Spring Boot ya trae Logback y SLF4J (11-07), que ya trae Jackson (11-07) y que ya trae Tomcat embebido (12-04). Y ves que un jar que tú no declaraste, logback-core, está en tu aplicación.
Para buscar quién arrastra un artefacto concreto:
2. Buscar vulnerabilidades conocidas. El plugin de OWASP contrasta tu árbol con la base de datos pública de vulnerabilidades:
Genera un informe HTML en target/ con los CVE encontrados y su gravedad. En un proyecto real esto se ejecuta en la integración continua, no a mano.
3. Ver qué está desactualizado.
Te lista, dependencia a dependencia, qué versión usas y cuál es la última disponible. Ejecutarlo cada pocas semanas y actualizar los parches es una de las prácticas de higiene más rentables que existen.
- Qué framework aprender primero
Si has llegado hasta aquí, la lista de opciones puede paralizarte. El criterio para elegir el primero no es técnico, es de aprendizaje:
- Aprende el que más se use. No porque sea el mejor, sino porque cuando te atasques —y te vas a atascar— habrá alguien que ya se atascó igual y lo escribió. La documentación, los cursos y las respuestas son un recurso real.
- Aprende uno bien antes que tres a medias. Los conceptos —IoC, DI, ORM, AOP, contexto de persistencia— se transfieren. La sintaxis, no. Quien domina Spring aprende Quarkus en dos semanas; quien ha tocado los tres por encima no domina ninguno.
- Aprende con un proyecto real, no con ejemplos aislados. Por eso este módulo va sobre BiblioTech, y por eso hay un módulo 12 entero para integrarlo todo.
- Por qué este curso elige Spring Boot
Con honestidad, tres razones y una advertencia.
- Es lo que hay en el mercado. Una mayoría muy amplia de las ofertas de empleo de back-end Java mencionan Spring o Spring Boot. Es el conocimiento con mayor valor inmediato.
- Cubre todas las categorías del mapa. Con Spring Boot tocas DI, web, persistencia, pruebas, configuración, seguridad y observabilidad con un modelo coherente. Aprendes ocho cosas en un ecosistema en vez de en ocho.
- Su magia es exactamente la que ya entiendes. Anotaciones, reflexión, proxies dinámicos. Estás en la mejor posición posible para aprenderlo sin que sea magia.
La advertencia: Spring no es la respuesta a todo. Para un CLI pequeño es un cañón para matar moscas, y hasta 12-03 verás que una aplicación de consola bien estructurada no lo necesita. Para funciones serverless con arranque en frío, Quarkus o Micronaut son mejores. Y para un servicio que solo hace tres consultas SQL, jOOQ o JdbcTemplate pueden ser más adecuados que Hibernate. Elegir bien es parte del oficio; y para elegir hay que conocer.
- El plan del módulo 11
Este módulo tiene una estructura deliberada: cada lección salda una deuda del módulo 10 y presenta una herramienta de forma completa y autónoma. La integración en un proyecto real es el módulo 12.
graph TD
L1["11-01<br/>Introducción<br/>a los frameworks"] --> L2["11-02<br/>Spring<br/>IoC, AOP, Boot"]
L2 --> L3["11-03<br/>Hibernate<br/>JPA sobre H2"]
L3 --> L4["11-04<br/>JUnit 5<br/>las primeras pruebas"]
L4 --> L5["11-05<br/>Maven<br/>construcción"]
L5 --> L6["11-06<br/>Mockito<br/>pruebas avanzadas"]
L6 --> L7["11-07<br/>Jackson, Lombok<br/>SLF4J y ecosistema"]
L7 --> M12["Módulo 12<br/>Aplicación real"]
| Lección | Herramienta | Deuda que salda |
|---|---|---|
| 11-02 | Spring Framework y Spring Boot | El ContenedorSimple casero y la configuración por entorno |
| 11-03 | Hibernate / JPA | El CSV como persistencia |
| 11-04 | JUnit 5 | La ausencia total de pruebas |
| 11-05 | Maven | El javac a mano y las dependencias |
| 11-06 | Mockito | Probar lo que depende de red, disco y reloj |
| 11-07 | Jackson, Lombok, SLF4J | El JSON con indexOf, el código repetitivo, java.util.logging |
Al final del módulo, BiblioTech será un proyecto Maven con Spring, persistencia JPA sobre H2, pruebas con JUnit y Mockito, JSON con Jackson y logging con SLF4J. No será todavía una aplicación completa —eso es el módulo 12—, pero todas sus piezas serán las de verdad.
- Errores Comunes y Consejos
Error: creer que un framework te exime de entender lo que hay debajo. Es exactamente al revés. El framework automatiza; cuando la automatización falla, solo puede arreglarlo quien entiende el mecanismo. Si @Transactional no revierte, necesitas saber de proxies y de transacciones de base de datos.
Error: añadir dependencias sin evaluarlas. «Necesito ordenar una lista, voy a añadir Guava.» No. Mira primero el JDK, luego lo que ya tienes en el árbol, y solo después Maven Central.
Error: copiar ejemplos de internet sin mirar la versión. El síntoma inequívoco: javax.persistence en vez de jakarta.persistence, o WebSecurityConfigurerAdapter, que se eliminó. Son de Spring Boot 2 y no compilan en 3. Comprueba siempre la fecha y la versión del ejemplo.
Error: fijar versiones de dependencias que gestiona el BOM. Cuando uses spring-boot-starter-parent, no pongas <version> en las dependencias que él gestiona: le estás quitando el trabajo de garantizar que las versiones son compatibles entre sí. Se explica en 11-05.
Error: usar rangos de versiones o LATEST. Rompe la reproducibilidad: la misma etiqueta de git compila distinto según el día. Fija siempre versiones concretas.
Error: no actualizar nunca «porque funciona». Una aplicación que no se toca durante tres años acumula decenas de vulnerabilidades conocidas. Y cuando por fin hay que actualizar, el salto es tan grande que se convierte en un proyecto.
Consejo: distingue el núcleo de la fontanería. Mantén tu dominio (entidades, reglas de negocio) lo más limpio posible de anotaciones del framework. La lógica de las multas de BiblioTech debe poder probarse sin arrancar Spring. Este principio se profundiza en 12-02.
Consejo: aprende a leer un dependency:tree. Es la herramienta que más veces te va a sacar de un apuro: conflictos de versiones, jars duplicados, clases que aparecen dos veces, y la pregunta de seguridad «¿tengo esta librería?».
Consejo: cuando algo del framework te parezca magia, busca el mecanismo. Pregúntate: ¿es una anotación leída por reflexión? ¿Es un proxy? ¿Es código generado en compilación? Casi siempre es una de las tres, y entonces deja de ser magia.
Consejo: mira las dependencias de un starter antes de añadirlo. Un spring-boot-starter-web trae Tomcat, Jackson, validación y logging. Saberlo evita añadir Jackson por tu cuenta con otra versión y provocar un conflicto.
- Ejercicios
Ejercicio 1: clasificar y justificar
Para cada elemento de la lista, indica si es librería o framework, y justifica la respuesta con el criterio de "quién llama a quién". En los casos ambiguos, explica por qué lo son.
java.util.Collections- JUnit 5
- Jackson
ObjectMapper - Hibernate
- SLF4J
- Spring Boot
- El
ContenedorSimpleque escribiste en 10-03 - El
ProxyAuditoriaque escribiste en 10-03
Ejercicio 2: evaluar una dependencia real
Nexus Software necesita generar códigos de barras para las etiquetas de los libros de BiblioTech. Un compañero propone añadir una librería que encontró. Escribe el análisis que harías antes de aceptarla, usando la lista de comprobación de la lección, y decide. Datos disponibles:
groupId:artifactId—com.ejemplo:barcode-magic- Última versión:
0.4.1, publicada hace 3 años - Licencia: no consta en el POM
- Usos en Maven Central: 7 proyectos
- Dependencias transitivas: 14 jars, incluidos un motor gráfico completo y una librería XML
- Documentación: un README de 8 líneas
- Un CVE de gravedad media abierto desde hace 14 meses
Ejercicio 3: el mapa inverso
Para cada uno de estos comportamientos "mágicos" que verás en las próximas lecciones, indica cuál de los cuatro mecanismos del apartado 6 lo hace posible y qué escribiste tú en el módulo 10 que se le parece.
- Escribes
@ServicesobreGestorPrestamosy Spring lo instancia solo. - Escribes
@Transactionaly la base de datos revierte al lanzar una excepción. - Escribes
@Getterde Lombok y aparece ungetTitulo()que tú no escribiste. - Declaras
interface PrestamoRepository extends JpaRepository<Prestamo, Long>sin implementarla y funciona. - Escribes
@Testy el método se ejecuta sinmain. - Escribes
@JsonProperty("isbn_13")y Jackson usa ese nombre en el JSON.
Soluciones
Solución 1
| Elemento | Tipo | Justificación |
|---|---|---|
java.util.Collections |
Librería | Tú llamas a Collections.sort(...) cuando quieres. Control totalmente tuyo. |
| JUnit 5 | Framework | No hay main. JUnit descubre tus @Test por reflexión y los invoca. Principio de Hollywood puro. |
Jackson ObjectMapper |
Librería | Tú llamas a writeValueAsString(...). Aunque lea anotaciones tuyas, el flujo lo controlas tú. |
| Hibernate | Los dos | Es librería cuando llamas a entityManager.find(...); es framework cuando la comprobación de cambios automática detecta que modificaste una entidad gestionada y emite un UPDATE sin que tú llames a nada. Ese segundo comportamiento es control invertido (se ve en 11-03). |
| SLF4J | Librería (fachada) | Tú llamas a log.info(...). Además es una fachada: la implementación se elige en tiempo de ejecución, pero el flujo sigue siendo tuyo. |
| Spring Boot | Framework | Arranca él, escanea tus clases, las instancia, las inyecta, y llama a tu CommandLineRunner o a tu controlador cuando llega una petición. |
Tu ContenedorSimple |
Framework (en miniatura) | Escanea @Componente, decide el orden de construcción e instancia tus clases. Tú no llamas a new: lo llama él. |
Tu ProxyAuditoria |
Framework (en miniatura) | Intercepta la llamada, ejecuta código antes y después, y decide cuándo invocar tu método. El flujo pasa por él. |
La conclusión que importa: escribiste dos frameworks en miniatura sin llamarlos así.
Solución 2
Aplicando la lista de comprobación:
| Casilla | Resultado | Comentario |
|---|---|---|
| ¿Lo necesito de verdad? | Sí | Generar un código de barras correcto (checksums EAN-13, quiet zones) es difícil de hacer bien a mano. Es un caso legítimo de dependencia. |
| ¿Licencia compatible? | NO | Sin licencia declarada, por defecto no hay permiso de uso. Esto por sí solo bloquea la decisión. |
| ¿Versión en los últimos 12 meses? | NO | 3 años sin publicar. Muerta a efectos prácticos. |
| ¿CVE abiertos? | NO | Uno de gravedad media, sin corregir en 14 meses: confirma el abandono. |
| ¿Transitividad razonable? | NO | 14 jars, con un motor gráfico y una librería XML, para generar una imagen de código de barras. Desproporcionado y superficie de ataque enorme. |
| ¿Compatible con mi Java? | Desconocido | Versión 0.x: la API puede cambiar sin previo aviso, aunque el abandono lo hace irrelevante. |
| ¿Alternativa mejor? | Sí | ZXing (Apache 2.0, de Google, mantenida, muy usada) o Barcode4J son opciones maduras para lo mismo. |
| ¿Coste de salida? | Bajo | Se usaría desde un único punto, tras una interfaz propia. Es lo único positivo. |
Decisión: rechazar. Cuatro casillas críticas fallan, y la licencia sola bastaría. Contrapropuesta: usar ZXing, y hacerlo detrás de una interfaz propia (GeneradorEtiquetas), de modo que la librería concreta esté aislada en una sola clase y el coste de cambiarla en el futuro sea de una implementación. Y añadir el análisis de dependencias a la integración continua para que el próximo CVE se detecte solo.
Nota profesional: el punto de la licencia no se negocia por criterio técnico. En una empresa, un jar sin licencia declarada es un problema legal, y esa conversación la debe tener el responsable correspondiente, no el desarrollador por su cuenta.
Solución 3
| Comportamiento | Mecanismo | Tu equivalente del módulo 10 |
|---|---|---|
1. @Service instancia sola |
Anotación + reflexión. Spring escanea el classpath, encuentra la clase anotada, lee su constructor con getDeclaredConstructors(), resuelve los parámetros y llama a newInstance |
El ContenedorSimple con @Componente, exactamente el mismo bucle (10-03) |
2. @Transactional revierte |
Proxy dinámico. El objeto que hay en el contenedor no es tu clase: es un envoltorio que abre transacción, invoca tu método y confirma o revierte según haya excepción | ProxyAuditoria y ProxyReintentos: el InvocationHandler que ejecuta código antes y después de metodo.invoke(...) (10-03) |
3. @Getter de Lombok |
Generación de código en compilación (procesador de anotaciones). No hay reflexión ni proxy: el .class ya contiene el método. Puedes verlo con javap |
El procesador de anotaciones estudiado en 10-02 |
4. JpaRepository sin implementación |
Proxy dinámico sobre interfaz más análisis del nombre del método. Proxy.newProxyInstance genera una implementación al vuelo que traduce findByIsbn a una consulta |
Proxy.newProxyInstance sobre interfaces, tal cual lo escribiste (10-03) |
5. @Test se ejecuta sin main |
Anotación + reflexión. JUnit Platform descubre las clases, filtra los métodos con isAnnotationPresent(Test.class) y los invoca con Method.invoke |
El ExportadorAnotado, que recorría miembros buscando una anotación y actuaba (10-03) |
6. @JsonProperty("isbn_13") |
Anotación + reflexión. Jackson introspecciona la clase, lee la anotación del campo y usa su valor como clave del JSON | @CampoCsv("nombre") leída por el ExportadorAnotado — es literalmente el mismo diseño (10-02, 10-03) |
Si has podido completar esta tabla, el objetivo de la lección está cumplido: no queda magia, quedan cuatro mecanismos que ya conoces aplicados a escala industrial.
Conclusión
Los frameworks han dejado de ser una caja negra antes incluso de usar el primero.
Sabes distinguir librería de framework por la única pregunta que importa —quién llama a quién— y reconoces el principio de Hollywood cuando lo ves: un método @Test sin main, un @Service que se instancia solo, una entidad que se guarda sin que llames a save. Y entiendes la inversión de control no como una definición de manual sino como el cambio concreto que hiciste en GestorPrestamos: de crear sus propias dependencias con new a declararlas en el constructor y dejar que otro las resuelva — con las cuatro consecuencias que eso tuvo (se puede probar, se puede cambiar de implementación, se puede configurar por entorno y el reloj deja de ser el del sistema).
Sabes qué compras y qué pagas. Compras código no diferenciador ya resuelto, convenciones que cualquier desarrollador entiende, un ecosistema integrado y —lo más subestimado— parches de seguridad mantenidos por otros. Pagas curva de aprendizaje, acoplamiento arquitectónico, comportamiento implícito difícil de depurar y peso. Y sabes que la respuesta correcta a «¿framework sí o no?» siempre empieza por «depende del contexto».
Y sobre todo, sabes cómo se sostiene la magia: anotaciones con @Retention(RUNTIME) que son etiquetas inertes; reflexión que las lee y actúa; proxies dinámicos que envuelven tus objetos para añadir comportamiento sin tocar tu código —con el detalle, que ya te encontrarás, de que la llamada interna no pasa por el proxy y de que CGLIB no puede con lo que es final—; y generación de código, en compilación con procesadores de anotaciones o en ejecución con bytecode. Cuatro mecanismos. Los cuatro los has escrito o estudiado. Y tienes el mapa completo de lo que hiciste a mano → lo que hace el framework de verdad, que es a la vez tu hoja de ruta del módulo y tu manual de diagnóstico cuando algo falle.
Tienes el mapa del ecosistema ordenado en ocho categorías con una opción por defecto en cada una, de modo que la lista infinita de nombres se convierte en ocho decisiones. Y tienes criterio para la decisión que tomarás decenas de veces: añadir o no una dependencia. Con la pregunta previa por delante —¿no lo hace ya el JDK?, porque java.time, HttpClient, Files.readString, String.isBlank y los record han dejado obsoletas muchas dependencias clásicas— y con los criterios reales detrás: mantenimiento, licencia, adopción, transitividad, seguridad y coste de salida.
Conoces Maven Central, las coordenadas groupId:artifactId:version, la ruta predecible dentro de ~/.m2 y el versionado semántico con su consecuencia más práctica hoy: el salto de Spring Boot 2 a 3 cambió javax.* por jakarta.*, y por eso un ejemplo copiado de internet con javax.persistence sencillamente no te va a compilar.
Y entiendes el riesgo de la cadena de suministro: que un proyecto medio tiene cientos de jars que nadie ha revisado ejecutándose con tus permisos, que Log4Shell demostró que la pregunta difícil no era parchear sino saber qué tienes, y que la capacidad de actualizar rápido depende exactamente de las dos herramientas de este módulo —Maven para cambiar una versión con una línea y JUnit para verificar que no rompiste nada—. Con la advertencia clara de que, en un proyecto real, la política de dependencias y la respuesta ante vulnerabilidades corresponden al responsable de seguridad, y de que las prácticas de seguridad de la aplicación llegan en 12-07.
El plan está fijado y cada lección salda una deuda. La primera es la más grande: ese ContenedorSimple de ciento cincuenta líneas que escribiste para no llamar a new, y al que le falta absolutamente todo lo demás — ámbitos, ciclo de vida, perfiles, propiedades externalizadas, resolución de ambigüedad y transacciones.
En la próxima lección, BiblioTech tira ese contenedor a la basura y adopta el que llevan veinte años puliendo diez mil desarrolladores. Verás el ApplicationContext, las tres formas de inyección y por qué solo una es recomendable, los estereotipos, el ciclo de vida de un bean, los ámbitos, los perfiles dev y prod, las propiedades tipadas que sustituyen a tu Configuracion de 07-07, y la autoconfiguración de Spring Boot explicada de verdad — no como magia, sino como un conjunto de condiciones que puedes leer y depurar.
Y cuando llegues a @Transactional, no verás una anotación misteriosa: verás tu propio ProxyAuditoria, con veinte años de pulido encima.
Empezamos por Spring.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
- Entrada y Salida por Consola
- Tu Primer Programa Completo: BiblioTech
Módulo 2: Flujo de Control
- Sentencias Condicionales
- Bucles
- Sentencias Switch
- Break y Continue
- Depuración y Trazas de Ejecución
- Proyecto: Menú Interactivo de BiblioTech
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- La Clase Object: equals, hashCode y toString
Módulo 4: Programación Orientada a Objetos Avanzada
- Interfaces
- Clases Abstractas
- Clases Internas
- Clases Anónimas
- Expresiones Lambda
- Interfaces Funcionales y Referencias a Métodos
- Enumeraciones y Registros
Módulo 5: Estructuras de Datos y Colecciones
- Arreglos
- El Framework de Colecciones
- ArrayList
- LinkedList
- HashMap
- HashSet
- Cola y Deque
- Pila
- Ordenación y Búsqueda en Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
- Try-with-resources y AutoCloseable
- Estrategias de Manejo de Errores y Logging
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
- La API NIO.2: Path y Files
- Formatos de Intercambio: CSV y Properties
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
- Colecciones Concurrentes y Variables Atómicas
- Tareas Asíncronas con CompletableFuture
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection
- El Cliente HTTP Moderno
Módulo 10: Temas Avanzados
- Genéricos
- Anotaciones
- Reflexión
- Características de Java 8: Streams y Optional
- Fechas y Horas con java.time
- Java 9 y Más Allá
- Memoria, Recolección de Basura y Rendimiento
Módulo 11: Frameworks y Librerías de Java
- Introducción a los Frameworks de Java
- Spring Framework
- Hibernate
- JUnit
- Maven
- Pruebas Avanzadas con Mockito
- Librerías Esenciales del Ecosistema
