# Día 10: Inyección de Dependencias Manual: El Nacimiento de Spring

> El Operador new como Acoplamiento, Inversión de Control (IoC) y Ensamblado en el main()

- Plan: [JavaMastery](https://aprende-java.webs-economicas.es/), día 10 de 30
- Fase 2: De Vanilla a la Necesidad de Herramientas
- Dedicación: 3.5h
- Profundidad: para dominar
- Revisado: 2026-10-29
- Versión interactiva: https://aprende-java.webs-economicas.es/dias/inyeccion-de-dependencias-manual/

## Al terminar este día sabrás

- Reconocer el acoplamiento que introduce `new` dentro de una clase de negocio.
- Aplicar inversión de control e inyección por constructor sin ningún framework.
- Construir a mano el grafo de objetos de una aplicación en su punto de entrada.
- Diseñar contra interfaces para poder sustituir implementaciones en tests.


## Conceptos

### 1. El Operador `new` como Fuente de Acoplamiento Rígido

Cuando una clase instancia a sus colaboradoras con `new`, queda atada a esa implementación concreta y no puede sustituirse sin modificar el código fuente.

**Cómo funciona por dentro.**

Cuando una clase hace `new RepositorioPostgres()` dentro de sí misma, decide tres cosas que no le corresponden: qué implementación concreta usar, cómo construirla (con qué URL, usuario, pool) y cuánto vive ese objeto. Cambiar cualquiera de esas decisiones obliga a modificar la clase, y probarla sin una base de datos real se vuelve imposible.

Ese es el acoplamiento rígido: la clase depende de un detalle concreto en lugar de una capacidad. Se nota en los tests (no puedes sustituir el colaborador), en la configuración (las credenciales acaban dentro del código) y en los cambios (pasar de PostgreSQL a otro almacenamiento toca la lógica de negocio).

`new` no es malo en sí. Crear objetos de valor, DTOs, colecciones o entidades con `new` es perfectamente normal. El problema es usarlo para obtener servicios o recursos externos.

*Antes: la clase fabrica sus dependencias*

```java
public class TareaService {
    // La clase decide la implementación, su configuración y su ciclo de vida
    private final TareaRepositoryJdbc repo = new TareaRepositoryJdbc(
        "jdbc:postgresql://localhost:5432/taskflow", "admin", "secreto");
    private final EmailSender email = new SmtpEmailSender("smtp.acme.com");

    public void completar(long id) {
        var tarea = repo.buscar(id).orElseThrow();
        tarea.completar(Clock.systemUTC());     // otra dependencia oculta: el reloj
        repo.guardar(tarea);
        email.enviar(tarea.responsable(), "Tarea completada");
    }
}
// Para probar completar() necesitas PostgreSQL y un servidor SMTP reales.
```

- **En una línea:** Reemplazar el `new` por un parámetro en el constructor: `public TareaServicio(TareaRepositorio repo)`.
- **Por qué importa:** Impide probar la clase en aislamiento y dificulta cambiar de infraestructura (e.g. cambiar base de datos).
- **Error típico:** Hacer `this.repositorio = new TareaJdbcRepositorio()` dentro del constructor del servicio.
- **Java 17 frente a 21:** Universal en todas las versiones de Java.
- **Se conecta con:** Principios SOLID, Inversión de Dependencias, Testing

### 2. Inversión de Control (IoC): "No me llames, yo te llamo"

Principio en el cual el flujo de control se invierte: la clase no busca ni crea sus dependencias; alguien externo se las proporciona.

**Cómo funciona por dentro.**

Inversión de control significa que tu código deja de dirigir el flujo y la creación de objetos, y otra pieza lo hace por él. Es el principio de Hollywood: «no nos llames, ya te llamaremos». Lo usas a diario sin darte cuenta: un `Comparator` que le pasas a `sort`, un listener de un botón o un método `@Test` que JUnit invoca.

La inyección de dependencias es una forma concreta de inversión de control aplicada a la creación de objetos: la clase declara qué necesita y alguien externo se lo entrega ya construido. Ese «alguien» puede ser tu método `main` o un contenedor como Spring.

La diferencia con un framework y una librería está precisamente aquí: tú llamas a una librería; un framework te llama a ti. Spring es un framework porque es él quien crea tus objetos y decide cuándo invocar tus métodos.

*Inversión de control en código que ya conoces*

```java
// Tú no recorres la lista comparando: le das a sort() la regla y él te llama
tareas.sort(Comparator.comparing(Tarea::prioridad).reversed());

// Un "mini framework": controla el flujo y llama a tus pasos
abstract class Importador<T> {
    public final void importar(Path fichero) throws IOException {   // el flujo es suyo
        try (var lineas = Files.lines(fichero)) {
            lineas.skip(1)
                  .map(this::parsear)                 // te llama a ti
                  .filter(this::esValido)             // y aquí
                  .forEach(this::guardar);            // y aquí
        }
    }
    protected abstract T parsear(String linea);
    protected boolean esValido(T elemento) { return true; }
    protected abstract void guardar(T elemento);
}
```

- **En una línea:** `Pasar un repositorio mock en tests unitarios y el repositorio real en producción.`
- **Por qué importa:** Convierte el código en piezas modulares y fácilmente intercambiables.
- **Error típico:** Confundir Inversión de Control (principio general) con Inyección de Dependencias (técnica concreta).
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** Inyección de Dependencias, Spring Framework, Desacoplamiento

### 3. Inyección por Constructor e Inmutabilidad con `final`

Declarar los colaboradores como campos `private final` y recibirlos en el constructor.

**Cómo funciona por dentro.**

Con inyección por constructor, todas las dependencias obligatorias aparecen en la firma del constructor y se guardan en campos `final`. Eso garantiza tres cosas: el objeto nunca existe a medio configurar, sus dependencias no cambian después de construirlo (lo que lo hace seguro entre hilos) y cualquiera que lea la clase ve de un vistazo qué necesita.

Es también una alarma de diseño gratuita. Si un constructor necesita siete u ocho parámetros, la clase tiene demasiadas responsabilidades y conviene partirla. Con inyección por campo ese síntoma queda escondido.

Para dependencias opcionales, en lugar de setters suele ser mejor un valor por defecto razonable (por ejemplo, un `Clock` que por defecto es `Clock.systemUTC()`) con un segundo constructor para tests.

*Después: la clase declara lo que necesita*

```java
public class TareaService {
    private final TareaRepository repo;
    private final Notificador notificador;
    private final Clock reloj;

    public TareaService(TareaRepository repo, Notificador notificador, Clock reloj) {
        this.repo = Objects.requireNonNull(repo);
        this.notificador = Objects.requireNonNull(notificador);
        this.reloj = Objects.requireNonNull(reloj);
    }

    public void completar(long id) {
        var tarea = repo.buscar(id).orElseThrow(() -> new TareaNoEncontradaException(id));
        tarea.completar(reloj);
        repo.guardar(tarea);
        notificador.avisar(tarea.responsable(), "Tarea completada: " + tarea.titulo());
    }
}
```

- **En una línea:** `private final Repositorio repo; public Servicio(Repositorio repo) { this.repo = repo; }`
- **Por qué importa:** Garantiza que la clase nunca nazca en un estado incompleto y sea intrínsecamente segura ante hilos concurrentes.
- **Error típico:** Usar inyección por setter o por campo privado con `@Autowired`, permitiendo que el objeto se quede sin dependencias.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** Thread-Safety, Clean Code, Spring Idiomático

### 4. El Árbol de Dependencias y el Método main()

En una app sin framework, el método `main()` actúa como el contenedor manual: crea los repositorios, luego los servicios pasándoles los repositorios, y luego los controladores.

**Cómo funciona por dentro.**

Si ninguna clase crea sus propias dependencias, alguien tiene que hacerlo. Ese lugar se llama raíz de composición (composition root) y debe ser único y estar lo más cerca posible del punto de entrada: normalmente el método `main`.

Allí se crean primero los objetos sin dependencias (configuración, `DataSource`, reloj), luego los que dependen de ellos (repositorios), después los servicios y por último los controladores o el punto de entrada. El resultado es un árbol (técnicamente un grafo dirigido) de objetos.

Hacerlo a mano en un proyecto pequeño es un ejercicio excelente: verás exactamente qué hace un contenedor de inyección. También verás el coste: con decenas de clases, mantener el orden de creación, los singletons compartidos y el cierre de recursos se vuelve tedioso. Spring automatiza justamente este método `main`.

*Raíz de composición escrita a mano*

```java
public class TaskFlowApp {
    public static void main(String[] args) {
        // 1. Infraestructura
        var config = Configuracion.desdeVariablesDeEntorno();
        var dataSource = new HikariDataSource(config.hikari());
        var reloj = Clock.systemUTC();

        // 2. Adaptadores
        TareaRepository repo = new TareaRepositoryJdbc(dataSource);
        Notificador notificador = config.emailActivo()
            ? new NotificadorEmail(config.smtp())
            : new NotificadorConsola();

        // 3. Casos de uso
        var tareaService = new TareaService(repo, notificador, reloj);

        // 4. Entrada
        var servidor = new ServidorHttp(config.puerto(), new TareaController(tareaService));
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            servidor.parar();
            dataSource.close();          // los recursos se cierran en orden inverso
        }));
        servidor.arrancar();
    }
}
```

- **En una línea:** `var repo = new Repo(); var serv = new Serv(repo);` en la clase principal.
- **Por qué importa:** Visualizar este árbol permite entender exactamente qué hace Spring de forma automática mediante reflexión.
- **Error típico:** Dispersar la creación de instancias por toda la aplicación en lugar de centralizarla en el punto de entrada.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** ApplicationContext, Contenedor de Beans, Spring Core

### 5. Programar contra Interfaces, no contra Implementaciones

El servicio debe depender de la interfaz `TareaRepositorio`, nunca de la clase concreta `TareaJdbcRepositorio`.

**Cómo funciona por dentro.**

Programar contra interfaces significa que las clases de negocio dependen de un contrato (`TareaRepository`, `Notificador`) y no de una implementación (`TareaRepositoryJdbc`, `NotificadorEmail`). La interfaz describe qué se puede hacer, en términos del dominio, sin decir cómo.

El beneficio inmediato son los tests: puedes pasar una implementación en memoria o un doble de prueba sin tocar el servicio. El beneficio a medio plazo es la flexibilidad: cambiar de base de datos, de proveedor de correo o añadir una caché es escribir una nueva implementación, no modificar la lógica.

No hace falta una interfaz para cada clase. Tiene sentido cuando hay o puede haber varias implementaciones, cuando la dependencia toca el mundo exterior (red, disco, reloj, base de datos) o cuando quieres aislar un módulo. Una interfaz con una única implementación interna y sin intención de sustituirla solo añade ruido.

*Contrato, dos implementaciones y un doble de test*

```java
public interface Notificador {
    void avisar(String destinatario, String mensaje);
}

public class NotificadorEmail implements Notificador {
    private final JavaMailSender mail;
    public NotificadorEmail(JavaMailSender mail) { this.mail = mail; }
    @Override public void avisar(String destinatario, String mensaje) { /* SMTP */ }
}

public class NotificadorConsola implements Notificador {
    @Override public void avisar(String destinatario, String mensaje) {
        System.out.printf("[aviso a %s] %s%n", destinatario, mensaje);
    }
}

// En el test: un doble que registra lo que recibe
class NotificadorEnMemoria implements Notificador {
    final List<String> enviados = new ArrayList<>();
    @Override public void avisar(String destinatario, String mensaje) {
        enviados.add(destinatario + ": " + mensaje);
    }
}
```

- **En una línea:** `public interface TareaRepositorio { void guardar(Tarea t); }`
- **Por qué importa:** Permite cambiar la base de datos o simularla en pruebas con cero impacto en la lógica de negocio.
- **Error típico:** Declarar `private TareaJdbcRepositorio repo;` en el servicio.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** Polimorfismo, Principio de Inversión de Dependencias, DIP


## Sesión de 90 minutos

### Bloque 1: El Acoplamiento del new (0 - 25 min)

- **Qué estudiar:** Principio de Inversión de Dependencias (D de SOLID) e interfaces.
- **Pregunta a responder:** ¿Cómo desacoplar un servicio de facturación de un notificador por email?
- **Entregable:** Interfaz `Notificador` y clase `FacturacionServicio` recibiéndola por constructor.

### Bloque 2: Ensamblado Artesanal en main() (25 - 50 min)

- **Qué estudiar:** Construcción manual del árbol de objetos en la clase principal.
- **Pregunta a responder:** ¿En qué orden deben instanciarse las capas (repositorio -> servicio -> controlador)?
- **Entregable:** Método main() que ensambla 4 clases sin ningún framework.

### Bloque 3: Testeabilidad Inmediata (50 - 80 min)

- **Qué estudiar:** Escribir tests unitarios instanciando el servicio con `new Servicio(fakeRepo)`.
- **Pregunta a responder:** ¿Por qué este test corre en 3 milisegundos?
- **Entregable:** Test JUnit demostrando que no se requiere base de datos ni Spring para probar el servicio.

### Cierre y Puente a Spring (80 - 90 min)

- **Qué estudiar:** Preparación para el Día 7: ¿cómo Spring automatizará este cableado?
- **Pregunta a responder:** ¿Qué anotaciones de Spring reemplazarán el código que escribimos hoy?
- **Entregable:** Anotación en el cuaderno de notas personal.


## Explícalo desde cero

### Por qué Spring nació por culpa del operador new

- **Qué entender:** Cuando un proyecto crece a 200 clases, ensamblar a mano el método main() requiere cientos de líneas de código frágil y repetitivo. Spring nació para ser un "ensamblador automático" de objetos.
- **Qué suele explicarse mal:** Presentar Spring como "un framework de anotaciones" sin explicar que su corazón es simplemente un contenedor que hace los `new` por ti en el orden correcto.
- **Ejemplo sencillo:** Montar un coche a mano apretando cada tornillo (ensamblado manual en main) frente a una línea de fábrica robotizada que coloca el motor, luego el chasis y luego las ruedas automáticamente (Spring IoC).
- **Señal de alerta:** Si crees que necesitas Spring obligatoriamente para hacer inyección de dependencias.


## Preguntas

### Nivel 1 (Básico): ¿Por qué la inyección por constructor es superior a la inyección por atributos privados (`@Autowired private Repo repo;`)?

Porque la inyección por constructor permite declarar los campos como `final` (inmutables), garantiza que la clase nunca se instancie en un estado incompleto y permite probarla con `new Servicio(mock)` en tests unitarios puros sin levantar Spring.

**Respuesta razonada:** Varias razones que se refuerzan. Con constructor, los campos pueden ser `final`: el objeto es inmutable en sus dependencias y seguro entre hilos. Las dependencias obligatorias son explícitas y el objeto no puede existir sin ellas; con `@Autowired` en un campo, si creas la clase con `new` (por ejemplo en un test) el campo queda `null` y falla más tarde con un `NullPointerException`.

También facilita los tests: basta con `new TareaService(repoFalso, notificadorFalso, relojFijo)`, sin contexto de Spring ni reflexión. Y destapa problemas de diseño: un constructor con muchos parámetros grita que la clase hace demasiado, y las dependencias circulares fallan al arrancar en lugar de esconderse. Por eso Spring recomienda la inyección por constructor y, desde Spring 4.3, no hace falta `@Autowired` si hay un único constructor.

### Nivel 2 (Intermedio): ¿Qué es una dependencia circular y por qué el operador `new` en constructores la hace imposible de resolver?

Si A necesita a B en su constructor y B necesita a A, para crear A necesitas B, pero para crear B necesitas A. Es un bloqueo mutuo irresoluble en instanciación por constructor.

**Respuesta razonada:** Hay dependencia circular cuando A necesita a B para construirse y B necesita a A. Si cada clase hace `new` de la otra en su constructor, crear una A crea una B, que crea otra A, que crea otra B... hasta un `StackOverflowError`, porque nunca se termina de construir ninguna.

Con inyección por constructor el problema se hace visible: no hay orden posible para crear los objetos. Spring falla al arrancar con `BeanCurrentlyInCreationException` y, desde Spring Boot 2.6, prohíbe los ciclos por defecto incluso con inyección por campo. La solución casi siempre es de diseño: extraer la parte común a un tercer componente del que dependan ambos, o desacoplar con eventos para que uno publique y el otro escuche.

### Nivel 3 (Entrevista Senior): Si la inyección por constructor es tan buena, ¿por qué los frameworks de testing usan librerías como Mockito?

Mockito genera implementaciones dinámicas (proxies en tiempo de ejecución) de las interfaces de tus dependencias para que puedas inyectarlas por constructor en tus tests sin escribir clases falsas a mano.

**Respuesta razonada:** Porque la inyección por constructor permite sustituir dependencias, pero alguien tiene que escribir esos sustitutos. Para un repositorio sencillo, una implementación en memoria escrita a mano es a menudo la mejor opción: es legible y reutilizable. Para interfaces grandes, o cuando solo te interesa una interacción concreta, escribir la clase entera es tedioso.

Mockito genera esos dobles en tiempo de ejecución: `when(repo.buscar(1L)).thenReturn(Optional.of(tarea))` para fijar respuestas y `verify(notificador).avisar(eq("ana"), anyString())` para comprobar interacciones. No compite con la inyección por constructor, la necesita: `new TareaService(mock(TareaRepository.class), ...)` solo es posible porque las dependencias entran por el constructor. El riesgo es abusar de `verify` y acabar probando cómo está implementado el código en lugar de qué hace.


## Reto del tutor

**Reto:** Un desarrollador propone: "En lugar de pasar el repositorio por constructor, voy a crear un `RepositorioSingleton.getInstance()` estático accesible desde cualquier clase". Evalúa esta propuesta arquitectónica.

- **Lo que está bien:** Reconoce que el patrón Singleton resuelve el problema de tener una sola instancia en memoria.
- **Respuesta superficial:** Decir que "los Singletons son malos".
- **Lo que falta de fondo:** No entender que el acceso estático global introduce un acoplamiento invisible y oculta las dependencias de la clase, haciendo imposible sustituir el repositorio por un mock en tests unitarios.
- **Respuesta senior:** El patrón Singleton estático clásico es un anti-patrón en arquitecturas modernas: (1) Oculta las dependencias; (2) Introduce estado mutable global difícil de depurar; (3) Impide ejecutar tests unitarios en paralelo; (4) La solución idiomática es el Singleton gestionado por el contenedor (como hace Spring): la clase sigue teniendo dependencias explícitas en su constructor, pero el contenedor le inyecta una única instancia compartida.


## Katas

### Kata 10.1: Refactorizar acoplamiento a Inyección de Dependencias

Toma una clase que tenga un `new Repositorio()` interno. Refactorízala extrayendo una interfaz `Repositorio` y pasando la dependencia por constructor con variable `final`. Escribe un test con un mock manual.


## Reglas de oro

- Programa siempre contra interfaces, nunca contra clases concretas.
- Declara siempre tus dependencias como `private final` en el constructor.
- Evita los Singletons estáticos globales (`getInstance()`); prefiere inyección de dependencias.


## Proyecto guiado «Pedidos»: Separar capas con inyección de dependencias

Que la lógica de negocio no sepa si los pedidos se guardan en memoria o en una base de datos.

**Tareas:**

- Define la interfaz `PedidoRepository` y haz que `CatalogoEnMemoria` y `PedidoRepositoryJdbc` la implementen.
- Crea `PedidoService` (crear, confirmar, cancelar) que recibe sus dependencias por constructor en campos `final`.
- Ensambla todas las piezas a mano en un método `main`.

**Está listo cuando:**

- Cambiar de repositorio en memoria a JDBC solo exige tocar el `main`.
- `PedidoService` no contiene ningún `new` de sus dependencias.


## Fuentes oficiales

- [Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern](https://martinfowler.com/articles/injection.html)
- [Spring Framework: el contenedor IoC](https://docs.spring.io/spring-framework/reference/core/beans/introduction.html)

