Inyección de dependencias

Intermedio
SpringBoot
SpringBoot
Actualizado: 12/06/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Fundamento teórico de la inyección de dependencias y la inversión de control

La inyección de dependencias es un patrón de diseño fundamental que resuelve uno de los problemas más comunes en el desarrollo de software: cómo gestionar las relaciones entre objetos de manera eficiente y mantenible. Para comprender su importancia, primero debemos entender qué es una dependencia en el contexto de la programación.

Una dependencia existe cuando una clase necesita utilizar los servicios o funcionalidades de otra clase para realizar su trabajo. Por ejemplo, si tenemos una clase PedidoService que necesita enviar notificaciones por email, esta clase depende de una clase EmailService. Tradicionalmente, esta relación se establecía creando directamente la instancia de la dependencia dentro de la clase que la necesita.

public class PedidoService {
    private EmailService emailService;
    
    public PedidoService() {
        // La clase crea su propia dependencia
        this.emailService = new EmailService();
    }
    
    public void procesarPedido(Pedido pedido) {
        // Lógica de procesamiento
        emailService.enviarConfirmacion(pedido.getEmail());
    }
}

Este enfoque tradicional presenta varios problemas significativos. Primero, crea un acoplamiento fuerte entre las clases, lo que significa que PedidoService está íntimamente ligado a la implementación específica de EmailService. Si necesitamos cambiar la forma de enviar emails o usar un servicio diferente, tendríamos que modificar el código de PedidoService.

Segundo, dificulta enormemente las pruebas unitarias. No podemos probar PedidoService de forma aislada porque siempre creará una instancia real de EmailService, lo que podría enviar emails reales durante las pruebas. Tercero, viola el principio de responsabilidad única, ya que PedidoService no solo se encarga de procesar pedidos, sino también de crear y gestionar sus dependencias.

Inversión de control

La inversión de control (IoC) es el principio fundamental que resuelve estos problemas. En lugar de que las clases creen sus propias dependencias, el control de la creación y gestión de objetos se invierte y se delega a un mecanismo externo. Este principio establece que las clases deben recibir sus dependencias desde el exterior, en lugar de crearlas internamente.

public class PedidoService {
    private EmailService emailService;
    
    // La dependencia se recibe desde el exterior
    public PedidoService(EmailService emailService) {
        this.emailService = emailService;
    }
    
    public void procesarPedido(Pedido pedido) {
        // Lógica de procesamiento
        emailService.enviarConfirmacion(pedido.getEmail());
    }
}

Con este enfoque, PedidoService ya no es responsable de crear EmailService. Simplemente declara que necesita esta dependencia y confía en que alguien más se la proporcionará. Este "alguien más" es lo que llamamos un contenedor de inversión de control.

El contenedor de Spring

Spring Framework implementa la inversión de control a través de su contenedor IoC, también conocido como contexto de aplicación. Este contenedor es responsable de crear, configurar y gestionar el ciclo de vida de los objetos (llamados beans en Spring) y de resolver sus dependencias automáticamente.

El proceso funciona de la siguiente manera: cuando Spring inicia, escanea las clases de la aplicación buscando aquellas marcadas con anotaciones especiales como @Service, @Component o @Repository. Para cada una de estas clases, Spring crea una instancia y la registra en su contenedor. Cuando una clase necesita una dependencia, Spring la busca en su contenedor y la inyecta automáticamente.

@Service
public class EmailService {
    public void enviarConfirmacion(String email) {
        System.out.println("Enviando confirmación a: " + email);
    }
}

@Service
public class PedidoService {
    private EmailService emailService;
    
    public PedidoService(EmailService emailService) {
        this.emailService = emailService;
    }
    
    public void procesarPedido(Pedido pedido) {
        // Lógica de procesamiento
        emailService.enviarConfirmacion(pedido.getEmail());
    }
}

En este ejemplo, Spring detecta que PedidoService necesita un EmailService en su constructor. Como también ha registrado EmailService como un bean, automáticamente crea una instancia de EmailService y la pasa al constructor de PedidoService cuando crea esta instancia.

Beneficios de la inyección de dependencias

La inyección de dependencias aporta múltiples ventajas al desarrollo de software. El bajo acoplamiento es quizás el beneficio más importante: las clases dependen de abstracciones en lugar de implementaciones concretas, lo que facilita enormemente el mantenimiento y la evolución del código.

La facilidad para realizar pruebas es otro beneficio crucial. Podemos crear implementaciones falsas (mocks) de las dependencias para probar cada clase de forma aislada, sin depender de servicios externos como bases de datos o APIs.

// En una prueba unitaria
@Test
public void testProcesarPedido() {
    // Creamos un mock del servicio de email
    EmailService mockEmailService = Mockito.mock(EmailService.class);
    
    // Inyectamos el mock en nuestro servicio
    PedidoService pedidoService = new PedidoService(mockEmailService);
    
    // Ejecutamos la prueba
    Pedido pedido = new Pedido("test@example.com");
    pedidoService.procesarPedido(pedido);
    
    // Verificamos que se llamó al método correcto
    Mockito.verify(mockEmailService).enviarConfirmacion("test@example.com");
}

La flexibilidad es otro aspecto fundamental. Podemos cambiar fácilmente las implementaciones sin modificar el código que las utiliza. Por ejemplo, podríamos tener diferentes implementaciones de EmailService para desarrollo, pruebas y producción, y Spring se encargará de inyectar la correcta según la configuración.

Finalmente, la inyección de dependencias promueve el principio de responsabilidad única, ya que cada clase se enfoca únicamente en su lógica de negocio, delegando la gestión de dependencias al contenedor de Spring. Esto resulta en código más limpio, mantenible y fácil de entender.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Inyección por constructor

La inyección por constructor es el mecanismo más recomendado para implementar la inyección de dependencias en Spring Boot. Este enfoque consiste en declarar las dependencias como parámetros del constructor de la clase, permitiendo que Spring las resuelva automáticamente durante la creación del objeto.

Implementación básica

Para implementar inyección por constructor, simplemente definimos un constructor que reciba como parámetros todas las dependencias que necesita nuestra clase. Spring detectará automáticamente este constructor y proporcionará las instancias correspondientes de cada dependencia.

@Service
public class UsuarioService {
    private final EmailService emailService;
    private final ValidacionService validacionService;
    
    public UsuarioService(EmailService emailService, ValidacionService validacionService) {
        this.emailService = emailService;
        this.validacionService = validacionService;
    }
    
    public void registrarUsuario(String nombre, String email) {
        if (validacionService.esEmailValido(email)) {
            // Lógica de registro
            emailService.enviarBienvenida(email, nombre);
        }
    }
}

En este ejemplo, UsuarioService declara dos dependencias en su constructor. Cuando Spring crea una instancia de esta clase, automáticamente busca beans de tipo EmailService y ValidacionService en su contenedor y los pasa al constructor.

Ventajas del constructor

La inyección por constructor ofrece inmutabilidad de las dependencias. Al declarar los campos como final, garantizamos que las dependencias no puedan ser modificadas después de la creación del objeto, lo que hace el código más seguro y predecible.

@Service
public class ProductoService {
    private final InventarioService inventarioService;
    private final PrecioService precioService;
    
    public ProductoService(InventarioService inventarioService, PrecioService precioService) {
        this.inventarioService = inventarioService;
        this.precioService = precioService;
    }
    
    public boolean verificarDisponibilidad(String codigoProducto) {
        return inventarioService.tieneStock(codigoProducto);
    }
    
    public double calcularPrecioFinal(String codigoProducto, int cantidad) {
        return precioService.calcularPrecio(codigoProducto, cantidad);
    }
}

Otra ventaja importante es la detección temprana de errores. Si una dependencia requerida no está disponible, la aplicación fallará durante el inicio, no durante la ejecución. Esto permite identificar problemas de configuración antes de que la aplicación llegue a producción.

Constructor único y @Autowired

Cuando una clase tiene un solo constructor, Spring automáticamente lo utiliza para la inyección de dependencias, sin necesidad de anotaciones adicionales. Esta es la práctica recomendada en Spring Boot moderno.

@Service
public class NotificacionService {
    private final EmailService emailService;
    
    // No necesita @Autowired - Spring lo detecta automáticamente
    public NotificacionService(EmailService emailService) {
        this.emailService = emailService;
    }
    
    public void notificarCambioEstado(String email, String nuevoEstado) {
        emailService.enviarNotificacion(email, "Estado actualizado: " + nuevoEstado);
    }
}

Sin embargo, si una clase tiene múltiples constructores, debemos indicar explícitamente cuál usar para la inyección mediante la anotación @Autowired:

@Service
public class ReporteService {
    private final DatabaseService databaseService;
    private final FormatoService formatoService;
    
    // Constructor para inyección de dependencias
    @Autowired
    public ReporteService(DatabaseService databaseService, FormatoService formatoService) {
        this.databaseService = databaseService;
        this.formatoService = formatoService;
    }
    
    // Constructor alternativo para casos especiales
    public ReporteService(DatabaseService databaseService) {
        this.databaseService = databaseService;
        this.formatoService = new FormatoService(); // Implementación por defecto
    }
    
    public String generarReporte(String consulta) {
        String datos = databaseService.ejecutarConsulta(consulta);
        return formatoService.formatear(datos);
    }
}

Validación de dependencias

La inyección por constructor permite implementar validaciones de las dependencias directamente en el constructor, asegurando que el objeto se cree en un estado válido:

@Service
public class PagoService {
    private final BancoService bancoService;
    private final AuditoriaService auditoriaService;
    
    public PagoService(BancoService bancoService, AuditoriaService auditoriaService) {
        if (bancoService == null) {
            throw new IllegalArgumentException("BancoService no puede ser null");
        }
        if (auditoriaService == null) {
            throw new IllegalArgumentException("AuditoriaService no puede ser null");
        }
        
        this.bancoService = bancoService;
        this.auditoriaService = auditoriaService;
    }
    
    public boolean procesarPago(double monto, String tarjeta) {
        auditoriaService.registrarIntento(monto, tarjeta);
        return bancoService.cobrar(monto, tarjeta);
    }
}

Trabajando con múltiples dependencias

Cuando una clase requiere varias dependencias, la inyección por constructor mantiene la claridad y organización del código. Es importante ordenar los parámetros del constructor de manera lógica, generalmente colocando las dependencias más importantes primero:

@Service
public class PedidoService {
    private final ClienteService clienteService;
    private final ProductoService productoService;
    private final InventarioService inventarioService;
    private final EmailService emailService;
    
    public PedidoService(ClienteService clienteService, 
                        ProductoService productoService,
                        InventarioService inventarioService, 
                        EmailService emailService) {
        this.clienteService = clienteService;
        this.productoService = productoService;
        this.inventarioService = inventarioService;
        this.emailService = emailService;
    }
    
    public void crearPedido(String clienteId, String productoId, int cantidad) {
        if (clienteService.existeCliente(clienteId) && 
            inventarioService.verificarStock(productoId, cantidad)) {
            
            // Crear pedido
            emailService.enviarConfirmacion(clienteService.getEmail(clienteId));
        }
    }
}

La inyección por constructor es la opción preferida en Spring Boot porque garantiza que todos los objetos se creen completamente inicializados, facilita las pruebas unitarias y hace explícitas las dependencias de cada clase, mejorando la legibilidad y mantenibilidad del código.

Inyección por setter

La inyección por setter es un mecanismo alternativo para proporcionar dependencias a una clase mediante métodos setter específicos. A diferencia de la inyección por constructor, este enfoque permite establecer las dependencias después de que el objeto ha sido creado, ofreciendo mayor flexibilidad en ciertos escenarios.

Implementación básica

Para implementar inyección por setter, creamos métodos setter para cada dependencia y los marcamos con la anotación @Autowired. Spring llamará automáticamente a estos métodos después de crear la instancia de la clase:

@Service
public class CatalogoService {
    private ProductoService productoService;
    private CategoriaService categoriaService;
    
    @Autowired
    public void setProductoService(ProductoService productoService) {
        this.productoService = productoService;
    }
    
    @Autowired
    public void setCategoriaService(CategoriaService categoriaService) {
        this.categoriaService = categoriaService;
    }
    
    public List<String> obtenerProductosPorCategoria(String categoria) {
        if (categoriaService.existeCategoria(categoria)) {
            return productoService.buscarPorCategoria(categoria);
        }
        return new ArrayList<>();
    }
}

En este ejemplo, Spring primero crea una instancia de CatalogoService usando su constructor por defecto, y luego llama a los métodos setProductoService y setCategoriaService para inyectar las dependencias correspondientes.

Dependencias opcionales

Una de las principales ventajas de la inyección por setter es la capacidad de manejar dependencias opcionales. Podemos configurar comportamientos por defecto cuando ciertas dependencias no están disponibles:

@Service
public class ConfiguracionService {
    private CacheService cacheService;
    private LogService logService;
    
    @Autowired(required = false)
    public void setCacheService(CacheService cacheService) {
        this.cacheService = cacheService;
    }
    
    @Autowired
    public void setLogService(LogService logService) {
        this.logService = logService;
    }
    
    public String obtenerConfiguracion(String clave) {
        // Usar cache si está disponible
        if (cacheService != null) {
            String valorCache = cacheService.obtener(clave);
            if (valorCache != null) {
                return valorCache;
            }
        }
        
        // Obtener de la fuente principal
        String valor = cargarDesdeBD(clave);
        logService.registrar("Configuración cargada: " + clave);
        
        return valor;
    }
    
    private String cargarDesdeBD(String clave) {
        // Simulación de carga desde base de datos
        return "valor_" + clave;
    }
}

El parámetro required = false en la anotación @Autowired indica que la dependencia es opcional. Si Spring no encuentra un bean del tipo especificado, simplemente no llamará al setter, dejando el campo con valor null.

Reconfiguración dinámica

La inyección por setter permite modificar dependencias después de la creación del objeto, lo que puede ser útil en escenarios de testing o configuraciones dinámicas:

@Service
public class FacturacionService {
    private CalculadoraImpuestos calculadoraImpuestos;
    private FormateadorMoneda formateadorMoneda;
    
    @Autowired
    public void setCalculadoraImpuestos(CalculadoraImpuestos calculadoraImpuestos) {
        this.calculadoraImpuestos = calculadoraImpuestos;
    }
    
    @Autowired
    public void setFormateadorMoneda(FormateadorMoneda formateadorMoneda) {
        this.formateadorMoneda = formateadorMoneda;
    }
    
    public String calcularFactura(double subtotal) {
        double impuestos = calculadoraImpuestos.calcular(subtotal);
        double total = subtotal + impuestos;
        return formateadorMoneda.formatear(total);
    }
}

Validación en setters

Podemos implementar validaciones dentro de los métodos setter para asegurar que las dependencias cumplan ciertos criterios antes de ser asignadas:

@Service
public class SeguridadService {
    private EncriptacionService encriptacionService;
    private ValidacionService validacionService;
    
    @Autowired
    public void setEncriptacionService(EncriptacionService encriptacionService) {
        if (encriptacionService == null) {
            throw new IllegalArgumentException("EncriptacionService no puede ser null");
        }
        this.encriptacionService = encriptacionService;
    }
    
    @Autowired
    public void setValidacionService(ValidacionService validacionService) {
        this.validacionService = validacionService;
    }
    
    public String procesarPassword(String password) {
        if (validacionService != null) {
            validacionService.validarFortaleza(password);
        }
        return encriptacionService.encriptar(password);
    }
}

Limitaciones y consideraciones

La inyección por setter presenta algunas limitaciones importantes. Los campos no pueden ser declarados como final, ya que deben ser modificables después de la construcción del objeto. Esto puede hacer el código menos seguro en términos de inmutabilidad:

@Service
public class DocumentoService {
    // No puede ser final con inyección por setter
    private AlmacenamientoService almacenamientoService;
    private ValidacionService validacionService;
    
    @Autowired
    public void setAlmacenamientoService(AlmacenamientoService almacenamientoService) {
        this.almacenamientoService = almacenamientoService;
    }
    
    @Autowired
    public void setValidacionService(ValidacionService validacionService) {
        this.validacionService = validacionService;
    }
    
    public void guardarDocumento(String contenido) {
        if (validacionService.esContenidoValido(contenido)) {
            almacenamientoService.guardar(contenido);
        }
    }
}

Otra consideración importante es que el objeto puede estar en un estado parcialmente inicializado durante un período de tiempo, ya que Spring primero crea la instancia y luego inyecta las dependencias. Esto requiere que los métodos de la clase manejen adecuadamente la posibilidad de que algunas dependencias aún no hayan sido inyectadas.

Cuándo usar inyección por setter

La inyección por setter es más apropiada cuando necesitamos dependencias opcionales o cuando queremos permitir la reconfiguración de dependencias en tiempo de ejecución. También puede ser útil cuando trabajamos con dependencias circulares, aunque esto generalmente indica un problema de diseño que debería resolverse de otra manera.

@Service
public class ReporteService {
    private DatabaseService databaseService;
    private ExportadorService exportadorService; // Opcional
    
    @Autowired
    public void setDatabaseService(DatabaseService databaseService) {
        this.databaseService = databaseService;
    }
    
    @Autowired(required = false)
    public void setExportadorService(ExportadorService exportadorService) {
        this.exportadorService = exportadorService;
    }
    
    public String generarReporte(String consulta) {
        String datos = databaseService.obtenerDatos(consulta);
        
        // Usar exportador si está disponible, sino formato básico
        if (exportadorService != null) {
            return exportadorService.exportarAPDF(datos);
        } else {
            return "Reporte básico: " + datos;
        }
    }
}

En general, la inyección por constructor sigue siendo la opción recomendada para la mayoría de casos, reservando la inyección por setter para situaciones específicas donde su flexibilidad adicional sea realmente necesaria.

Inyección por campo @Autowired

La inyección por campo es el mecanismo más directo para establecer dependencias en Spring Boot, donde las dependencias se inyectan directamente en los campos de la clase mediante la anotación @Autowired. Este enfoque elimina la necesidad de constructores o métodos setter específicos, simplificando considerablemente la sintaxis del código.

Implementación básica

Para implementar inyección por campo, simplemente declaramos los campos que representan nuestras dependencias y los marcamos con @Autowired. Spring se encarga automáticamente de encontrar e inyectar las instancias correspondientes:

@Service
public class VentaService {
    @Autowired
    private ClienteService clienteService;
    
    @Autowired
    private ProductoService productoService;
    
    @Autowired
    private FacturacionService facturacionService;
    
    public void procesarVenta(String clienteId, String productoId, int cantidad) {
        if (clienteService.esClienteActivo(clienteId)) {
            double precio = productoService.obtenerPrecio(productoId);
            facturacionService.generarFactura(clienteId, precio * cantidad);
        }
    }
}

En este ejemplo, Spring inyecta automáticamente las tres dependencias directamente en los campos correspondientes, sin necesidad de código adicional para la inicialización.

Ventajas de la inyección por campo

La principal ventaja de este enfoque es su simplicidad y limpieza visual. El código resulta más conciso y fácil de leer, especialmente cuando una clase tiene múltiples dependencias:

@Service
public class GestionPedidosService {
    @Autowired
    private ValidacionService validacionService;
    
    @Autowired
    private InventarioService inventarioService;
    
    @Autowired
    private EmailService emailService;
    
    @Autowired
    private AuditoriaService auditoriaService;
    
    @Autowired
    private DescuentoService descuentoService;
    
    public void crearPedido(Pedido pedido) {
        validacionService.validarPedido(pedido);
        
        if (inventarioService.verificarDisponibilidad(pedido.getProductos())) {
            double descuento = descuentoService.calcularDescuento(pedido);
            pedido.aplicarDescuento(descuento);
            
            auditoriaService.registrarPedido(pedido);
            emailService.enviarConfirmacion(pedido.getClienteEmail());
        }
    }
}

Dependencias opcionales con @Autowired

Al igual que con la inyección por setter, podemos hacer que las dependencias sean opcionales utilizando el parámetro required = false:

@Service
public class MonitoreoService {
    @Autowired
    private LogService logService;
    
    @Autowired(required = false)
    private MetricasService metricasService;
    
    @Autowired(required = false)
    private AlertaService alertaService;
    
    public void registrarEvento(String evento) {
        logService.escribir("Evento registrado: " + evento);
        
        // Usar métricas si está disponible
        if (metricasService != null) {
            metricasService.incrementarContador(evento);
        }
        
        // Enviar alerta si es crítico y el servicio está disponible
        if (evento.contains("ERROR") && alertaService != null) {
            alertaService.enviarAlerta("Error detectado: " + evento);
        }
    }
}

Limitaciones importantes

La inyección por campo presenta limitaciones significativas que debemos considerar. La más importante es que los campos no pueden ser declarados como final, lo que impide garantizar la inmutabilidad de las dependencias:

@Service
public class PagoService {
    // No puede ser final con inyección por campo
    @Autowired
    private BancoService bancoService;
    
    @Autowired
    private ValidacionService validacionService;
    
    public boolean procesarPago(String tarjeta, double monto) {
        if (validacionService.validarTarjeta(tarjeta)) {
            return bancoService.cobrar(tarjeta, monto);
        }
        return false;
    }
}

Dificultades para testing

Otra limitación importante es la complejidad adicional que introduce en las pruebas unitarias. Sin constructores públicos que reciban las dependencias, necesitamos usar reflection o frameworks de testing específicos para inyectar mocks:

public class VentaServiceTest {
    @InjectMocks
    private VentaService ventaService;
    
    @Mock
    private ClienteService clienteService;
    
    @Mock
    private ProductoService productoService;
    
    @Test
    public void testProcesarVenta() {
        // Configurar mocks
        when(clienteService.esClienteActivo("123")).thenReturn(true);
        when(productoService.obtenerPrecio("ABC")).thenReturn(10.0);
        
        // Ejecutar prueba
        ventaService.procesarVenta("123", "ABC", 2);
        
        // Verificar interacciones
        verify(clienteService).esClienteActivo("123");
        verify(productoService).obtenerPrecio("ABC");
    }
}

Detección tardía de errores

Con la inyección por campo, los errores de dependencias faltantes se detectan en tiempo de ejecución, no durante el inicio de la aplicación. Esto puede llevar a fallos inesperados cuando se intenta usar una dependencia que no fue inyectada correctamente:

@Service
public class ConfiguracionService {
    @Autowired
    private DatabaseService databaseService;
    
    @Autowired
    private CacheService cacheService;
    
    public String obtenerConfiguracion(String clave) {
        // Si databaseService no fue inyectado, obtendremos NullPointerException aquí
        String valor = databaseService.buscarConfiguracion(clave);
        
        if (cacheService != null) {
            cacheService.guardar(clave, valor);
        }
        
        return valor;
    }
}

Cuándo usar inyección por campo

La inyección por campo puede ser apropiada en casos específicos donde la simplicidad del código es prioritaria y las limitaciones mencionadas no representan un problema. Es común encontrarla en aplicaciones legacy o en casos donde se requiere compatibilidad con versiones anteriores de Spring:

@Service
public class UtilService {
    @Autowired
    private FormateoService formateoService;
    
    @Autowired
    private ValidacionService validacionService;
    
    public String procesarTexto(String texto) {
        if (validacionService.esTextoValido(texto)) {
            return formateoService.formatear(texto);
        }
        return texto;
    }
}

Recomendaciones de uso

Aunque la inyección por campo es técnicamente válida, la comunidad de Spring y las mejores prácticas actuales recomiendan evitarla en favor de la inyección por constructor para nuevos desarrollos. La inyección por constructor ofrece mejor testabilidad, inmutabilidad y detección temprana de errores.

Sin embargo, es importante conocer la inyección por campo porque es probable que la encuentres en código existente y necesites entender cómo funciona para mantener o migrar aplicaciones que la utilizan.

@Service
public class LegacyService {
    @Autowired
    private DataService dataService;
    
    @Autowired
    private ProcessingService processingService;
    
    // Método que podría necesitar refactoring hacia inyección por constructor
    public void procesarDatos() {
        String datos = dataService.obtenerDatos();
        processingService.procesar(datos);
    }
}

La inyección por campo representa una opción disponible en el ecosistema de Spring, pero su uso debe evaluarse cuidadosamente considerando las implicaciones en mantenibilidad, testabilidad y robustez del código.

Aprendizajes de esta lección

  • Comprender el concepto de inyección de dependencias y la inversión de control.
  • Identificar las ventajas de utilizar inyección de dependencias en el desarrollo de software.
  • Aprender a implementar inyección por constructor, setter y campo en Spring Boot.
  • Conocer las diferencias, ventajas y limitaciones de cada tipo de inyección.
  • Aplicar buenas prácticas para mejorar la mantenibilidad y testabilidad del código mediante inyección de dependencias.

Completa SpringBoot y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración