5 escenarios críticos de errores en la Inyección de Dependencias con Spring Boot

En el ecosistema de Spring Boot, la Inyección de Dependencias (DI) es el pilar que sostiene la arquitectura del software. Sin embargo, es frecuente encontrarse con excepciones del tipo NoSuchBeanDefinitionException o el error expected at least 1 bean which qualifies as autowire candidate. Estos fallos suelen ocurrir cuando el contenedor de Inversión de Control (IoC) no logra localizar o instanciar un componente requerido.

A continuación, analizamos cinco situaciones reales que provocan estos fallos en entornos corporativos y cómo resolverlas eficazmente.

1. Alcance insuficiente del escaneo de componentes en proyectos multimódulo

En arquitecturas distribuidas o proyectos divididos en múltiples módulos Maven/Gradle, es común que la clase principal anotada con @SpringBootApplication se encuentre en un paquete raíz que no cubre a los demás módulos. Por defecto, Spring escanea el paquete donde reside la clase principal y sus subpaquetes.

Escenario: El módulo core-logic tiene el paquete com.sistema.servicios, pero la aplicación principal está en com.sistema.main.

Solución: Se debe expandir explícitamente el rango de escaneo en la configuración inicial:

@SpringBootApplication(scanBasePackages = {
    "com.sistema.main",
    "com.sistema.servicios",
    "com.sistema.repositorios"
})
public class GestionInventarioApplication {
    public static void main(String[] args) {
        SpringApplication.run(GestionInventarioApplication.class, args);
    }
}

2. Beans omitidos por lógica condicional fallida

Spring permite la creación dinámica de beans mediante anotaciones como @ConditionalOnProperty o @ConditionalOnClass. Si la proipedad configurada en el archivo application.properties no coincide exactamente con el valor esperado, el bean no se registrará en el contexto.

Escenario: Un servicio de notificaciones que solo debe activarse si existe una clave de API configurada.

@Configuration
public class NotificacionConfig {
    @Bean
    @ConditionalOnProperty(name = "app.notificaciones.activo", havingValue = "true")
    public ServicioSMS smsService() {
        return new ServicioSMS();
    }
}

Diagnóstico: Si en application.yml la propiedad es false o está ausente, cualquier intento de inyectar ServicioSMS fallará. La solución es usar required = false en el @Autowired o proporcionar una implementación por defecto mediante @ConditionalOnMissingBean.

3. Dependencias circulares en constructores

Aunque Spring puede gestionar dependencias circulares mediante inyección de campos en ciertos casos, el uso de inyección por constructor (altamente recomendada) bloquea el arranque si dos beans se necesitan mutuamente para iniicalizarse.

Escenario: ContabilidadService requiere FacturaService, y este último requiere ContabilidadService para validar estados.

Solución: La mejor práctica es rediseñar para romper el ciclo (usando un tercer servicio o eventos). Si es inevitable, se puede utilizar la anotación @Lazy:

@Service
public class FacturaService {
    private final ContabilidadService contabilidad;

    public FacturaService(@Lazy ContabilidadService contabilidad) {
        this.contabilidad = contabilidad;
    }
}

4. Perfiles (Profiles) no activados

El uso de @Profile es fundamental para separar configuraciones de desarrollo, pruebas y producción. Un error común es definir un bean crítico bajo un perfil específico y olvidar activarlo en el entorno de ejecución.

Escenario: Un DataSource configurado exclusivamente con @Profile("prod").

Solución: Verificar que el argumento de ejecución incluya -Dspring.profiles.active=prod o que el archivo application.properties contenga la clave activa. Para evitar que la aplicación falle en desarrollo, se puede definir un perfil por defecto (default) o usar múltiples perfiles compatibles.

5. Incompatibilidad por Proxies Dinámicos (JDK vs CGLIB)

Cuando se utiliza AOP (Programación Orientada a Aspectos) para transacciones (@Transactional) o seguridad, Spring crea un proxy alrededor del bean. Si se intenta inyectar el bean usando la implementación concreta en lugar de su interfaz, y Spring está configurado para usar Proxies dinámicos de JDK, la inyección fallará.

Escenario:@Autowired private ServicioImpl servicio; fallará si el proxy solo implementa la interfaz Servicio.

Solución: Siempre inyectar dependencias utilizando la interfaz. Alternativamente, se puede forzar el uso de CGLIB (que permite proxies basados en clases) configurando:

# application.properties
spring.aop.proxy-target-class=true

Herramientas de diagnóstico

Para identificar rápidamente cuál de estos problemas afecta al proyecto, se pueden emplear las siguientes técnicas:

  • Actuator: Acceder al endpoint /actuator/beans para visualizar todos los beans cargados.
  • Modo Debug: Iniciar la aplicación con --debug para ver el informe de evaluación de condiciones (Condition Evaluation Report), que explica por qué cada bean condicional fue o no creado.

Etiquetas: spring-boot dependency-injection java software-architecture backend

Publicado el 6-23 21:25