CodeGym /Cursos /Módulo 5. Spring /Diferentes formas de inyección de dependencias: por const...

Diferentes formas de inyección de dependencias: por constructores, setters y campos

Módulo 5. Spring
Nivel 2 , Lección 2
Disponible

Ahora que sabemos para qué sirve la DI y cómo Spring gestiona las dependencias, es hora de profundizar en los diferentes métodos de inyección de dependencias. Ya hablamos un poco de esto en la lección anterior.

La inyección de dependencias es el proceso de pasar un objeto (o su referencia) a otro objeto que lo necesita, en lugar de que el propio objeto cree sus dependencias. Spring ofrece tres formas principales de inyectar dependencias:

  1. Por constructores.
  2. Por setters.
  3. Por campos.

Cada uno de estos enfoques tiene sus pros y sus contras, y la elección depende del contexto de la tarea. Vamos a ver cada uno con detalle y dónde conviene usarlos.


Inyección de dependencias por constructores

Ventajas:

  • Inmutabilidad: la dependencia se fija al crear el objeto y no puede cambiarse tras la instanciación.
  • Obligatoriedad de la dependencia: si el constructor requiere parámetros, Spring los proporcionará sí o sí, de lo contrario habrá un error.

Desventajas:

  • Muchos parámetros en el constructor pueden hacer el código menos legible.
  • Con muchas dependencias los constructores se vuelven pesados.

Ejemplo:


import org.springframework.stereotype.Component;

// Dependencia que vamos a inyectar
@Component
public class Engine {
    public void start() {
        System.out.println("Engine is starting...");
    }
}

// Clase principal
@Component
public class Car {
    private final Engine engine;

    // Inyección de dependencia vía constructor
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        engine.start();
        System.out.println("Car is driving...");
    }
}

Cómo funciona:

Spring encuentra automáticamente el bean apropiado Engine y se lo pasa al constructor de Car. El enfoque por constructor también es muy útil si el objeto debe ser estrictamente dependiente de otro.


Inyección de dependencias por setters

Ventajas:

  • Permite establecer la dependencia después de crear el objeto.
  • Enfoque más flexible: puedes cambiar la dependencia en runtime (aunque rara vez sea necesario).

Desventajas:

  • No queda claro si la dependencia es obligatoria. Por ejemplo, si olvidas llamar al setter, la aplicación puede fallar inesperadamente.
  • Menos adecuado para implementar objetos inmutables (immutable).

Ejemplo de código:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// Dependencia que vamos a inyectar
@Component
public class Transmission {
    public void engage() {
        System.out.println("Transmission engaged.");
    }
}

// Clase principal
@Component
public class Truck {
    private Transmission transmission;

    // Inyección de dependencia por setter
    @Autowired
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    public void haul() {
        transmission.engage();
        System.out.println("Truck is hauling cargo...");
    }
}

Cómo funciona: Spring llama al método setTransmission() después de crear el objeto Truck. @Autowired le indica al contenedor IoC que debe inyectar la dependencia.

Este método es cómodo si la dependencia puede cambiar o si el objeto requiere una inicialización compleja.


Inyección de dependencias por campos

Ventajas:

  • Código más compacto y conciso.
  • Se necesita menos código para inyectar la dependencia.

Desventajas:

  • No es muy cómodo para testing modular, porque las dependencias no son tan fáciles de pasar vía constructor.
  • Rompe el principio de encapsulación, ya que los campos se inyectan directamente.

Ejemplo:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// Dependencia que vamos a inyectar
@Component
public class BrakeSystem {
    public void applyBrakes() {
        System.out.println("Brakes applied!");
    }
}

// Clase principal
@Component
public class Bicycle {
    @Autowired
    private BrakeSystem brakeSystem; // Inyección de dependencia por campo

    public void stop() {
        brakeSystem.applyBrakes();
        System.out.println("Bicycle has stopped.");
    }
}

Cómo funciona: Spring inyecta BrakeSystem directamente en el campo privado brakeSystem. Esta magia se logra con la anotación @Autowired.


Comparación de los métodos de inyección

Enfoque Ventajas Desventajas
Constructor DI Inmutabilidad, obligatoriedad de la dependencia Incómodo para objetos con muchas dependencias
Setter DI Flexibilidad, útil para cambiar dependencias en runtime Puede funcionar sin establecer la dependencia; no apto para objetos immutable
DI por campos Código compacto, esfuerzo mínimo Ruptura de encapsulación, menos práctico para tests

Ejemplo práctico: ¡todo junto!

Hagamos un ejemplo pequeño donde usamos los tres enfoques a la vez.


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// Dependencias
@Component
class Processor {
    public void process() {
        System.out.println("Processing data...");
    }
}

@Component
class Memory {
    public void load() {
        System.out.println("Memory loaded!");
    }
}

@Component
class HardDrive {
    public void save() {
        System.out.println("Data saved to hard drive.");
    }
}

// Clase principal
@Component
class Computer {
    private final Processor processor; // Inyección por constructor
    private Memory memory;             // Inyección por setter
    @Autowired
    private HardDrive hardDrive;       // Inyección por campo

    @Autowired
    public Computer(Processor processor) {
        this.processor = processor;
    }

    @Autowired
    public void setMemory(Memory memory) {
        this.memory = memory;
    }

    public void start() {
        processor.process();
        memory.load();
        hardDrive.save();
        System.out.println("Computer is running!");
    }
}

Con este ejemplo vemos cómo se pueden usar simultáneamente distintos métodos de DI.


¿Cuándo usar cada enfoque?

  1. Constructor DI:

    • La dependencia es obligatoria para que la clase funcione.
    • El objeto es inmutable.
    • Te importa que al crear el objeto la dependencia se pase necesariamente.
  2. Setter DI:

    • La dependencia es opcional.
    • Necesitas flexibilidad para cambiar la dependencia después de la instanciación.
  3. DI por campos:

    • Si te importa la concisión del código.
    • Si las pruebas no son la prioridad principal.

Trampas y errores típicos

Al inyectar dependencias puedes encontrarte con problemas comunes:

  1. Dependencias cíclicas. Por ejemplo, si el bean A depende de B y B depende de A, Spring no podrá inicializarlos. Para solucionarlo puedes usar la anotación @Lazy o replantear la arquitectura de la aplicación.

  2. Falta de dependencia. Si olvidas declarar un bean o el @Autowired, Spring lanzará NoSuchBeanDefinitionException. Revisa siempre tu configuración.

  3. Ambigüedad de dependencias. Si Spring encuentra varios beans del mismo tipo, no podrá decidir cuál inyectar automáticamente. Usa la anotación @Qualifier para indicar explícitamente el bean deseado.


@Autowired
@Qualifier("specificBeanName")
private MyService myService;

Ahora sabes cómo inyectar dependencias en Spring y podrás elegir con confianza el enfoque adecuado para cada caso. En la siguiente lección veremos cómo gestionar el ciclo de vida de los beans y añadir configuraciones adicionales.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION