什么是NoSuchBeanDefinitionException,如何解决?
请在Spring中解释有关NoSuchBeanDefinitionException
exception的以下内容:
- 这是什么意思?
- 在什么情况下会被抛出?
- 我怎样才能防止它?
这篇文章是针对使用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定义。 因此,由AnnotationConfigApplicationContext
pipe理的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 { }
在这里,通过@ComponentScan
为Foo
注册一个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会注册两个Foo
types的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的方式一样,也有多种方法来命名它们。
@Bean
有name
这个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
。 由于StackOverflow
configuration文件未处于活动状态,因此该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:
- 使用JDK的Proxy类在运行时创build一个dynamic类的实例, 它只实现您的bean的接口 ,并将所有方法调用委托给实际的bean实例。
- 使用CGLIB代理在运行时创build一个dynamic类的实例,它实现了目标bean的接口和具体types,并将所有方法调用委托给实际的bean实例。
以JDK代理的示例(通过@EnableAsync
的默认proxyTargetClass
为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()); } }
在这里,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,并通过仅实现HttpClient
的Proxy
对象公开它。 所以你可以检索它
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( HandlerMapping
, HandlerAdapter
, ViewResolver
, ExceptionResolver
等)注册的beanfind它们。 最好的解决scheme是正确分离豆。 DispatcherServlet
负责路由和处理请求,所有相关的bean都应该进入它的上下文。 加载根上下文的ContextLoaderListener
应该初始化应用程序需要的其余部分:服务,存储库等。
数组,集合和地图
一些已知types的豆类被Spring以特殊方式处理。 例如,如果您试图将一个MovieCatalog
数组注入到一个字段中
@Autowired private MovieCatalog[] movieCatalogs;
Spring会查找MovieCatalog
types的所有bean,将它们包装在一个数组中,然后注入该数组。 在讨论@Autowired
的Spring文档中对此进行了描述。 类似的行为适用于Set
, List
和Collection
注入目标。
对于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的@Autowired
types匹配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并注入。