原文链接:https://www.baeldung.com/spring-nosuchbeandefinitionexception
作者:Eugen Paraschiv
译者:helloworldtang
1.概览
在本文中,我们将讨论Spring中的org.springframework.beans.factory.NoSuchBeanDefinitionException异常——当BeanFactory试图装配一个在Spring上下文中没有定义的 Bean
时,自动装配会失败并抛出这个异常。
我们将找出导致这个异常的可能原因并给出可用的解决方案。
当然,异常总是在你最不希望看到的时候发生;Spring异常及解决方案的完整列表可以在这里查看。
2.Cause: No qualifying bean of type […] found for dependency
这个异常最常见的原因是试图注入一个未定义的 Bean
。例如,在* BeanB中自动装配了BeanA*:
@Component
public class BeanA {
@Autowired
private BeanB dependency;
//...
}
现在,如果在Spring上下文中没有定义这个依赖项BeanB,那么将启动失败,并抛出这个异常NoSuchBeanDefinitionException:
代码语言:javascript复制org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.baeldung.packageB.BeanB]
found for dependency:
expected at least 1 bean which qualifies as
autowire candidate for this dependency.
Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
Spring已经将错误原因提示地很明显了: “expected at least 1 bean which qualifies as autowire candidate for this dependency“
原因是BeanB 在Spring上下文中不存在——如果 Bean
实例化是通过扫描指定的package,并且如果BeanB已经被正确地标注为一个 Bean
(添加过了@Component或@Repository或@Service或@Controller等其它基于元注解@Component的业务注解)——也就是说这个类所在的package没有被Spring扫描到:
package org.baeldung.packageB;
@Component
public class BeanB {
...
}
与这对应的, @ComponentScan
的配置情况如下所示:
@Configuration
@ComponentScan("org.baeldung.packageA")
public class ContextWithJavaConfig {
...
}
如果 Bean
是手动初始化而不是通过Spring的自动扫描,那么在当前的Spring上下文中是不存在BeanB的。
3.Cause: Field […] in […] required a bean of type […] that could not be found
在上述场景中,如果是一个Spring Boot应用程序,我们将得到一个不同的报错消息。
示例如下,其中BeanB被自动装配到BeanA,但是BeanB并没有被定义:
代码语言:javascript复制@Component
public class BeanA {
@Autowired
private BeanB dependency;
//...
}
如果我们试图运行这个需要加载BeanA的简单应用程序:
代码语言:javascript复制@SpringBootApplication
public class NoSuchBeanDefinitionDemoApp {
public static void main(String[] args) {
SpringApplication.run(NoSuchBeanDefinitionDemoApp.class, args);
}
}
这个应用程序将启动失败,并报如下错误:
代码语言:javascript复制***************************
APPLICATION FAILED TO START
***************************
Description:
Field dependency in com.baeldung.springbootmvc.nosuchbeandefinitionexception.BeanA required a bean of type 'com.baeldung.springbootmvc.nosuchbeandefinitionexception.BeanB' that could not be found.
Action:
Consider defining a bean of type 'com.baeldung.springbootmvc.nosuchbeandefinitionexception.BeanB' in your configuration.
在这里, com.baeldung.springbootmvc.nosuchbeandefinitionexception是BeanA、BeanB和NoSuchBeanDefinitionDemoApp的package。
这个示例的完整代码可以在这个Github项目中找到。
4.Cause: No qualifying bean of type […] is defined
异常的另一个原因是Spring上下文中存在两个 Bean
定义,而不是一个。例如,如果一个接口IBeanB有两个 Bean
实现——BeanB1和BeanB2:
@Component
public class BeanB1 implements IBeanB {
//
}
@Component
public class BeanB2 implements IBeanB {
//
}
现在,如果在BeanA中自动装配了这个接口,那么Spring将不知道使用这两个实现中的哪一个来进行注入:
代码语言:javascript复制@Component
public class BeanA {
@Autowired
private IBeanB dependency;
...
}
同样,这将导致BeanFactory抛出异常 NoSuchBeanDefinitionException
:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type
[org.baeldung.packageB.IBeanB] is defined:
expected single matching bean but found 2: beanB1,beanB2
类似地,Spring也清楚地指出了装配失败的原因:“expected single matching bean but found 2”。
但是,请注意,在这种情况下,抛出的异常不是NoSuchBeanDefinitionException,而是它的一个子类–NoUniqueBeanDefinitionException。这个新异常在Spring 3.2.1时就已经引入的,它的引入就是为了区分在Spring上下文中没有找到 Bean
定义和找到多个定义。
在没有引入NoUniqueBeanDefinitionException之前,会抛出这样的异常:
代码语言:javascript复制Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.baeldung.packageB.IBeanB] is defined:
expected single matching bean but found 2: beanB1,beanB2
这个问题的一个解决方案是使用 @Qualifier
注解来精确地指定我们要注入的 Bean
名字【在Spring中,所有的东西都会赋予一个名字】:
@Component
public class BeanA {
@Autowired
@Qualifier("beanB2")
private IBeanB dependency;
...
}
现在,Spring已经有足够的信息来决定注入哪个 Bean
——BeanB1或BeanB2(BeanB2的默认名字是beanB2)。
5.Cause: No Bean Named […] is defined
当通过名字从Spring上下文中请求一个没有定义的 Bean
时,也可能抛出NoSuchBeanDefinitionException异常:
@Component
public class BeanA implements InitializingBean {
@Autowired
private ApplicationContext context;
@Override
public void afterPropertiesSet() {
context.getBean("someBeanName");
}
}
在这种情况下,由于 Bean
“someBeanName”在Spring上下文中是没有定义的——这将导致抛出下面的异常:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No bean named 'someBeanName' is defined
同样,Spring清晰又简洁地指出了失败的原因:“No bean named X is defined“。
6.Cause: Proxied Beans
请注意:如果Spring上下文中的 Bean
添加了JDK动态代理特性,那么代理是不会织入到具体的实现类 Bean
的,即使用接口的具体实现类进行注入时,代理将失效(虽然实现类也实现了相同的接口)。
正因为如此,如果 Bean
是通过一个接口注入,事务将会被正确地织入。如果 Bean
是由一个实现类注入的,那么Spring将不会找到与该类相匹配的 Bean
定义——即代理不会被织入。
Bean
代理的一个非常常见的原因是Spring的事务支持——即用 @Transactional
注解修饰的 Bean
。
例如,如果将ServiceB注入到ServiceA,并且这两个服务都是支持事务的,那么通过类定义注入服务的事务将不会生效:
代码语言:javascript复制@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private ServiceB serviceB;
...
}
@Service
@Transactional
public class ServiceB implements IServiceB{
...
}
在下面的示例中,因为正确地通过接口进行注入,因此,两个服务的事务都会生效:
代码语言:javascript复制@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private IServiceB serviceB;
...
}
@Service
@Transactional
public class ServiceB implements IServiceB{
...
}
7. 总结
本教程结合实例讨论了引发NoSuchBeanDefinitionException的常见原因,重点讨论了如何在实践中处理这些异常。
文中用到的示例代码都可以在GitHub项目 上找到——这是一个基于Eclipse的项目,因此应该很容易导入和运行。
最后,在Spring中,这篇完整的异常及解决方案列表 应该写得不错,建议收藏。