O que é um NoSuchBeanDefinitionException e como faço para corrigir isso?

Por favor, explique o seguinte sobre a exceção NoSuchBeanDefinitionException no Spring:

  • O que isso significa?
  • Em que condições será lançada?
  • Como posso evitar isso?

Este post foi projetado para ser um abrangente Q & A sobre ocorrências de NoSuchBeanDefinitionException em aplicativos usando Spring.

O javadoc de NoSuchBeanDefinitionException explica

Exceção lançada quando uma BeanFactory é solicitada por uma instância de bean para a qual não consegue encontrar uma definição. Isso pode apontar para um bean não existente, um bean não-exclusivo ou uma instância singleton registrada manualmente sem uma definição de bean associada.

Um BeanFactory é basicamente a abstração representando o container Inversion of Control do Spring . Ele expõe os beans interna e externamente ao seu aplicativo. Quando não consegue encontrar ou recuperar esses beans, lança uma NoSuchBeanDefinitionException .

Abaixo estão os motivos simples pelos quais uma BeanFactory (ou classs relacionadas) não seria capaz de encontrar um bean e como você pode ter certeza de que isso acontece.


O feijão não existe, não foi registrado

No exemplo abaixo

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

não registramos uma definição de bean para o tipo Foo por meio de um método @Component , de uma varredura de @Component , de uma definição XML ou de qualquer outra forma. O BeanFactory gerenciado pelo AnnotationConfigApplicationContext portanto, não tem nenhuma indicação de onde obter o bean solicitado pelo getBean(Foo.class) . O trecho acima lança

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

Da mesma forma, a exceção poderia ter sido lançada ao tentar satisfazer uma dependência @Autowired . Por exemplo,

 @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 { } 

Aqui, uma definição de bean é registrada para Foo através de @ComponentScan . Mas a primavera não conhece nada de Bar . Portanto, ele não consegue encontrar um bean correspondente ao tentar autowire o campo da bar da instância do bean Foo . Ele lança (nested dentro de um 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)} 

Existem várias maneiras de registrar definições de bean.

  • Método @Bean em uma class @Configuration ou na configuração XML
  • @Component (e suas meta-annotations, por exemplo, @Repository ) através de @ComponentScan ou em XML
  • Manualmente através de GenericApplicationContext#registerBeanDefinition
  • Manualmente por meio de BeanDefinitionRegistryPostProcessor

…e mais.

Certifique-se de que os grãos esperados estejam registrados corretamente.

Um erro comum é registrar beans várias vezes, ex. misturando as opções acima para o mesmo tipo. Por exemplo, eu poderia ter

 @Component public class Foo {} 

e uma configuração XML com

   

enquanto o Java fornece a anotação @ImportResource .

Esperado único bean correspondente, mas encontrado 2 (ou mais)

Há momentos em que você precisa de vários beans para o mesmo tipo (ou interface). Por exemplo, seu aplicativo pode usar dois bancos de dados, uma instância do MySQL e um Oracle. Nesse caso, você teria dois beans DataSource para gerenciar conexões para cada um deles. Para exemplo (simplificado), os seguintes

 @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 {} 

lança

 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 os beans registrados através @Bean methods @Bean satisfizeram o requisito de BeanFactory#getBean(Class) , ie. ambos implementam o DataSource . Neste exemplo, o Spring não possui um mecanismo para diferenciar ou priorizar os dois. Mas tais mecanismos existem.

Você poderia usar @Primary (e seu equivalente em XML) conforme descrito na documentação e neste post . Com esta mudança

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

o trecho anterior não lançaria a exceção e, em vez disso, retornaria o bean mysql .

Você também pode usar @Qualifier (e seu equivalente em XML) para ter mais controle sobre o processo de seleção de beans, conforme descrito na documentação . Enquanto o @Autowired é usado principalmente para copiar por tipo, o @Qualifier permite que você autowire pelo nome. Por exemplo,

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

agora poderia ser injetado como

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

sem problema. @Resource também é uma opção.

Usando o nome do bean errado

Assim como existem várias maneiras de registrar beans, também há várias maneiras de nomeá-los.

@Bean tem name

O nome desse bean ou, se plural, aliases para esse bean. Se não for especificado, o nome do bean será o nome do método anotado. Se especificado, o nome do método é ignorado.

tem o atributo id para representar o identificador exclusivo de um bean e o name pode ser usado para criar um ou mais aliases ilegais em um ID (XML).

@Component e suas meta annotations têm value

O valor pode indicar uma sugestão para um nome de componente lógico, para ser transformado em um bean Spring no caso de um componente autodetectado.

Se isso não for especificado, um nome de bean será gerado automaticamente para o tipo anotado, geralmente a versão de caso de camelo inferior do nome do tipo.

@Qualifier , como mencionado anteriormente, permite adicionar mais aliases a um bean.

Certifique-se de usar o nome certo ao autowiring por nome.


Casos mais avançados

Perfis

Perfis de definição de bean permitem que você registre beans condicionalmente. @Profile , especificamente,

Indica que um componente é elegível para registro quando um ou mais perfis especificados estão ativos.

Um perfil é um agrupamento lógico nomeado que pode ser ativado programaticamente via ConfigurableEnvironment.setActiveProfiles(java.lang.String...) ou declarativamente, definindo a propriedade spring.profiles.active como uma propriedade do sistema JVM, como uma variável de ambiente ou como um parâmetro de contexto Servlet no web.xml para aplicativos da web. Os perfis também podem ser ativados declarativamente em testes de integração por meio da anotação @ActiveProfiles .

Considere estes exemplos em que a propriedade spring.profiles.active não 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 { } 

Isso mostrará nenhum perfil ativo e lançará uma NoSuchBeanDefinitionException para um bean Foo . Como o perfil StackOverflow não estava ativo, o bean não estava registrado.

Em vez disso, se eu inicializar o ApplicationContext ao registrar o perfil apropriado

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

o feijão é registrado e pode ser devolvido / injetado.

Proxies AOP

Spring usa muito proxies do AOP para implementar um comportamento avançado. Alguns exemplos incluem:

  • Gerenciamento de transactions com @Transactional
  • Cache com @Cacheable
  • Agendamento e execução assíncrona com @Async e @Scheduled

Para conseguir isso, o Spring tem duas opções:

  1. Use a class Proxy do JDK para criar uma instância de uma class dinâmica em tempo de execução que apenas implemente as interfaces do seu bean e delega todas as chamadas de método a uma instância real do bean.
  2. Use proxies CGLIB para criar uma instância de uma class dinâmica no tempo de execução que implementa as interfaces e os tipos concretos de seu bean de destino e delega todas as chamadas de método a uma instância de bean real.

Veja este exemplo de proxies do JDK (obtidos por @EnableAsync do proxyTargetClass de false do proxyTargetClass )

 @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()); } } 

Aqui, Spring tenta encontrar um bean do tipo HttpClientImpl que esperamos encontrar, porque o tipo é claramente anotado com @Component . No entanto, em vez disso, temos uma exceção

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

Spring HttpClientImpl bean HttpClientImpl e o expôs por meio de um object Proxy que implementa apenas o HttpClient . Então você pode recuperá-lo com

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

É sempre recomendado programar para interfaces . Quando você não pode, você pode dizer ao Spring para usar proxies CGLIB. Por exemplo, com @EnableAsync , você pode definir proxyTargetClass como true . Anotações semelhantes ( EnableTransactionManagement , etc.) têm atributos semelhantes. O XML também terá opções de configuração equivalentes.

Hierarquias do ApplicationContext – Spring MVC

O Spring permite criar instâncias do ApplicationContext com outras instâncias do ApplicationContext como pais, usando ConfigurableApplicationContext#setParent(ApplicationContext) . Um contexto filho terá access a beans no contexto pai, mas o oposto não é verdadeiro. Este post entra em detalhes sobre quando isso é útil, particularmente no Spring MVC.

Em um aplicativo Spring MVC típico, você define dois contextos: um para o aplicativo inteiro (a raiz) e um especificamente para o DispatcherServlet (roteamento, methods do manipulador, controladores). Você pode obter mais detalhes aqui:

  • Diferença entre applicationContext.xml e spring-servlet.xml no Spring Framework

Também está muito bem explicado na documentação oficial, aqui .

Um erro comum nas configurações do Spring MVC é declarar a configuração do WebMVC no contexto raiz com as classs @Configuration @EnableWebMvc anotadas ou em XML, mas os beans @Controller no contexto do servlet. Como o contexto raiz não pode chegar ao contexto de servlet para encontrar nenhum bean, nenhum manipulador é registrado e todos os pedidos falham com 404s. Você não verá uma NoSuchBeanDefinitionException , mas o efeito é o mesmo.

Certifique-se de que seus beans estejam registrados no contexto apropriado, por exemplo. onde eles podem ser encontrados pelos beans registrados para WebMVC ( HandlerMapping , HandlerAdapter , ViewResolver , ExceptionResolver , etc.). A melhor solução é isolar adequadamente os grãos. O DispatcherServlet é responsável pelo roteamento e tratamento de solicitações para que todos os beans relacionados entrem em seu contexto. O ContextLoaderListener , que carrega o contexto raiz, deve inicializar todos os beans que o restante do seu aplicativo precisa: serviços, repositorys, etc.

Matrizes, collections e mapas

Feijões de alguns tipos conhecidos são manipulados de maneiras especiais pelo Spring. Por exemplo, se você tentou injetar uma matriz de MovieCatalog em um campo

 @Autowired private MovieCatalog[] movieCatalogs; 

O Spring encontrará todos os beans do tipo MovieCatalog , MovieCatalog os em um array e injetará esse array. Isso é descrito na documentação do Spring discutindo @Autowired . Comportamento semelhante se aplica a destinos de injeção Set , List e Collection .

Para um destino de injeção de Map , o Spring também se comportará dessa maneira se o tipo de chave for String . Por exemplo, se você tem

 @Autowired private Map movies; 

O Spring encontrará todos os beans do tipo MovieCatalog e os adicionará como valores a um Map , onde a chave correspondente será o nome do bean.

Como descrito anteriormente, se nenhum bean do tipo solicitado estiver disponível, o Spring lançará uma NoSuchBeanDefinitionException . Às vezes, no entanto, você só quer declarar um bean desses tipos de coleção como

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

e injetá-los

 @Autowired private List foos; 

Neste exemplo, o Spring falharia com um NoSuchBeanDefinitionException porque não há beans Foo no seu contexto. Mas você não queria um feijão Foo , você queria um bean List . Antes do Spring 4.3, você teria que usar @Resource

Para beans que são definidos como um tipo de coleção / mapa ou matriz, @Resource é uma boa solução, referindo-se à coleção específica ou ao bean de matriz pelo nome exclusivo. Dito isso, a partir de 4.3 , os tipos collection / map e array podem ser combinados através do algoritmo de correspondência de tipo @Autowired da Spring, desde que as informações de tipo de elemento sejam preservadas em assinaturas de tipo de retorno @Bean ou hierarquias de inheritance de coleção. Nesse caso, os valores do qualificador podem ser usados ​​para selecionar entre as collections do mesmo tipo, conforme descrito no parágrafo anterior.

Isso funciona para o construtor, setter e injeção de campo.

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

No entanto, falhará @Bean methods @Bean , ie.

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

Aqui, Spring ignora qualquer anotação @Autowired ou @Autowired do método, porque é um método @Bean e, portanto, não pode aplicar o comportamento descrito na documentação. No entanto, você pode usar o Spring Expression Language (SpEL) para se referir a beans pelo nome. No exemplo acima, você poderia usar

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

para se referir ao bean chamado fooList e injetar isso.

    Intereting Posts