Spring Boot

SpringBoot

Tutorial SpringBoot: Inyección de dependencias

Spring Boot inyección de dependencias: uso. Domina la inyección de dependencias en Spring Boot con ejemplos prácticos y detallados.

Aprende SpringBoot GRATIS y certifícate

Qué es la Inversión de Control y la inyección de dependencias

La Inversión de Control (IoC, por sus siglas en inglés) es un principio fundamental en el diseño de software que promueve la desacoplamiento entre componentes. En lugar de que una clase sea responsable de instanciar sus propias dependencias, delega esa responsabilidad a un contenedor o framework externo. Esto permite que las clases se centren en su propia funcionalidad, facilitando la mantenibilidad y la prueba unitaria del código.

Dentro del ecosistema de Spring Boot 3, la Inversión de Control se implementa a través del contenedor de IoC de Spring. Este contenedor se encarga de gestionar el ciclo de vida de los beans, resolviendo y inyectando las dependencias necesarias en tiempo de ejecución. De este modo, las clases no necesitan conocer los detalles de cómo se construyen sus dependencias, lo que reduce el acoplamiento y aumenta la flexibilidad del sistema.

La inyección de dependencias es el proceso mediante el cual el contenedor de IoC proporciona las dependencias requeridas a un objeto. Existen varias formas de realizar esta inyección en Spring Boot 3, como por constructor, por setter o directamente en el campo de la clase. Independientemente del método utilizado, el objetivo es siempre el mismo: delegar la gestión de las dependencias al contenedor.

Consideremos un ejemplo sencillo. Supongamos que tenemos una interfaz RepositorioCliente que define las operaciones para acceder a los datos de clientes:

public interface RepositorioCliente {
    Cliente encontrarPorId(Long id);
}

Y una implementación concreta de esta interfaz:

@Repository
public class RepositorioClienteImpl implements RepositorioCliente {
    @Override
    public Cliente encontrarPorId(Long id) {
        // Lógica para acceder a la base de datos y devolver el cliente
    }
}

Ahora, una clase ServicioCliente que utiliza el repositorio para realizar operaciones de negocio:

@Service
public class ServicioCliente {
    private final RepositorioCliente repositorioCliente;

    public ServicioCliente(RepositorioCliente repositorioCliente) {
        this.repositorioCliente = repositorioCliente;
    }

    public Cliente obtenerCliente(Long id) {
        return repositorioCliente.encontrarPorId(id);
    }
}

En este caso, ServicioCliente no crea una instancia de RepositorioClienteImpl; en su lugar, recibe una instancia de RepositorioCliente a través de la inyección de dependencias. El contenedor de Spring se encarga de proporcionar la implementación adecuada cuando crea el bean de ServicioCliente.

La ventaja de este enfoque es múltiple:

  • Sustituibilidad: Podemos cambiar la implementación de RepositorioCliente sin modificar ServicioCliente, por ejemplo, para utilizar una base de datos diferente o para crear una versión simulada para pruebas.
  • Facilidad de prueba: Podemos inyectar una dependencia simulada (mock) en los tests unitarios, permitiendo aislar y probar el comportamiento de ServicioCliente sin depender de la capa de acceso a datos.
  • Modularidad: Fomentamos una arquitectura más limpia y modular, donde cada componente tiene responsabilidades bien definidas.

La Inversión de Control y la inyección de dependencias son conceptos clave para aprovechar al máximo las capacidades de Spring Boot 3. Al delegar la creación y gestión de dependencias al contenedor, podemos construir aplicaciones más robustas y adaptables a cambios futuros.

Es importante mencionar que, para que el contenedor pueda inyectar las dependencias correctamente, los componentes deben estar anotados adecuadamente (@Component, @Service, @Repository, etc.) y que las dependencias deben definirse como beans dentro del contexto de Spring. De esta forma, el contenedor reconoce y gestiona estos objetos, permitiendo la inyección automática donde sea necesario.

Además, al utilizar la inyección de dependencias, es recomendable depender de interfaces en lugar de implementaciones concretas. Esto mejora la abstracción y permite cambiar las implementaciones sin afectar al código que las utiliza. Por ejemplo, si en el futuro queremos introducir una nueva implementación de RepositorioCliente, simplemente la añadimos y configuramos el contenedor para que la utilice.

En resumen, la Inversión de Control y la inyección de dependencias son prácticas esenciales para desarrollar aplicaciones modernas con Spring Boot 3. Comprender y aplicar estos conceptos nos permite escribir código más limpio, flexible y preparado para evolucionar con las necesidades del software.

Inyección por constructor

La inyección por constructor es un mecanismo mediante el cual se proveen las dependencias necesarias a una clase a través de su constructor. En Spring Boot 3, esta es la forma recomendada de inyección de dependencias, ya que garantiza que las dependencias se proporcionen de manera segura y que la clase esté en un estado válido desde su creación.

Consideremos una clase ServicioOrden que depende de una interfaz RepositorioOrden para interactuar con la capa de datos:

@Service
public class ServicioOrden {
    private final RepositorioOrden repositorioOrden;

    public ServicioOrden(RepositorioOrden repositorioOrden) {
        this.repositorioOrden = repositorioOrden;
    }

    public void procesarOrden(Orden orden) {
        // Lógica de negocio para procesar la orden
        repositorioOrden.guardar(orden);
    }
}

En este ejemplo, el constructor de ServicioOrden recibe una instancia de RepositorioOrden. El contenedor de Spring inyectará automáticamente la implementación adecuada cuando instancie ServicioOrden. La palabra clave final en el campo repositorioOrden garantiza que esta dependencia es obligatoria y no puede ser modificada después de la construcción del objeto.

Para que el contenedor pueda inyectar la dependencia, debemos asegurarnos de que existe un bean de tipo RepositorioOrden. Por ejemplo, una implementación concreta podría ser:

@Repository
public class RepositorioOrdenJPA implements RepositorioOrden {
    private final EntityManager entityManager;

    public RepositorioOrdenJPA(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public void guardar(Orden orden) {
        entityManager.persist(orden);
    }
}

Aquí, RepositorioOrdenJPA también utiliza inyección por constructor para obtener una instancia de EntityManager, que es administrada por el contenedor de persistencia de JPA.

La inyección por constructor tiene varias ventajas importantes:

  • Inmutabilidad: Al utilizar campos final, las dependencias no pueden ser reasignadas después de la construcción, lo que contribuye a la inmutabilidad de la clase.
  • Seguridad: Garantiza que todas las dependencias necesarias se proporcionen en el momento de la creación, evitando problemas de dependencias faltantes.
  • Facilidad de prueba: Facilita la inyección de dependencias simuladas o falsas en las pruebas unitarias, permitiendo aislar el comportamiento de la clase bajo prueba.

Es importante tener en cuenta que, para aprovechar al máximo la inyección por constructor, las clases deben depender de interfaces en lugar de implementaciones concretas. Esto promueve un diseño abierto a la extensión y cerrado a la modificación, permitiendo cambiar las implementaciones sin alterar el código cliente.

Además, si una clase tiene múltiples dependencias, el constructor puede incluir todos los parámetros necesarios:

@Service
public class ServicioNotificacion {
    private final RepositorioUsuario repositorioUsuario;
    private final ServicioEmail servicioEmail;
    private final ServicioSMS servicioSMS;

    public ServicioNotificacion(RepositorioUsuario repositorioUsuario,
                                ServicioEmail servicioEmail,
                                ServicioSMS servicioSMS) {
        this.repositorioUsuario = repositorioUsuario;
        this.servicioEmail = servicioEmail;
        this.servicioSMS = servicioSMS;
    }

    // Métodos que utilizan las dependencias
}

En este caso, el contenedor de Spring resolverá e inyectará cada una de las dependencias declaradas en el constructor. Si alguna de ellas no está disponible como bean en el contexto de aplicación, se producirá un error en tiempo de arranque, lo que ayuda a detectar problemas de configuración temprano en el ciclo de desarrollo.

En versiones modernas de Spring, si una clase tiene un único constructor, el contenedor lo utiliza automáticamente para la inyección de dependencias, sin necesidad de la anotación @Autowired. Si existen múltiples constructores, es necesario indicar al contenedor cuál debe utilizar mediante la anotación @Autowired. Sin embargo, es una buena práctica diseñar las clases de componentes con un único constructor para evitar ambigüedades y facilitar la inyección.

La inyección por constructor favorece el desacoplamiento y la modularidad del código, principios fundamentales en el desarrollo de aplicaciones con Spring Boot 3. Al delegar la gestión de las dependencias al contenedor, se facilita la implementación de la lógica de negocio sin preocuparse por la creación y gestión de objetos complejos.

Inyección por constructor usando Lombok

En el desarrollo con Spring Boot 3, la inyección de dependencias por constructor es una práctica recomendada que promueve la inmutabilidad y facilita las pruebas unitarias. Sin embargo, definir manualmente los constructores puede generar código boilerplate innecesario. Para solucionar esto, Lombok proporciona herramientas que simplifican la creación de constructores mediante anotaciones.

La anotación @RequiredArgsConstructor de Lombok genera automáticamente un constructor con todos los argumentos necesarios para los campos marcados como final o anotados con @NonNull. Esto permite inyectar dependencias de forma eficiente sin escribir código adicional.

Por ejemplo, consideremos una clase de servicio:

@Service
@RequiredArgsConstructor
public class ServicioUsuario {

    private final RepositorioUsuario repositorioUsuario;

    public Usuario obtenerUsuarioPorId(Long id) {
        return repositorioUsuario.findById(id);
    }
}

Aquí, Lombok crea el constructor requerido para inyectar RepositorioUsuario. La dependencia está declarada como final, lo que refuerza su inmutabilidad y garantiza que se asigne durante la construcción del objeto.

Si la clase depende de múltiples componentes, Lombok los incluye todos en el constructor generado:

@Service
@RequiredArgsConstructor
public class ServicioCompra {

    private final RepositorioCompra repositorioCompra;
    private final ServicioNotificacion servicioNotificacion;

    public void procesarCompra(Compra compra) {
        repositorioCompra.guardar(compra);
        servicioNotificacion.enviarAviso(compra);
    }
}

En este caso, el constructor incluirá tanto RepositorioCompra como ServicioNotificacion, facilitando la inyección de ambas dependencias.

Es posible que algunas dependencias no puedan ser marcadas como final, quizás debido a restricciones en el diseño. En tales situaciones, Lombok ofrece la anotación @NonNull para incluir campos no finales pero que deben ser inicializados a través del constructor:

@Component
@RequiredArgsConstructor
public class GestorTarea {

    @NonNull
    private ServicioTarea servicioTarea;
    private Configuracion configuracion;

    // Métodos que utilizan las dependencias
}

Aunque Lombok permite esta flexibilidad, es preferible mantener las dependencias como finales siempre que sea posible para fortalecer la integridad del objeto.

Además de @RequiredArgsConstructor, Lombok proporciona @AllArgsConstructor, que genera un constructor con todos los campos de la clase, independientemente de si son finales o no:

@Service
@AllArgsConstructor
public class ServicioFactura {

    private RepositorioFactura repositorioFactura;
    private ServicioEmail servicioEmail;

    // Métodos que utilizan las dependencias
}

Sin embargo, el uso de @AllArgsConstructor puede introducir dependencias innecesarias en el constructor, por lo que su uso debe ser evaluado con cautela.

Al integrar Lombok en proyectos de Spring Boot 3, es esencial asegurarse de que el entorno de desarrollo reconozca las anotaciones de Lombok. Configurar correctamente los plugins en el IDE garantiza que no aparezcan errores falsos relacionados con código aparentemente ausente.

La combinación de Lombok y la inyección por constructor ofrece varias ventajas:

  • Reducción de código repetitivo: Minimiza la escritura de constructores manuales.
  • Mayor legibilidad: El código es más limpio y enfocado en la lógica de negocio.
  • Mantenimiento simplificado: Menos código significa menos posibilidades de introducir errores al modificar las dependencias.

Es crucial recordar que para que el contenedor de Spring pueda inyectar las dependencias, las clases deben estar anotadas correctamente con @Component, @Service, @Repository, u otras anotaciones estereotipadas.

Finalmente, el uso de Lombok para la inyección por constructor en Spring Boot 3 es una práctica que optimiza el desarrollo, mantiene las buenas prácticas de inmutabilidad y mejora la calidad del código. Al adoptar estas herramientas, los desarrolladores pueden centrarse en aportar valor a través de la lógica de negocio, confiando en que el manejo de las dependencias está gestionado de forma eficiente y segura.

Inyección por setter

La inyección por setter es un mecanismo de inyección de dependencias en Spring Boot 3 que utiliza métodos setter para proporcionar los componentes necesarios a una clase. A diferencia de la inyección por constructor, este enfoque permite que las dependencias sean asignadas después de la creación del objeto, ofreciendo mayor flexibilidad en ciertos escenarios.

En este método, se define un método setter para cada dependencia que se desea inyectar. El contenedor de Spring detecta estos métodos y se encarga de inyectar las dependencias correspondientes en tiempo de ejecución. Para habilitar esta inyección, es común utilizar la anotación @Autowired sobre los métodos setter.

A continuación, se muestra un ejemplo de cómo implementar la inyección por setter:

@Service
public class ServicioProducto {

    private RepositorioProducto repositorioProducto;

    @Autowired
    public void setRepositorioProducto(RepositorioProducto repositorioProducto) {
        this.repositorioProducto = repositorioProducto;
    }

    public List<Producto> obtenerListadoProductos() {
        return repositorioProducto.encontrarTodos();
    }
}

En este ejemplo, la clase ServicioProducto depende de RepositorioProducto para acceder a los datos de los productos. El método setRepositorioProducto, anotado con @Autowired, permite al contenedor de Spring inyectar automáticamente una instancia de RepositorioProducto al llamar a este setter.

Es importante destacar que, al utilizar la inyección por setter, las dependencias no necesitan ser declaradas como finales, ya que pueden ser modificadas después de la construcción del objeto. Esto puede ser útil en situaciones donde las dependencias pueden cambiar durante el ciclo de vida del objeto o para facilitar la mocking en pruebas unitarias.

Otro aspecto relevante es la posibilidad de establecer dependencias opcionales. Si una dependencia no es estrictamente necesaria para el funcionamiento básico de la clase, se puede configurar la inyección para que no sea obligatoria:

@Component
public class GestorNotificaciones {

    private ServicioEmail servicioEmail;

    @Autowired(required = false)
    public void setServicioEmail(ServicioEmail servicioEmail) {
        this.servicioEmail = servicioEmail;
    }

    public void enviarNotificacion(String mensaje) {
        if (servicioEmail != null) {
            servicioEmail.enviarEmail(mensaje);
        } else {
            // Lógica alternativa si el servicio de email no está disponible
        }
    }
}

En este caso, la propiedad required = false en la anotación @Autowired indica que la dependencia es opcional. Si no se encuentra un bean de tipo ServicioEmail, el contenedor no lanzará una excepción y el campo servicioEmail permanecerá nulo.

La inyección por setter también es útil cuando se trabaja con herencia y las clases hijas necesitan inyectar dependencias adicionales o sobreescribir las existentes:

@Service
public class ServicioPedido extends ServicioBase {

    private RepositorioPedido repositorioPedido;

    @Autowired
    public void setRepositorioPedido(RepositorioPedido repositorioPedido) {
        this.repositorioPedido = repositorioPedido;
    }

    // Métodos que utilizan repositorioPedido
}

En este ejemplo, ServicioPedido hereda de ServicioBase y utiliza un setter para inyectar RepositorioPedido, permitiendo una mayor modularidad y reutilización del código.

No obstante, la inyección por setter tiene algunas consideraciones que deben tenerse en cuenta:

Estados inconsistentes: Dado que las dependencias se asignan después de la construcción del objeto, existe el riesgo de que métodos llamados antes de la inyección accedan a dependencias no inicializadas. Para evitar esto, es fundamental asegurar que las dependencias se establezcan antes de su uso.

Testabilidad: Aunque facilita la sustitución de dependencias en pruebas, puede complicar la creación de objetos en tests unitarios si no se gestionan adecuadamente los setters.

Para mejorar la calidad del código, es posible combinar la inyección por setter con validaciones y precondiciones:

@Service
public class ProcesadorArchivo {

    private ServicioAlmacenamiento servicioAlmacenamiento;

    @Autowired
    public void setServicioAlmacenamiento(ServicioAlmacenamiento servicioAlmacenamiento) {
        Assert.notNull(servicioAlmacenamiento, "El servicio de almacenamiento no debe ser nulo");
        this.servicioAlmacenamiento = servicioAlmacenamiento;
    }

    public void procesar(String nombreArchivo) {
        // Lógica de procesamiento
        servicioAlmacenamiento.guardar(nombreArchivo);
    }
}

La utilización de una aserción garantiza que la dependencia no sea nula, evitando posibles errores en tiempo de ejecución.

Asimismo, Spring Boot 3 permite utilizar la inyección por setter junto con la anotación @Qualifier para especificar qué implementación exacta se desea inyectar cuando existen múltiples beans del mismo tipo:

@Service
public class ServicioExportacion {

    private FormatoExportacion formatoExportacion;

    @Autowired
    public void setFormatoExportacion(@Qualifier("formatoPDF") FormatoExportacion formatoExportacion) {
        this.formatoExportacion = formatoExportacion;
    }

    public void exportarDatos(List<Dato> datos) {
        formatoExportacion.exportar(datos);
    }
}

De este modo, se asegura que se inyecta el bean denominado formatoPDF, proporcionando un mayor control sobre las dependencias.

En contextos donde se utiliza Lombok, es posible simplificar aún más la inyección por setter:

@Service
public class ServicioAuditoria {

    @Setter(onMethod = @__(@Autowired))
    private RepositorioAuditoria repositorioAuditoria;

    public void registrarEvento(Evento evento) {
        repositorioAuditoria.guardar(evento);
    }
}

La anotación @Setter de Lombok genera el método setter, y mediante onMethod = @__(@Autowired), se incluye la anotación @Autowired en el método generado. Esto reduce la cantidad de código explícito, manteniendo una claridad en la definición de las dependencias.

Es recomendable utilizar la inyección por setter cuando se necesita:

  • Modificar las dependencias después de la construcción del objeto.
  • Gestionar dependencias opcionales que no son críticas para el funcionamiento básico.
  • Establecer dependencias en situaciones de herencia o polimorfismo donde la inyección por constructor no es viable.

Sin embargo, es fundamental ser cauteloso para evitar problemas de consistencia y garantizar que todas las dependencias estén correctamente inicializadas antes de su uso.

En conclusión, la inyección por setter en Spring Boot 3 es una herramienta poderosa que, utilizada apropiadamente, puede incrementar la flexibilidad y adaptabilidad de las aplicaciones. Comprender cuándo y cómo aplicarla permite diseñar sistemas más robustos y fáciles de mantener, aprovechando al máximo las capacidades del framework.

Inyección por campo con @Autowired

La inyección por campo es un mecanismo de inyección de dependencias en Spring Boot 3 que permite inyectar directamente las dependencias en los campos de una clase utilizando la anotación @Autowired. En este enfoque, el contenedor de Spring se encarga de asignar valores a los campos marcados con @Autowired sin necesidad de constructores o métodos setter.

A continuación, se presenta un ejemplo de cómo implementar la inyección por campo:

@Service
public class ServicioPago {

    @Autowired
    private ProcesadorPago procesadorPago;

    public void realizarPago(Pago pago) {
        procesadorPago.procesar(pago);
    }
}

En este ejemplo, la clase ServicioPago depende de ProcesadorPago para llevar a cabo las operaciones de pago. Al utilizar @Autowired directamente sobre el campo procesadorPago, el contenedor inyecta automáticamente la implementación adecuada de ProcesadorPago.

Este método de inyección es sencillo y reduce el código repetitivo al eliminar la necesidad de constructores o setters para las dependencias. Sin embargo, es importante considerar ciertos aspectos al utilizar la inyección por campo:

Encapsulamiento: Los campos inyectados suelen ser private, lo que mantiene el principio de encapsulación. No obstante, al inyectar dependencias directamente, se rompe parcialmente el encapsulamiento, ya que el framework accede a los campos privados.

Testabilidad: La inyección por campo puede complicar las pruebas unitarias, ya que requiere el uso de herramientas como Reflection para asignar dependencias a los campos privados durante los tests. Esto puede hacer que las pruebas sean más complejas y menos mantenibles.

Inmutabilidad: Al no tener constructores que establezcan las dependencias, es posible que los objetos no estén totalmente inicializados al momento de su creación. Esto puede llevar a estados inconsistentes si no se gestiona adecuadamente.

Para ilustrar otro ejemplo, consideremos una clase que utiliza múltiples dependencias:

@Component
public class GeneradorInformes {

    @Autowired
    private RepositorioDatos repositorioDatos;

    @Autowired
    private ServicioEmail servicioEmail;

    public void generarYEnviarInforme() {
        Datos datos = repositorioDatos.obtenerDatos();
        Informe informe = crearInforme(datos);
        servicioEmail.enviar(informe);
    }

    private Informe crearInforme(Datos datos) {
        // Lógica para crear el informe
    }
}

En este caso, GeneradorInformes depende de RepositorioDatos y ServicioEmail. Las dos dependencias se inyectan directamente en los campos con @Autowired, lo que simplifica la clase al no requerir un constructor con múltiples parámetros.

Es importante destacar que, a partir de Spring Framework 4.3, si solo hay un constructor, el contenedor inyecta automáticamente las dependencias sin necesidad de la anotación @Autowired. Sin embargo, en la inyección por campo, es necesario mantener la anotación @Autowired sobre cada campo.

Además, en Spring Boot 3, es posible omitir la anotación @Autowired si el campo es único y el tipo es conocido por el contenedor. No obstante, es una buena práctica mantener la anotación para mejorar la legibilidad y claridad del código.

La inyección por campo también admite el uso de la anotación @Qualifier para especificar qué implementación se debe inyectar cuando existen múltiples beans del mismo tipo:

@Service
public class ServicioNotificacion {

    @Autowired
    @Qualifier("servicioSMS")
    private ServicioMensajeria servicioMensajeria;

    public void enviarNotificacion(String mensaje) {
        servicioMensajeria.enviarMensaje(mensaje);
    }
}

Con @Qualifier, se indica al contenedor que debe inyectar el bean denominado servicioSMS, asegurando que se utiliza la implementación correcta.

Aunque la inyección por campo es una práctica común, presenta ciertas limitaciones que deben tenerse en cuenta al diseñar aplicaciones en Spring Boot 3:

Dificultad en pruebas unitarias: Como los campos inyectados son privados y no se puede acceder a ellos directamente, se requiere el uso de frameworks como Mockito con anotaciones como @InjectMocks y @Mock para simular las dependencias.

Legibilidad y mantenimiento: La ausencia de un constructor explícito puede ocultar las dependencias reales de una clase, dificultando la comprensión del código y su mantenimiento a largo plazo.

Acoplamiento al framework: La inyección por campo acopla la clase directamente a Spring, ya que se utilizan anotaciones específicas del framework en los campos, lo que puede complicar su reutilización en contextos no gestionados por Spring.

Otra consideración es el uso de final en las dependencias. En la inyección por campo, no es posible declarar los campos como final, ya que no se pueden asignar en la declaración ni en el constructor. Esto va en contra de las prácticas que promueven la inmutabilidad de las dependencias.

Pese a estas limitaciones, la inyección por campo puede ser apropiada en casos donde se busca simplicidad y el ámbito de la clase es bien conocido y controlado. Es especialmente útil en aplicaciones pequeñas o prototipos donde la rapidez de desarrollo es prioritaria.

Para mejorar la testabilidad, se puede considerar la utilización de herramientas que faciliten la inyección en pruebas, como la extensión SpringExtension de JUnit 5:

@ExtendWith(SpringExtension.class)
public class ServicioClienteTest {

    @InjectMocks
    private ServicioCliente servicioCliente;

    @Mock
    private RepositorioCliente repositorioCliente;

    @Test
    public void testObtenerCliente() {
        // Configurar el mock y realizar pruebas
    }
}

En este ejemplo, se utilizan las anotaciones @InjectMocks y @Mock de Mockito para inyectar las dependencias necesarias en los campos privados, lo que simplifica las pruebas.

En resumen, la inyección por campo con @Autowired es una técnica que permite inyectar dependencias de forma directa y concisa en las clases gestionadas por Spring Boot 3. Si bien ofrece ventajas en términos de simplicidad, es fundamental evaluar sus implicaciones en cuanto a encapsulamiento, testabilidad y mantenimiento al decidir su implementación en proyectos de desarrollo.

Uso de la anotación @Qualifier

En el desarrollo con Spring Boot 3, la inyección de dependencias es una técnica fundamental que permite crear aplicaciones modulares y mantenibles. Sin embargo, en ocasiones nos encontramos con situaciones donde existen múltiples implementaciones de una misma interfaz o tipo, lo que puede generar ambigüedad para el contenedor de Spring al momento de inyectar los beans. Para resolver este conflicto, se utiliza la anotación @Qualifier, que especifica cuál de las implementaciones se debe inyectar.

Supongamos que tenemos una interfaz ServicioMensaje y dos implementaciones distintas: ServicioMensajeEmail y ServicioMensajeSMS. Ambas clases están anotadas con @Service para ser reconocidas como beans por el contenedor:

public interface ServicioMensaje {
    void enviarMensaje(String destinatario, String contenido);
}
@Service
public class ServicioMensajeEmail implements ServicioMensaje {
    @Override
    public void enviarMensaje(String destinatario, String contenido) {
        // Lógica para enviar un correo electrónico
    }
}
@Service
public class ServicioMensajeSMS implements ServicioMensaje {
    @Override
    public void enviarMensaje(String destinatario, String contenido) {
        // Lógica para enviar un mensaje SMS
    }
}

Al intentar inyectar ServicioMensaje en otra clase sin especificar qué implementación usar, el contenedor de Spring no sabrá cuál elegir:

@Service
public class NotificadorUsuario {

    private final ServicioMensaje servicioMensaje;

    public NotificadorUsuario(ServicioMensaje servicioMensaje) {
        this.servicioMensaje = servicioMensaje;
    }

    public void notificar(String usuario, String mensaje) {
        servicioMensaje.enviarMensaje(usuario, mensaje);
    }
}

Esta situación provocará una excepción del tipo NoUniqueBeanDefinitionException, ya que existen múltiples beans del tipo ServicioMensaje. Para solucionar este problema, empleamos @Qualifier para indicar explícitamente cuál implementación deseamos inyectar.

Primero, asignamos un nombre de calificador a cada implementación. Esto se puede hacer directamente en la anotación @Service:

@Service("servicioMensajeEmail")
public class ServicioMensajeEmail implements ServicioMensaje {
    // Implementación del método enviarMensaje
}

@Service("servicioMensajeSMS")
public class ServicioMensajeSMS implements ServicioMensaje {
    // Implementación del método enviarMensaje
}

Ahora, en la clase donde inyectamos la dependencia, utilizamos @Qualifier junto con @Autowired (aunque con inyección por constructor no es necesario @Autowired) para especificar el bean deseado:

@Service
public class NotificadorUsuario {

    private final ServicioMensaje servicioMensaje;

    public NotificadorUsuario(@Qualifier("servicioMensajeEmail") ServicioMensaje servicioMensaje) {
        this.servicioMensaje = servicioMensaje;
    }

    // Método notificar que utiliza servicioMensaje
}

En este ejemplo, hemos indicado que queremos inyectar el bean denominado servicioMensajeEmail, evitando así la ambigüedad. Es importante que el nombre utilizado en @Qualifier coincida exactamente con el asignado en la definición del bean.

Además de nombrar los beans en la anotación estereotipada, podemos utilizar la anotación @Component o incluso @Qualifier directamente en las clases de implementación:

@Component
@Qualifier("email")
public class ServicioMensajeEmail implements ServicioMensaje {
    // Implementación
}

@Component
@Qualifier("sms")
public class ServicioMensajeSMS implements ServicioMensaje {
    // Implementación
}

Luego, al inyectar la dependencia, usamos el calificador correspondiente:

public NotificadorUsuario(@Qualifier("email") ServicioMensaje servicioMensaje) {
    this.servicioMensaje = servicioMensaje;
}

También es posible crear nuestras propias anotaciones de calificador para mejorar la legibilidad y el mantenimiento del código. Por ejemplo:

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MensajeEmail {
}

Aplicamos esta nueva anotación en la implementación:

@Service
@MensajeEmail
public class ServicioMensajeEmail implements ServicioMensaje {
    // Implementación
}

Y luego la usamos al inyectar la dependencia:

public NotificadorUsuario(@MensajeEmail ServicioMensaje servicioMensaje) {
    this.servicioMensaje = servicioMensaje;
}

Este enfoque hace que el código sea más semántico y fácil de entender, especialmente cuando hay múltiples implementaciones.

Es relevante mencionar que @Qualifier no solo se utiliza en la inyección por constructor, sino también en la inyección por setter y por campo. Por ejemplo, en la inyección por campo:

@Autowired
@Qualifier("servicioMensajeSMS")
private ServicioMensaje servicioMensaje;

En el caso de listas o colecciones de beans, podemos inyectar todos los beans de un tipo específico y luego filtrar o seleccionar según necesitemos. Por ejemplo:

@Service
public class GestorMensajes {

    private final List<ServicioMensaje> serviciosMensaje;

    public GestorMensajes(List<ServicioMensaje> serviciosMensaje) {
        this.serviciosMensaje = serviciosMensaje;
    }

    public void enviarNotificaciones(String mensaje) {
        serviciosMensaje.forEach(servicio -> servicio.enviarMensaje("destinatario", mensaje));
    }
}

Si queremos inyectar solo un subconjunto de beans, utilizamos @Qualifier combinado con @Autowired:

@Autowired
@Qualifier("email")
private ServicioMensaje servicioMensaje;

Otra situación común es cuando trabajamos con beans primarios. Si marcamos una implementación con @Primary, el contenedor de Spring la seleccionará por defecto en caso de ambigüedad:

@Service
@Primary
public class ServicioMensajePush implements ServicioMensaje {
    // Implementación
}

Sin embargo, si necesitamos otra implementación específica, @Qualifier prevalece sobre @Primary, permitiendo mayor control sobre qué bean se inyecta en cada caso.

Además, en aplicaciones más complejas, es posible utilizar @Qualifier con calificadores de nombre y @Value para inyectar beans basados en propiedades de configuración:

@Service
public class ProcesadorPago {

    private final PasarelaPago pasarelaPago;

    public ProcesadorPago(@Qualifier("${pasarela.por.defecto}") PasarelaPago pasarelaPago) {
        this.pasarelaPago = pasarelaPago;
    }

    // Métodos que utilizan pasarelaPago
}

En este ejemplo, el valor del calificador se obtiene de una propiedad definida en el archivo de configuración, lo que aporta flexibilidad y permite cambiar la implementación inyectada sin modificar el código fuente.

Es importante tener en cuenta que el uso excesivo de @Qualifier puede ser un indicio de que el diseño de la aplicación necesita ser revisado. Siempre que sea posible, es recomendable utilizar interfaces y patrones de diseño que reduzcan la necesidad de especificar implementaciones concretas.

Finalmente, el conocimiento y uso adecuado de la anotación @Qualifier en Spring Boot 3 es esencial para manejar situaciones donde existen múltiples implementaciones de una misma dependencia. Proporciona una forma clara y explícita de indicar al contenedor qué bean debe inyectar, mejorando la legibilidad y la mantenibilidad del código en aplicaciones profesionales.

Aprende SpringBoot GRATIS online

Ejercicios de esta lección Inyección de dependencias

Evalúa tus conocimientos de esta lección Inyección de dependencias con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

API Query By Example (QBE)

Spring Boot
Test

Identificadores y relaciones JPA

Spring Boot
Puzzle

Borrar datos de base de datos

Spring Boot
Test

Web y Test Starters

Spring Boot
Puzzle

Métodos find en repositorios

Spring Boot
Test

Controladores Spring MVC

Spring Boot
Código

Inserción de datos

Spring Boot
Test

CRUD Customers Spring MVC + Spring Data JPA

Spring Boot
Proyecto

Backend API REST con Spring Boot

Spring Boot
Proyecto

Controladores Spring REST

Spring Boot
Código

Uso de Spring con Thymeleaf

Spring Boot
Puzzle

API Specification

Spring Boot
Puzzle

Registro de usuarios

Spring Boot
Test

Crear entidades JPA

Spring Boot
Código

Asociaciones en JPA

Spring Boot
Test

Asociaciones de entidades JPA

Spring Boot
Código

Integración con Vue

Spring Boot
Test

Consultas JPQL

Spring Boot
Código

Open API y cómo agregarlo en Spring Boot

Spring Boot
Puzzle

Uso de Controladores REST

Spring Boot
Puzzle

Repositorios reactivos

Spring Boot
Test

Inyección de dependencias

Spring Boot
Test

Introducción a Spring Boot

Spring Boot
Test

CRUD y JPA Repository

Spring Boot
Puzzle

Inyección de dependencias

Spring Boot
Código

Vista en Spring MVC con Thymeleaf

Spring Boot
Test

Servicios en Spring

Spring Boot
Código

Operadores Reactivos

Spring Boot
Puzzle

Configuración de Vue

Spring Boot
Puzzle

Entidades JPA

Spring Boot
Test

Integración con Angular

Spring Boot
Test

API Specification

Spring Boot
Test

API Query By Example (QBE)

Spring Boot
Puzzle

Controladores MVC

Spring Boot
Test

Anotaciones y mapeo en JPA

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Test

Repositorios Spring Data

Spring Boot
Test

Inyección de dependencias

Spring Boot
Puzzle

Data JPA y Mail Starters

Spring Boot
Test

Configuración de Angular

Spring Boot
Puzzle

Controladores Spring REST

Spring Boot
Test

Configuración de Controladores MVC

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Puzzle

Actualizar datos de base de datos

Spring Boot
Test

Verificar token JWT en peticiones

Spring Boot
Test

Login de usuarios

Spring Boot
Test

Integración con React

Spring Boot
Test

Configuración de React

Spring Boot
Puzzle

Todas las lecciones de SpringBoot

Accede a todas las lecciones de SpringBoot y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Spring Boot

Spring Boot

Introducción Y Entorno

Spring Boot Starters

Spring Boot

Introducción Y Entorno

Inyección De Dependencias

Spring Boot

Introducción Y Entorno

Controladores Spring Mvc

Spring Boot

Spring Web

Vista En Spring Mvc Con Thymeleaf

Spring Boot

Spring Web

Controladores Spring Rest

Spring Boot

Spring Web

Open Api Y Cómo Agregarlo En Spring Boot

Spring Boot

Spring Web

Servicios En Spring

Spring Boot

Spring Web

Clientes Resttemplate Y Restclient

Spring Boot

Spring Web

Rxjava En Spring Web

Spring Boot

Spring Web

Crear Entidades Jpa

Spring Boot

Persistencia Spring Data

Asociaciones De Entidades Jpa

Spring Boot

Persistencia Spring Data

Repositorios Spring Data

Spring Boot

Persistencia Spring Data

Métodos Find En Repositorios

Spring Boot

Persistencia Spring Data

Inserción De Datos

Spring Boot

Persistencia Spring Data

Actualizar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Borrar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Consultas Jpql Con @Query En Spring Data Jpa

Spring Boot

Persistencia Spring Data

Api Query By Example (Qbe)

Spring Boot

Persistencia Spring Data

Api Specification

Spring Boot

Persistencia Spring Data

Repositorios Reactivos

Spring Boot

Persistencia Spring Data

Introducción E Instalación De Apache Kafka

Spring Boot

Mensajería Asíncrona

Crear Proyecto Con Apache Kafka

Spring Boot

Mensajería Asíncrona

Creación De Producers

Spring Boot

Mensajería Asíncrona

Creación De Consumers

Spring Boot

Mensajería Asíncrona

Kafka Streams En Spring Boot

Spring Boot

Mensajería Asíncrona

Introducción A Spring Webflux

Spring Boot

Reactividad Webflux

Spring Data R2dbc

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Basado En Anotaciones

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Funcional

Spring Boot

Reactividad Webflux

Operadores Reactivos Básicos

Spring Boot

Reactividad Webflux

Operadores Reactivos Avanzados

Spring Boot

Reactividad Webflux

Cliente Reactivo Webclient

Spring Boot

Reactividad Webflux

Introducción A Spring Security

Spring Boot

Seguridad Con Spring Security

Seguridad Basada En Formulario En Mvc Con Thymeleaf

Spring Boot

Seguridad Con Spring Security

Registro De Usuarios

Spring Boot

Seguridad Con Spring Security

Login De Usuarios

Spring Boot

Seguridad Con Spring Security

Verificar Token Jwt En Peticiones

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Spring Web

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Reactiva Spring Webflux

Spring Boot

Seguridad Con Spring Security

Autenticación Y Autorización Con Anotaciones

Spring Boot

Seguridad Con Spring Security

Testing Unitario De Componentes Y Servicios

Spring Boot

Testing Con Spring Test

Testing De Repositorios Spring Data Jpa

Spring Boot

Testing Con Spring Test

Testing Controladores Spring Mvc Con Thymeleaf

Spring Boot

Testing Con Spring Test

Testing Controladores Rest Con Json

Spring Boot

Testing Con Spring Test

Testing De Aplicaciones Reactivas Webflux

Spring Boot

Testing Con Spring Test

Testing De Seguridad Spring Security

Spring Boot

Testing Con Spring Test

Testing Con Apache Kafka

Spring Boot

Testing Con Spring Test

Integración Con Angular

Spring Boot

Integración Frontend

Integración Con React

Spring Boot

Integración Frontend

Integración Con Vue

Spring Boot

Integración Frontend

Accede GRATIS a SpringBoot y certifícate

Objetivos de aprendizaje de esta lección

  1. Comprender el concepto y el propósito de la Inversión de Control (IoC) y cómo se implementa en Spring Boot.
  2. Aprender sobre la inyección de dependencias, incluyendo la inyección por constructor, setter y field.
  3. Entender cómo funciona Autowiring en Spring Boot y cómo se pueden usar las anotaciones @Autowired y @Qualifier para automatizar y personalizar la inyección de dependencias.
  4. Aprender a manejar situaciones en las que hay múltiples beans de un mismo tipo y cómo Spring resuelve las ambigüedades.
  5. Familiarizarse con los distintos modos de Autowiring en Spring: no, byName, byType, constructor.