什么是NoSuchBeanDefinitionException,如何解决?

请在Spring中解释有关NoSuchBeanDefinitionExceptionexception的以下内容:

  • 这是什么意思?
  • 在什么情况下会被抛出?
  • 我怎样才能防止它?

这篇文章是针对使用Spring的应用程序中关于NoSuchBeanDefinitionException一个全面的问答。

NoSuchBeanDefinitionException的javadoc解释

当一个BeanFactory被请求一个bean实例,它不能find一个定义时抛出exception。 这可能指向一个不存在的bean,一个非唯一的bean,或一个没有关联的bean定义的手动注册的单例实例。

BeanFactory基本上是表示Spring的Inversion of Control容器的抽象。 它将内部和外部的bean公开给你的应用程序。 当它找不到或检索这些bean时,会抛出一个NoSuchBeanDefinitionException

下面是简单的原因,为什么一个BeanFactory (或相关的类)将无法find一个bean,以及如何确保它。


这个bean不存在,它没有注册

在下面的例子中

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

我们还没有通过@Bean方法, @Component @Bean扫描,XML定义或任何其他方式注册types为Foo的bean定义。 因此,由AnnotationConfigApplicationContextpipe理的BeanFactory没有指示getBean(Foo.class)请求的bean的位置。 上面的代码片段抛出

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

同样,可以在尝试满足@Autowired依赖时引发exception。 例如,

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

在这里,通过@ComponentScanFoo注册一个bean定义。 但是Spring对Bar一无所知。 因此,当尝试自动装载Foo bean实例的bar ,它无法find对应的bean。 它抛出(嵌套在一个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)} 

有多种方法来注册bean定义。

  • @Configuration类中的@Bean方法或XMLconfiguration中的<bean>
  • 在XML中通过@ComponentScan<context:component-scan ... /> @Component (及其元注释,例如@Repository
  • 手动通过GenericApplicationContext#registerBeanDefinition
  • 手动通过BeanDefinitionRegistryPostProcessor

…和更多。

确保你期望的豆子被正确地注册了。

一个常见的错误是多次注册bean,即。 混合上面的选项相同的types。 例如,我可能有

 @Component public class Foo {} 

和一个XMLconfiguration

 <context:component-scan base-packages="com.example" /> <bean name="eg-different-name" class="com.example.Foo /> 

这种configuration会注册两个Footypes的bean,一个名称为foo ,另一个名称为eg-different-name 。 确保你不会无意中注册比你想要的更多的豆。 这导致我们…

如果您使用XML和基于注释的configuration,请确保从另一个导入。 XML提供

 <import resource=""/> 

而Java提供了@ImportResource注解。

预计单个匹配的bean,但发现2(或更多)

有时候你需要同一types(或接口)的多个bean。 例如,您的应用程序可能使用两个数据库,一个MySQL实例和一个Oracle实例。 在这种情况下,您将有两个DataSource bean来pipe理每个bean的连接。 对于(简化)的例子,下面

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

 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 

因为通过@Bean方法注册的两个bean都满足了BeanFactory#getBean(Class) ,即。 他们都实现了DataSource 。 在这个例子中,Spring没有机制来区分或优先考虑两者。 但是这样的机制存在。

您可以按照文档和本文中所述使用@Primary (及其在XML中的等价物)。 随着这个变化

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

前面的代码片段不会抛出exception,而是返回mysql bean。

您也可以使用@Qualifier (及其在XML中的等价物)来更好地控制beanselect过程,如文档中所述。 虽然@Autowired主要用于按types自动assembly,但@Qualifier允许您按名称自动assembly。 例如,

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

现在可以注入为

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

没有问题。 @Resource也是一个选项。

使用错误的bean名称

就像有多种注册bean的方式一样,也有多种方法来命名它们。

@Beanname

这个bean的名字,或者是这个bean的复数,别名。 如果未指定,则bean的名称是注释方法的名称。 如果指定,方法名称将被忽略。

<bean>具有id属性来表示bean的唯一标识符name 可以用来在(XML)id中创build一个或多个非法的别名。

@Component和它的元注释value

该值可能表示对逻辑组件名称的build议,在自动检测到组件的情况下将其转换为Spring bean。

如果没有指定,则自动为注释types生成一个bean名称,通常是types名称的较低骆驼大小版本。

如前所述,@ @Qualifier可以让你添加更多的别名到bean。

确保按名称自动assembly时使用正确的名称。


更高级的情况

简介

Bean定义configuration文件允许您有条件地注册Bean。 @Profile ,具体来说,

表示当一个或多个指定的configuration文件处于活动状态时,组件可以注册。

configuration文件是一个命名的逻辑分组,可以通过ConfigurableEnvironment.setActiveProfiles(java.lang.String...)以编程方式激活,也可以通过将spring.profiles.active属性设置为JVM系统属性,环境variables或Web.xml中的Web应用程序的Servlet上下文参数。 configuration文件也可以在集成testing中通过@ActiveProfiles批注声明性地激活。

考虑一下spring.profiles.active属性没有设置的例子。

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

这将显示没有活动的configuration文件,并为Foo bean引发NoSuchBeanDefinitionException 。 由于StackOverflowconfiguration文件未处于活动状态,因此该Bean未注册。

相反,如果我注册适当的configuration文件时初始化ApplicationContext

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

这个bean被注册并且可以被返回/注入。

AOP代理

Spring使用AOP代理来实现高级行为。 一些例子包括:

  • 使用@Transactional 事务pipe理
  • @Cacheable caching
  • 使用@Async@Scheduled 计划和asynchronous执行

为了达到这个目标,Spring有两个select:

  1. 使用JDK的Proxy类在运行时创build一个dynamic类的实例, 它只实现您的bean的接口 ,并将所有方法调用委托给实际的bean实例。
  2. 使用CGLIB代理在运行时创build一个dynamic类的实例,它实现了目标bean的接口和具体types,并将所有方法调用委托给实际的bean实例。

以JDK代理的示例(通过@EnableAsync的默认proxyTargetClassfalse

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

在这里,Spring试图find一个我们期望find的types为HttpClientImpl的bean,因为types明确地用@Component注解。 然而,相反,我们得到一个例外

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

Spring包装了HttpClientImpl bean,并通过仅实现HttpClientProxy对象公开它。 所以你可以检索它

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

总是build议编程接口 。 当你不能,你可以告诉Spring使用CGLIB代理。 例如,使用@EnableAsync ,可以将proxyTargetClass设置为true 。 类似的注释( EnableTransactionManagement等)具有相似的属性。 XML也将有相同的configuration选项。

ApplicationContext层次结构 – Spring MVC

Spring使用ConfigurableApplicationContext#setParent(ApplicationContext) ,可以使用其他ApplicationContext实例作为父项来构buildApplicationContext实例。 子上下文将有权访问父上下文中的bean,但事实恰恰相反。 这篇文章详细讨论了何时这是有用的,特别是在Spring MVC中。

在典型的Spring MVC应用程序中,您定义了两个上下文:一个用于整个应用程序(根),另一个专门用于DispatcherServlet (路由,处理程序方法,控制器)。 你可以在这里获得更多的细节:

  • Spring框架中的applicationContext.xml和spring-servlet.xml之间的区别

在这里的官方文档中也有很好的解释。

Spring MVCconfiguration中的一个常见错误是在根环境中使用@EnableWebMvc注释的@Configuration类声明WebMVCconfiguration,或者在XML中使用<mvc:annotation-driven />声明WebMVCconfiguration,而在servlet上下文中声明@Controller bean。 由于根上下文无法访问servlet上下文来查找任何bean,因此没有处理程序注册,并且所有请求都以404s失败。 你不会看到一个NoSuchBeanDefinitionException ,但效果是一样的。

确保你的bean是在合适的上下文中注册的。 可以通过为WebMVC( HandlerMappingHandlerAdapterViewResolverExceptionResolver等)注册的beanfind它们。 最好的解决scheme是正确分离豆。 DispatcherServlet负责路由和处理请求,所有相关的bean都应该进入它的上下文。 加载根上下文的ContextLoaderListener应该初始化应用程序需要的其余部分:服务,存储库等。

数组,集合和地图

一些已知types的豆类被Spring以特殊方式处理。 例如,如果您试图将一个MovieCatalog数组注入到一个字段中

 @Autowired private MovieCatalog[] movieCatalogs; 

Spring会查找MovieCatalogtypes的所有bean,将它们包装在一个数组中,然后注入该数组。 在讨论@Autowired的Spring文档中对此进行了描述。 类似的行为适用于SetListCollection注入目标。

对于Map注入目标,如果键types是String ,Spring也将以这种方式运行。 例如,如果你有

 @Autowired private Map<String, MovieCatalog> movies; 

Spring将查找所有types为MovieCatalog bean,并将它们作为值添加到Map ,其中相应的键将是它们的bean名称。

如前所述,如果没有请求types的bean可用,Spring将抛出一个NoSuchBeanDefinitionException 。 然而有时候,你只是想声明一个类似这些集合types的bean

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

并注入它们

 @Autowired private List<Foo> foos; 

在这个例子中,Spring会因NoSuchBeanDefinitionException失败,因为上下文中没有Foo bean。 但是你不想要一个Foo bean,你想要一个List<Foo> bean。 在Spring 4.3之前,你必须使用@Resource

对于自己定义为集合/映射或数组types的bean, @Resource是一个很好的解决scheme,通过唯一名称引用特定的集合或数组bean。 也就是说,只要元素types信息保存在@Bean返回types签名或集合inheritance层次结构中,就可以通过Spring的@Autowiredtypes匹配algorithm来匹配集合/映射和数组types。 在这种情况下,可以使用限定符值在同一types的集合中进行select,如前一段所述。

这适用于构造函数,setter和字段注入。

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

但是,它会失败的@Bean方法,即。

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

在这里,Spring忽略任何@Resource@Autowired注释方法,因为它是@Bean方法,因此不能应用文档中描述的行为。 但是,您可以使用Springexpression式语言(SpEL)通过名称引用bean。 在上面的例子中,你可以使用

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

引用名为fooList的bean并注入。