¿Qué es una NoSuchBeanDefinitionException y cómo la soluciono?

Explique lo siguiente acerca de la excepción NoSuchBeanDefinitionException en Spring:

  • Qué significa eso?
  • ¿Bajo qué condiciones será arrojado?
  • ¿Cómo puedo prevenirlo?

Esta publicación está diseñada para ser una NoSuchBeanDefinitionException de preguntas y respuestas exhaustivas sobre las apariciones de NoSuchBeanDefinitionException en aplicaciones que usan Spring.

El javadoc de NoSuchBeanDefinitionException explica

Excepción lanzada cuando se le pide a BeanFactory una instancia de bean para la cual no puede encontrar una definición. Esto puede apuntar a un bean no existente, un bean no único o una instancia de Singleton registrada manualmente sin una definición de bean asociada.

Una BeanFactory es básicamente la abstracción que representa el contenedor de inversión de control de Spring . Expone frijoles interna y externamente a su aplicación. Cuando no puede encontrar o recuperar estos beans, arroja una NoSuchBeanDefinitionException .

A continuación se presentan razones simples por las que una BeanFactory (o clases relacionadas) no podría encontrar un bean y cómo puede asegurarse de que lo haga.


El bean no existe, no fue registrado

En el siguiente ejemplo

 @Configuration public class Example { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class); ctx.getBean(Foo.class); } } class Foo { } 

no hemos registrado una definición de bean para el tipo Foo ya sea a través de un método @Component , escaneo @Component , una definición XML o de cualquier otra manera. El BeanFactory manejado por el AnnotationConfigApplicationContext por lo tanto no tiene ninguna indicación de dónde obtener el bean solicitado por getBean(Foo.class) . El fragmento de arriba arroja

 Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.example.Foo] is defined 

Del mismo modo, la excepción podría haberse lanzado al intentar satisfacer una dependencia de @Autowired . Por ejemplo,

 @Configuration @ComponentScan public class Example { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class); } } @Component class Foo { @Autowired Bar bar; } class Bar { } 

Aquí, se registra una definición de bean para Foo través de @ComponentScan . Pero Spring no sabe nada de Bar . Por lo tanto, no puede encontrar un bean correspondiente mientras trata de autoboost el campo de la bar de la instancia de Foo Bean. Lanza (nested dentro de una UnsatisfiedDependencyException )

 Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.example.Bar] found for dependency [com.example.Bar]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} 

Existen múltiples formas de registrar definiciones de beans.

  • Método @Bean en una clase @Configuration o en la configuración XML
  • @Component (y sus @Component , por ejemplo, @Repository ) a través de @ComponentScan o en XML
  • Manualmente a través de GenericApplicationContext#registerBeanDefinition
  • Manualmente a través de BeanDefinitionRegistryPostProcessor

…y más.

Asegúrese de que los granos que espera estén registrados correctamente.

Un error común es registrar frijoles varias veces, es decir. mezclando las opciones anteriores para el mismo tipo. Por ejemplo, podría tener

 @Component public class Foo {} 

y una configuración XML con

   

mientras que Java proporciona la anotación @ImportResource .

Se esperaba un frijol de emparejamiento único, pero se encontraron 2 (o más)

Hay momentos en los que necesita varios beans para el mismo tipo (o interfaz). Por ejemplo, su aplicación puede usar dos bases de datos, una instancia de MySQL y una de Oracle. En tal caso, tendría dos beans DataSource para administrar las conexiones a cada uno. Para el ejemplo (simplificado), el siguiente

 @Configuration public class Example { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class); System.out.println(ctx.getBean(DataSource.class)); } @Bean(name = "mysql") public DataSource mysql() { return new MySQL(); } @Bean(name = "oracle") public DataSource oracle() { return new Oracle(); } } interface DataSource{} class MySQL implements DataSource {} class Oracle implements DataSource {} 

tiros

 Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.example.DataSource] is defined: expected single matching bean but found 2: oracle,mysql 

porque ambos beans registrados a través de los métodos @Bean cumplieron con el requisito de BeanFactory#getBean(Class) , es decir. ambos implementan DataSource . En este ejemplo, Spring no tiene ningún mecanismo para diferenciar o priorizar entre los dos. Pero tales mecanismos existen.

Puede usar @Primary (y su equivalente en XML) como se describe en la documentación y en esta publicación . Con este cambio

 @Bean(name = "mysql") @Primary public DataSource mysql() { return new MySQL(); } 

el fragmento anterior no lanzaría la excepción y en su lugar devolvería el frijol mysql .

También puede usar @Qualifier (y su equivalente en XML) para tener más control sobre el proceso de selección de beans, como se describe en la documentación . Mientras que @Autowired se usa principalmente para autocablear por tipo, @Qualifier te permite autocausar por nombre. Por ejemplo,

 @Bean(name = "mysql") @Qualifier(value = "main") public DataSource mysql() { return new MySQL(); } 

ahora podría ser inyectado como

 @Qualifier("main") // or @Qualifier("mysql"), to use the bean name private DataSource dataSource; 

sin problema @Resource también es una opción.

Usando el nombre de bean equivocado

Del mismo modo que existen múltiples formas de registrar frijoles, también hay varias formas de registrarlas.

@Bean tiene name

El nombre de este bean, o si es plural, alias para este bean. Si no se especifica, el nombre del bean es el nombre del método anotado. Si se especifica, el nombre del método se ignora.

tiene el atributo id para representar el identificador único de un bean y el name se puede usar para crear uno o más alias ilegales en un id. (XML).

@Component y sus @Component tienen value

El valor puede indicar una sugerencia para un nombre de componente lógico, que se convertirá en un bean Spring en el caso de un componente autodetectado.

Si no se especifica, un nombre de bean se genera automáticamente para el tipo anotado, generalmente la versión más baja de camel case del nombre del tipo.

@Qualifier , como se mencionó anteriormente, le permite agregar más alias a un bean.

Asegúrese de utilizar el nombre correcto cuando realice el autoenvío por su nombre.


Casos más avanzados

Perfiles

Los perfiles de definición de frijoles le permiten registrar frijoles condicionalmente. @Profile , específicamente,

Indica que un componente es elegible para el registro cuando uno o más perfiles especificados están activos.

Un perfil es una agrupación lógica nombrada que se puede activar mediante progtwigción a través de ConfigurableEnvironment.setActiveProfiles(java.lang.String...) o de forma declarativa estableciendo la propiedad spring.profiles.active como una propiedad del sistema JVM, como una variable de entorno, o como un parámetro de contexto de servlet en web.xml para aplicaciones web. Los perfiles también pueden activarse declarativamente en las pruebas de integración a través de la anotación @ActiveProfiles .

Considere estos ejemplos donde la propiedad spring.profiles.active no está configurada.

 @Configuration @ComponentScan public class Example { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class); System.out.println(Arrays.toString(ctx.getEnvironment().getActiveProfiles())); System.out.println(ctx.getBean(Foo.class)); } } @Profile(value = "StackOverflow") @Component class Foo { } 

Esto no mostrará perfiles activos y arrojará una NoSuchBeanDefinitionException para un frijol Foo . Como el perfil de StackOverflow no estaba activo, el bean no se registró.

En cambio, si inicializo ApplicationContext al registrar el perfil apropiado

 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("StackOverflow"); ctx.register(Example.class); ctx.refresh(); 

el frijol está registrado y puede ser devuelto / inyectado.

Proxy de AOP

Spring usa mucho los proxies para implementar un comportamiento avanzado. Algunos ejemplos incluyen:

  • Gestión de transacciones con @Transactional
  • Almacenamiento en caché con @Cacheable
  • Progtwigción y ejecución asincrónica con @Async y @Scheduled

Para lograr esto, Spring tiene dos opciones:

  1. Utilice la clase Proxy de JDK para crear una instancia de una clase dinámica en tiempo de ejecución que solo implemente las interfaces de su bean y delegue todas las invocaciones de métodos a una instancia de bean real.
  2. Utilice los proxies CGLIB para crear una instancia de una clase dinámica en tiempo de ejecución que implemente interfaces y tipos concretos de su bean objective y delegue todas las invocaciones de métodos a una instancia de bean real.

Tome este ejemplo de proxies JDK (logrado a través del @EnableAsync predeterminado de proxyTargetClass de false )

 @Configuration @EnableAsync public class Example { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class); System.out.println(ctx.getBean(HttpClientImpl.class).getClass()); } } interface HttpClient { void doGetAsync(); } @Component class HttpClientImpl implements HttpClient { @Async public void doGetAsync() { System.out.println(Thread.currentThread()); } } 

Aquí, Spring intenta encontrar un bean de tipo HttpClientImpl que esperamos encontrar porque el tipo está claramente anotado con @Component . Sin embargo, en cambio, obtenemos una excepción

 Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.example.HttpClientImpl] is defined 

Spring envolvió el bean HttpClientImpl y lo expuso a través de un objeto Proxy que solo implementa HttpClient . Entonces podrías recuperarlo con

 ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33 // or @Autowired private HttpClient httpClient; 

Siempre se recomienda progtwigr a las interfaces . Cuando no puede, puede decirle a Spring que use los proxies CGLIB. Por ejemplo, con @EnableAsync , puede establecer proxyTargetClass en true . Anotaciones similares ( EnableTransactionManagement , etc.) tienen atributos similares. XML también tendrá opciones de configuración equivalentes.

Jerarquías de ApplicationContext – Spring MVC

Spring le permite crear instancias de ApplicationContext con otras instancias de ApplicationContext como padres, usando ConfigurableApplicationContext#setParent(ApplicationContext) . Un contexto secundario tendrá acceso a los beans en el contexto principal, pero lo contrario no es cierto. Esta publicación entra en detalles sobre cuándo es útil, particularmente en Spring MVC.

En una aplicación Spring MVC típica, usted define dos contextos: uno para toda la aplicación (la raíz) y otro específicamente para el DispatcherServlet (enrutamiento, métodos de manejo, controladores). Puedes obtener mas detalles aqui:

  • Diferencia entre applicationContext.xml y spring-servlet.xml en Spring Framework

También está muy bien explicado en la documentación oficial, aquí .

Un error común en las configuraciones de Spring MVC es declarar la configuración de WebMVC en el contexto raíz con @EnableWebMvc anotado @Configuration classes o en XML, pero los beans @Controller en el contexto de servlet. Como el contexto raíz no puede alcanzar el contexto del servlet para encontrar ningún grano, no se registran manejadores y todas las solicitudes fallan con 404s. No verá una NoSuchBeanDefinitionException , pero el efecto es el mismo.

Asegúrese de que sus granos estén registrados en el contexto apropiado, es decir. donde los pueden encontrar los beans registrados para WebMVC ( HandlerMapping , HandlerAdapter , ViewResolver , ExceptionResolver , etc.). La mejor solución es aislar adecuadamente los frijoles. El DispatcherServlet es responsable de las solicitudes de enrutamiento y manejo para que todos los beans relacionados entren en su contexto. ContextLoaderListener , que carga el contexto raíz, debe inicializar los beans que necesite el rest de la aplicación: servicios, repositorys, etc.

Arrays, colecciones y mapas

Los frijoles de algunos tipos conocidos son manejados de manera especial por Spring. Por ejemplo, si trataste de inyectar una matriz de MovieCatalog en un campo

 @Autowired private MovieCatalog[] movieCatalogs; 

Spring encontrará todos los beans de tipo MovieCatalog , los envolverá en una matriz e inyectará esa matriz. Esto se describe en la documentación de Spring sobre @Autowired . Se aplica un comportamiento similar a los objectives de inyección Set , List y Collection .

Para un objective de inyección de Map , Spring también se comportará de esta manera si el tipo de clave es String . Por ejemplo, si tiene

 @Autowired private Map movies; 

Spring encontrará todos los beans de tipo MovieCatalog y los agregará como valores a un Map , donde la clave correspondiente será su nombre de bean.

Como se describió anteriormente, si no hay disponibles beans del tipo solicitado, Spring arrojará NoSuchBeanDefinitionException . A veces, sin embargo, solo quieres declarar un bean de estos tipos de colecciones como

 @Bean public List fooList() { return Arrays.asList(new Foo()); } 

e inyectarlos

 @Autowired private List foos; 

En este ejemplo, Spring fallaría con NoSuchBeanDefinitionException porque no hay beans Foo en su contexto. Pero no querías un frijol Foo , querías un frijol List . Antes de la spring 4.3, tendrías que usar @Resource

Para los beans que se definen a sí mismos como una colección / mapa o tipo de matriz, @Resource es una solución excelente, que hace referencia a la colección específica o al bean array por nombre único. Dicho esto, a partir de 4.3 , los tipos de colecciones / mapas y matrices se pueden emparejar a través del algoritmo de coincidencia de tipos @Autowired de Spring, siempre que la información del tipo de elemento se conserve en firmas de tipo de retorno @Bean o jerarquías de herencia de recostackción. En este caso, los valores calificadores se pueden usar para seleccionar entre colecciones del mismo tipo, como se describe en el párrafo anterior.

Esto funciona para constructor, setter e inyección de campo.

 @Resource private List foos; // or since 4.3 public Example(@Autowired List foos) {} 

Sin embargo, fallará para los métodos @Bean , es decir.

 @Bean public Bar other(List foos) { new Bar(foos); } 

Aquí, Spring ignora cualquier @Resource o @Autowired anotando el método, porque es un método @Bean , y por lo tanto no puede aplicar el comportamiento descrito en la documentación. Sin embargo, puede usar Spring Expression Language (SpEL) para referirse a beans por su nombre. En el ejemplo anterior, podrías usar

 @Bean public Bar other(@Value("#{fooList}") List foos) { new Bar(foos); } 

para referirse al bean llamado fooList e inyectar eso.