vida contexto context configurar ciclo bean application java spring applicationcontext

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 un BeanFactory 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.

@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.

<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 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 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:

Para lograr esto, Spring tiene dos opciones:

  1. 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.
  2. 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.