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

Resuelto Sotirios Delimanolis asked hace 8 años • 1 respuestas

Explique lo siguiente sobre NoSuchBeanDefinitionExceptionla 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 NoSuchBeanDefinitionExceptionaplicaciones que utilizan Spring.

Sotirios Delimanolis avatar Aug 27 '16 03:08 Sotirios Delimanolis
Aceptado

El javadoc deNoSuchBeanDefinitionException explica.

Se produce una excepción cuando a BeanFactoryse 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 BeanFactoryes 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 Fooni mediante un @Beanmétodo, @Componentescaneo, una definición XML ni de ninguna otra forma. Por lo tanto, el BeanFactoryadministrado por AnnotationConfigApplicationContextno 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 @Autowireddependencia. 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 Foohasta @ComponentScan. Pero Spring no sabe nada de eso Bar. Por lo tanto, no puede encontrar el bean correspondiente al intentar conectar automáticamente el barcampo de la Fooinstancia 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.

  • @Beanmétodo en una @Configurationclase o <bean>en configuración XML
  • @Component(y sus metaanotaciones, por ejemplo @Repository) a través @ComponentScano <context:component-scan ... />en XML
  • manualmente a través deGenericApplicationContext#registerBeanDefinition
  • manualmente a través deBeanDefinitionRegistryPostProcessor

...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 fooy 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 @ImportResourceanotació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 DataSourcebeans 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 @Beanmé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 mysqlbean.

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 @Autowiredse utiliza principalmente para realizar la conexión automática por tipo, @Qualifierle 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. @ResourceTambié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.

@Beantienename

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 idatributo 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).

@Componenty 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 myClassNameen 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 la spring.profiles.activepropiedad 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.activepropiedad 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 NoSuchBeanDefinitionExceptionerror Foo. Como el StackOverflowperfil no estaba activo, el bean no estaba registrado.

En cambio, si inicializo el ApplicationContextmientras 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 @Asyncy@Scheduled

Para lograrlo, Spring tiene dos opciones:

  1. 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.
  2. 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 @EnableAsyncel valor predeterminado proxyTargetClassde 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 HttpClientImplque 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 HttpClientImplfrijol y lo expuso a través de un Proxyobjeto 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 proxyTargetClassen true. Anotaciones similares ( EnableTransactionManagement, etc.) tienen atributos similares. XML también tendrá opciones de configuración equivalentes.

ApplicationContextJerarquías - Spring MVC

Spring te permite crear ApplicationContextinstancias con otras ApplicationContextinstancias 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 @EnableWebMvcanotadas @Configurationo <mvc:annotation-driven />en XML, pero los @Controllerbeans 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.HandlerAdapterViewResolverExceptionResolverDispatcherServletContextLoaderListener

Matrices, colecciones y mapas.

Spring maneja los beans de algunos tipos conocidos de maneras especiales. Por ejemplo, si intentaste inyectar una matriz de MovieCatalogen 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, Listy Collectionde inyección.

Para un Mapobjetivo 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 MovieCatalogy 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 NoSuchBeanDefinitionExceptionporque no hay Foobeans en su contexto. Pero no querías un Foofrijol, 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, @Resourcees 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 @Autowiredalgoritmo de coincidencia de tipos de Spring, siempre que la información del tipo de elemento se conserve en @Beanlas 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 @Beanlos métodos, es decir.

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

Aquí, Spring ignora cualquier método @Resourceo 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 fooListe inyectarlo.

Sotirios Delimanolis avatar Aug 26 '2016 20:08 Sotirios Delimanolis