java - contexto - file application context spring
¿Qué es una NoSuchBeanDefinitionException y cómo la soluciono? (1)
El
javadoc de
NoSuchBeanDefinitionException
explica
Se
BeanFactory
una excepción cuando se solicita a unBeanFactory
una instancia de bean para la que 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.
Un
BeanFactory
es básicamente la abstracción que representa
el contenedor de Inversión de Control de Spring
.
Expone los beans internos y externos 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 un
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
a través de un método
@Component
, escaneo
@Component
, una definición XML o de cualquier otra manera.
BeanFactory
administrado por
AnnotationConfigApplicationContext
por lo tanto, no tiene 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 producido al intentar satisfacer una dependencia
@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í, una definición de bean se registra para
Foo
través de
@ComponentScan
.
Pero Spring no sabe nada de
Bar
.
Por lo tanto, no puede encontrar un bean correspondiente al intentar conectar automáticamente el campo de
bar
de la instancia del bean
Foo
.
Se lanza (anidado 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)}
Hay varias formas de registrar definiciones de frijol.
-
Método
@Bean
en una clase@Configuration
o<bean>
en configuración XML -
@Component
(y sus@Component
, por ejemplo,@Repository
) a través de@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 debidamente registrados.
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 />
Dicha configuración registraría dos beans de tipo
Foo
, uno con el nombre
foo
y otro con el nombre
eg-different-name
.
Asegúrate de no registrar accidentalmente más frijoles de los que querías.
Lo que nos lleva a ...
Si está utilizando configuraciones basadas en anotaciones y XML, asegúrese de importar una de la otra. XML proporciona
<import resource=""/>
mientras que Java proporciona la anotación
@ImportResource
.
Se esperaba un solo bean coincidente, pero se encontraron 2 (o más)
Hay momentos en que necesita múltiples 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.
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 {}
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
satisfací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.
Puede usar
@Primary
(y su equivalente en XML) como se describe en la
documentation
y en
esta publicación
.
Con este cambio
@Bean(name = "mysql")
@Primary
public DataSource mysql() { return new MySQL(); }
el fragmento anterior no arrojaría la excepción y en su lugar devolvería el bean
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
documentation
.
Si bien
@Autowired
se usa principalmente para autoalambrar por tipo,
@Qualifier
permite autoalambrar 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.
Usar un nombre de bean incorrecto
Así como hay múltiples formas de registrar beans, también hay múltiples formas de nombrarlos.
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.
<bean>
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 meta anotaciones 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, se genera automáticamente un nombre de bean para el tipo anotado, generalmente la versión en minúscula del nombre del tipo.
@Qualifier
, como se mencionó anteriormente, le permite agregar más alias a un bean.
Asegúrese de usar el nombre correcto cuando realice el cableado automático por nombre.
Casos más avanzados
Perfiles
Los perfiles de definición de bean le
permiten registrar beans 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 con nombre que puede activarse mediante programación a través de
ConfigurableEnvironment.setActiveProfiles(java.lang.String...)
o declarativamente estableciendo la propiedadspring.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 pruebas de integración a través de la anotación@ActiveProfiles
.
Considere estos ejemplos donde la propiedad
spring.profiles.active
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á ningún perfil activo y arrojará una
NoSuchBeanDefinitionException
para un bean
Foo
.
Como el perfil
StackOverflow
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 bean está registrado y puede ser devuelto / inyectado.
Proxies AOP
Spring usa muchos proxys AOP para implementar un comportamiento avanzado. Algunos ejemplos incluyen:
-
Gestión de transacciones
con
@Transactional
-
Caching
en
Caching
con
@Cacheable
-
Programación y ejecución asincrónica
con
@Async
y@Scheduled
Para lograr esto, Spring tiene dos opciones:
- Use 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.
- Use 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 de destino y delegue todas las invocaciones de métodos a una instancia de bean real.
Tome este ejemplo de proxys 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, tenemos 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
.
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 en interfaces
.
Cuando no puede, puede decirle a Spring que use 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
ApplicationContext
- Spring MVC
Spring le permite construir instancias de
ApplicationContext
con otras instancias de
ApplicationContext
como padres, utilizando
ConfigurableApplicationContext#setParent(ApplicationContext)
.
Un contexto hijo tendrá acceso a beans en el contexto padre, pero lo contrario no es cierto.
Esta publicación
entra en detalles sobre cuándo esto 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
DispatcherServlet
(enrutamiento, métodos de manejo, controladores).
Puedes obtener mas detalles aqui:
También está muy bien explicado en la documentación oficial, here .
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
<mvc:annotation-driven />
en XML, pero los beans
@Controller
en el contexto del servlet.
Como el contexto raíz no puede alcanzar el contexto de servlet para encontrar beans, no se registran controladores y todas las solicitudes fallan con 404s.
No verá una
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 (
HandlerMapping
,
HandlerAdapter
,
ViewResolver
,
ExceptionResolver
, etc.).
La mejor solución es aislar adecuadamente los frijoles.
El
DispatcherServlet
es responsable de enrutar y manejar las solicitudes, por lo que todos los beans relacionados deben entrar en su contexto.
ContextLoaderListener
, que carga el contexto raíz, debe inicializar cualquier
ContextLoaderListener
que el resto de su aplicación necesite: servicios, repositorios, etc.
Matrices, colecciones y mapas.
Los frijoles de algunos tipos conocidos son manejados de manera especial por Spring.
Por ejemplo, si intentó 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
.
Un comportamiento similar se aplica a los objetivos de inyección
Set
,
List
y
Collection
.
Para un objetivo de inyección de
Map
, 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 un
Map
, donde la clave correspondiente será su nombre de bean.
Como se describió anteriormente, si no hay beans disponibles del tipo solicitado, Spring lanzará una
NoSuchBeanDefinitionException
.
A veces, sin embargo, solo 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 una
NoSuchBeanDefinitionException
porque no hay
NoSuchBeanDefinitionException
beans en su contexto.
Pero no querías un bean
Foo
, querías un bean
List<Foo>
.
documentation
Para los beans que se definen a sí mismos como un tipo de colección / mapa o matriz,
@Resource
es una buena solución, que se refiere a la colección específica o al bean de matriz por nombre único. Dicho esto, a partir de 4.3 , los tipos de colección / mapa y matriz se pueden combinar a través del algoritmo de coincidencia de tipos@Autowired
de Spring, siempre que la información del tipo de elemento se conserve en las firmas de tipo de retorno@Bean
o en las jerarquías de herencia de colección. En este caso, los valores del calificador 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<Foo> foos;
// or since 4.3
public Example(@Autowired List<Foo> foos) {}
Sin embargo, fallará para los métodos
@Bean
, es decir.
@Bean
public Bar other(List<Foo> foos) {
new Bar(foos);
}
Aquí, Spring ignora cualquier
@Resource
o
@Autowired
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 los beans por su nombre.
En el ejemplo anterior, podría usar
@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
new Bar(foos);
}
para referirse al bean llamado
fooList
e inyectar eso.
Explique lo siguiente sobre 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 un Q&A completo sobre las ocurrencias de
NoSuchBeanDefinitionException
en aplicaciones que usan Spring.