¿Qué es una excepción NoSuchBeanDefinitionException y cómo la soluciono?
Explique lo siguiente sobre NoSuchBeanDefinitionException
la excepción en Spring:
- ¿Qué significa?
- ¿En qué condiciones será arrojado?
- ¿Cómo puedo prevenirlo?
Esta publicación está diseñada para ser una sesión completa de preguntas y respuestas sobre la aparición de NoSuchBeanDefinitionException
aplicaciones que utilizan Spring.
El javadoc deNoSuchBeanDefinitionException
explica.
Se produce una excepción cuando a
BeanFactory
se le solicita 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 singleton registrada manualmente sin una definición de bean asociada.
A BeanFactory
es básicamente la abstracción que representa el contenedor de inversión de control de Spring . Expone beans interna y externamente a su aplicación. Cuando no puede encontrar o recuperar estos beans, lanza un archivo NoSuchBeanDefinitionException
.
A continuación se detallan razones simples por las cuales una BeanFactory
(o clases relacionadas) no podrían encontrar un bean y cómo puede asegurarse de que lo haga.
El frijol 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
ni mediante un @Bean
método, @Component
escaneo, una definición XML ni de ninguna otra forma. Por lo tanto, el BeanFactory
administrado por AnnotationConfigApplicationContext
no tiene ninguna indicación de dónde conseguir el frijol 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
De manera similar, la excepción podría haberse lanzado al intentar satisfacer una @Autowired
dependencia. 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 Foo
hasta @ComponentScan
. Pero Spring no sabe nada de eso Bar
. Por lo tanto, no puede encontrar el bean correspondiente al intentar conectar automáticamente el bar
campo de la Foo
instancia del bean. Lanza (anidado dentro de un 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)}
Hay varias formas de registrar definiciones de beans.
@Bean
método en una@Configuration
clase o<bean>
en configuración XML@Component
(y sus metaanotaciones, por ejemplo@Repository
) a través@ComponentScan
o<context:component-scan ... />
en XML- manualmente a través de
GenericApplicationContext#registerBeanDefinition
- manualmente a través de
BeanDefinitionRegistryPostProcessor
...y más.
Asegúrese de que los frijoles que espera estén registrados correctamente.
Un error común es registrar beans 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
<context:component-scan base-packages="com.example" />
<bean name="eg-different-name" class="com.example.Foo />
Tal configuración registraría dos beans de tipo Foo
, uno con nombre foo
y otro con nombre eg-different-name
. Asegúrese de no registrar accidentalmente más beans de los que deseaba. Lo que nos lleva a...
Si utiliza configuraciones XML y basadas en anotaciones, asegúrese de importar una desde la otra. XML proporciona
<import resource=""/>
mientras que Java proporciona la @ImportResource
anotación.
Se esperaba un solo frijol coincidente, pero se encontraron 2 (o más)
Hay ocasiones en las que necesitas varios beans para el mismo tipo (o interfaz). Por ejemplo, su aplicación puede utilizar dos bases de datos, una instancia de MySQL y otra de Oracle. En tal caso, tendría dos DataSource
beans para administrar las conexiones a cada uno. Por ejemplo (simplificado), lo 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 {}
lanza
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 mediante @Bean
métodos cumplían 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.
Podrías 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 generaría la excepción y en su lugar devolvería el mysql
bean.
También puedes 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 . Si bien @Autowired
se utiliza principalmente para realizar la conexión automática por tipo, @Qualifier
le permite realizar la conexión automáticamente por nombre. Por ejemplo,
@Bean(name = "mysql")
@Qualifier(value = "main")
public DataSource mysql() { return new MySQL(); }
ahora podría inyectarse como
@Qualifier("main") // or @Qualifier("mysql"), to use the bean name
private DataSource dataSource;
sin problema. @Resource
También es una opción.
Usando un nombre de frijol incorrecto
Así como existen múltiples formas de registrar beans, también existen múltiples formas de nombrarlos.
@Bean
tienename
El nombre de este bean o, en caso de plural, alias de este bean. Si no se especifica, el nombre del bean es el nombre del método anotado. Si se especifica, se ignora el nombre del método.
<bean>
tiene el id
atributo de representar el identificador único de un bean y name
se puede usar para crear uno o más alias ilegales en una identificación (XML).
@Component
y sus metanotaciones tienenvalue
El valor puede indicar una sugerencia para un nombre de componente lógico, que se convertirá en un Spring Bean en caso de que se detecte automáticamente un componente.
Si no se especifica, se genera automáticamente un nombre de bean para el tipo anotado, normalmente la versión en minúsculas camel del nombre del tipo. Por ejemplo MyClassName
, se convierte myClassName
en su nombre de frijol. Los nombres de los frijoles distinguen entre mayúsculas y minúsculas. También tenga en cuenta que normalmente se producen nombres/mayúsculas incorrectos en beans a los que se hace referencia mediante @DependsOn("my BeanName")
archivos de configuración tipo cadena o XML.
@Qualifier
, como se mencionó anteriormente, le permite agregar más alias a un bean.
Asegúrese de utilizar el nombre correcto cuando se refiera a un frijol.
Casos más avanzados
Perfiles
Los perfiles de definición de beans le permiten registrar beans de forma condicional. @Profile
, específicamente,
Indica que un componente es elegible para registrarse cuando uno o más perfiles específicos están activos.
Un perfil es una agrupación lógica con nombre que se puede activar mediante programación
ConfigurableEnvironment.setActiveProfiles(java.lang.String...)
o mediante declaración estableciendo laspring.profiles.active
propiedad 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 se pueden activar de forma declarativa en las pruebas de integración mediante la@ActiveProfiles
anotación.
Considere estos ejemplos en los que la spring.profiles.active
propiedad no está establecida.
@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á un NoSuchBeanDefinitionException
error Foo
. Como el StackOverflow
perfil no estaba activo, el bean no estaba registrado.
En cambio, si inicializo el ApplicationContext
mientras registro el perfil apropiado
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("StackOverflow");
ctx.register(Example.class);
ctx.refresh();
el grano está registrado y puede devolverse/inyectarse.
Proxys AOP
Spring usa mucho proxies AOP para implementar comportamientos avanzados. Algunos ejemplos incluyen:
- Gestión de transacciones con
@Transactional
- Almacenamiento en caché con
@Cacheable
- Programación y ejecución asincrónica con
@Async
y@Scheduled
Para lograrlo, Spring tiene dos opciones:
- Utilice la clase Proxy del JDK para crear una instancia de una clase dinámica en tiempo de ejecución que solo implemente las interfaces de su bean y delega todas las invocaciones de métodos a una instancia de bean real.
- Utilice proxies CGLIB para crear una instancia de una clase dinámica en tiempo de ejecución que implemente tanto interfaces como tipos concretos de su bean objetivo y delega todas las invocaciones de métodos a una instancia de bean real.
Tome este ejemplo de servidores proxy JDK (logrado mediante @EnableAsync
el valor predeterminado 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 del tipo HttpClientImpl
que esperamos encontrar porque el tipo está claramente anotado con @Component
. Sin embargo, en su lugar, 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 HttpClientImpl
frijol y lo expuso a través de un Proxy
objeto que solo implementa HttpClient
. Para que puedas recuperarlo con
ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;
Siempre se recomienda programar para interfaces . Cuando no pueda, puede decirle a Spring que use proxies CGLIB. Por ejemplo, con @EnableAsync
, puedes configurarlo proxyTargetClass
en true
. Anotaciones similares ( EnableTransactionManagement
, etc.) tienen atributos similares. XML también tendrá opciones de configuración equivalentes.
ApplicationContext
Jerarquías - Spring MVC
Spring te permite crear ApplicationContext
instancias con otras ApplicationContext
instancias como padres, usando ConfigurableApplicationContext#setParent(ApplicationContext)
. Un contexto hijo tendrá acceso a los beans en el contexto padre, pero no ocurre lo contrario. Esta publicación detalla cuándo esto es útil, particularmente en Spring MVC.
En una aplicación Spring MVC típica, se definen dos contextos: uno para toda la aplicación (la raíz) y otro específicamente para 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 WebMVC en el contexto raíz con clases @EnableWebMvc
anotadas @Configuration
o <mvc:annotation-driven />
en XML, pero los @Controller
beans en el contexto del servlet. Dado que el contexto raíz no puede acceder al contexto del servlet para encontrar beans, no se registra ningún controlador y todas las solicitudes fallan con 404. No verás un NoSuchBeanDefinitionException
, pero el efecto es el mismo.
Asegúrese de que sus beans estén registrados en el contexto apropiado, es decir. donde pueden ser encontrados por los beans registrados para WebMVC ( ,,,, etc. HandlerMapping
) . La mejor solución es aislar adecuadamente los frijoles. Es responsable de enrutar y manejar las solicitudes, por lo que todos los beans relacionados deben entrar en su contexto. El , que carga el contexto raíz, debe inicializar los beans que el resto de su aplicación necesita: servicios, repositorios, etc.HandlerAdapter
ViewResolver
ExceptionResolver
DispatcherServlet
ContextLoaderListener
Matrices, colecciones y mapas.
Spring maneja los beans de algunos tipos conocidos de maneras especiales. Por ejemplo, si intentaste 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 que analiza@Autowired
. Se aplica un comportamiento similar a los objetivos Set
, List
y Collection
de inyección.
Para un Map
objetivo de inyección, Spring también se comportará de esta manera si el tipo de clave es String
. Por ejemplo, si tienes
@Autowired
private Map<String, MovieCatalog> movies;
Spring encontrará todos los beans de tipo MovieCatalog
y los agregará como valores a a Map
, donde la clave correspondiente será el nombre de su bean.
Como se describió anteriormente, si no hay beans del tipo solicitado disponibles, Spring generará un archivo NoSuchBeanDefinitionException
. A veces, sin embargo, sólo desea declarar un bean de estos tipos de colección como
@Bean
public List<Foo> fooList() {
return Arrays.asList(new Foo());
}
e inyectarlos
@Autowired
private List<Foo> foos;
En este ejemplo, Spring fallaría con a NoSuchBeanDefinitionException
porque no hay Foo
beans en su contexto. Pero no querías un Foo
frijol, querías un List<Foo>
frijol. Antes de Spring 4.3, tendrías que usar@Resource
Para los beans que están definidos como una colección/mapa o un tipo de matriz,
@Resource
es una buena solución hacer referencia a la colección o al bean de matriz específico por un nombre único. Dicho esto, a partir de 4.3 , los tipos de colección/mapa y matriz también se pueden comparar a través del@Autowired
algoritmo de coincidencia de tipos de Spring, siempre que la información del tipo de elemento se conserve en@Bean
las firmas de tipos de retorno o en las jerarquías de herencia de colecciones. En este caso, los valores calificadores se pueden utilizar para seleccionar entre colecciones del mismo tipo, como se describe en el párrafo anterior.
Esto funciona para constructor, configurador e inyección de campo.
@Resource
private List<Foo> foos;
// or since 4.3
public Example(@Autowired List<Foo> foos) {}
Sin embargo, fallará en @Bean
los métodos, es decir.
@Bean
public Bar other(List<Foo> foos) {
new Bar(foos);
}
Aquí, Spring ignora cualquier método @Resource
o anota, porque es un método y, por lo tanto, no puede aplicar el comportamiento descrito en la documentación. Sin embargo, puede utilizar Spring Expression Language (SpEL) para referirse a los beans por su nombre. En el ejemplo anterior, podría utilizar@Autowired
@Bean
@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
new Bar(foos);
}
para referirse al frijol nombrado fooList
e inyectarlo.