不畏浮云遮望眼:望断`NoSuchBeanDefinitionException`

2023-03-07 14:51:54 浏览数 (1)

云南.丽江

原文链接: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*:

代码语言:javascript复制
@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扫描到

代码语言:javascript复制
package org.baeldung.packageB;
@Component
public class BeanB { 
    ...
}

与这对应的, @ComponentScan的配置情况如下所示:

代码语言:javascript复制
@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:

代码语言:javascript复制
@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

代码语言:javascript复制
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中,所有的东西都会赋予一个名字】:

代码语言:javascript复制
@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异常:

代码语言:javascript复制
@Component
public class BeanA implements InitializingBean {

    @Autowired
    private ApplicationContext context;

    @Override
    public void afterPropertiesSet() {
        context.getBean("someBeanName");
    }
}

在这种情况下,由于 Bean“someBeanName”在Spring上下文中是没有定义的——这将导致抛出下面的异常:

代码语言:javascript复制
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中,这篇完整的异常及解决方案列表 应该写得不错,建议收藏。

0 人点赞