什么是NoSuchBeanDefinitionException以及如何解决它?

时间:2016-08-26 20:08:23

标签: java spring applicationcontext

请在Spring中解释以下有关NoSuchBeanDefinitionException例外的内容:

  • 这是什么意思?
  • 在什么条件下会被抛出?
  • 我该如何预防?

本文旨在为使用Spring的应用程序中NoSuchBeanDefinitionException的出现提供全面的问答。

1 个答案:

答案 0 :(得分:78)

javadoc of NoSuchBeanDefinitionException解释了

  

当为{bean}实例请求BeanFactory时抛出异常   哪个找不到定义。这可能指向不存在的   bean,非唯一bean或手动注册的单例实例   没有关联的bean定义。

BeanFactory基本上是代表Spring's Inversion of Control container的抽象。它将bean内部和外部暴露给您的应用程序。当它找不到或检索这些bean时,它会抛出NoSuchBeanDefinitionException

以下是BeanFactory(或相关类)无法找到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 {}   

我们尚未通过Foo方法,@Bean扫描,XML定义或任何其他方式为@Component类型注册bean定义。因此,BeanFactory管理的AnnotationConfigApplicationContext无法指示getBean(Foo.class)请求的bean的位置。上面的代码段抛出

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

类似地,在尝试满足@Autowired依赖项时可能会抛出异常。例如,

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

此处,通过Foo@ComponentScan注册了bean定义。但是春天对Bar一无所知。因此,在尝试自动装配bar bean实例的Foo字段时,它无法找到相应的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定义。

    {li> @Bean类中的
  • @Configuration方法或XML配置中的<bean>
  • @Component(及其元注释,例如。@Repository)到@ComponentScan<context:component-scan ... />的XML
  • 手动通过GenericApplicationContext#registerBeanDefinition
  • 手动完成BeanDefinitionRegistryPostProcessor

......等等。

确保您期望的bean已正确注册。

常见错误是多次注册bean,即。将上述选项混合为相同类型。例如,我可能有

@Component
public class Foo {}

和带

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

这样的配置会注册两个类型为Foo的bean,一个名为foo,另一个名为eg-different-name。确保您不会意外地注册超过您想要的豆类。这导致我们......

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

<import resource=""/>

而Java提供@ImportResource注释。

预期的单个匹配bean,但找到2(或更多)

有时您需要多个bean用于相同类型(或接口)。例如,您的应用程序可能使用两个数据库,一个MySQL实例和一个Oracle实例。在这种情况下,您有两个DataSource bean来管理每个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没有机制来区分或优先考虑两者。但这种机制存在。

您可以使用@Primarydocumentation中所述的this post(以及XML中的等效内容)。有了这个改变

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

前一个代码段不会抛出异常,而是返回mysql bean。

您还可以使用@Qualifier(及其在XML中的等效项)来更好地控制bean选择过程,如documentation中所述。虽然@Autowired主要用于按类型自动装配,但@Qualifier允许您按名称自动装配。例如,

@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。

@Component及其元注释有value

  

该值可能表示对逻辑组件名称的建议   如果是自动检测的组件,则将其转换为Spring bean。

如果未指定,则会自动为带注释的类型生成bean名称,通常是类型名称的较低驼峰版本。

如前所述,

@Qualifier允许您向bean添加更多别名。

确保在按名称自动装配时使用正确的名称。

更高级的案例

配置文件

Bean definition profiles允许您有条件地注册bean。 @Profile,具体来说,

  

表示组件有资格在一个或多个时注册   更多指定的配置文件处于活

     

配置文件是可以激活的命名逻辑分组   以编程方式通过   ConfigurableEnvironment.setActiveProfiles(java.lang.String...)或   通过将spring.profiles.active属性设置为JVM来声明性地   系统属性,环境变量或Servlet上下文   web.xml中的参数用于Web应用程序。个人资料也可能是   通过@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 {
}

这将显示没有活动的配置文件,并为NoSuchBeanDefinitionException bean抛出Foo。由于StackOverflow配置文件未激活,因此该bean未注册。

相反,如果我在注册相应的配置文件时初始化ApplicationContext

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

bean已注册,可以返回/注入。

AOP Proxies

Spring大量使用AOP proxies来实现高级行为。一些例子包括:

为实现这一目标,Spring有两个选择:

  1. 使用JDK的Proxy类在运行时创建动态类的实例,仅实现bean的接口,并将所有方法调用委托给实际的bean实例。
  2. 使用CGLIB代理在运行时创建动态类的实例,实现目标bean的接口和具体类型,并将所有方法调用委托给实际的bean实例。
  3. 以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尝试找到我们希望找到的HttpClientImpl类型的bean,因为该类型明确注释了@Component。但是,相反,我们得到一个例外

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

    Spring将HttpClientImpl bean包装起来并通过仅实现Proxy的{​​{1}}对象公开它。所以你可以用

    来检索它
    HttpClient

    始终建议program to interfaces。如果不能,您可以告诉Spring使用CGLIB代理。例如,使用@EnableAsync,您可以将proxyTargetClass设置为ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33 // or @Autowired private HttpClient httpClient; 。类似的注释(true等)具有相似的属性。 XML也将具有等效的配置选项。

    EnableTransactionManagement层次结构 - Spring MVC

    Spring允许您使用ConfigurableApplicationContext#setParent(ApplicationContext)ApplicationContext个实例与其他ApplicationContext实例一起构建为父项。子上下文可以访问父上下文中的bean,但反之则不然。 This post详细说明了什么时候有用,特别是在Spring MVC中。

    在典型的Spring MVC应用程序中,您定义了两个上下文:一个用于整个应用程序(根),另一个用于DispatcherServlet(路由,处理程序方法,控制器)。您可以在此处获取更多详细信息:

    在官方文档here中也很好地解释了这一点。

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

    确保您的bean在适当的上下文中注册,即。注册WebMVC的bean(NoSuchBeanDefinitionExceptionHandlerMappingHandlerAdapterViewResolver等)可以找到它们。最好的解决方案是正确隔离bean。 ExceptionResolver负责路由和处理请求,因此所有相关的bean都应该进入其上下文。加载根上下文的DispatcherServlet应初始化应用程序其余部分所需的任何bean:服务,存储库等。

    数组,集合和地图

    一些已知类型的豆类由Spring以特殊方式处理。例如,如果您尝试将ContextLoaderListener数组注入字段

    MovieCatalog

    Spring将找到类型为@Autowired private MovieCatalog[] movieCatalogs; 的所有bean,将它们包装在一个数组中,然后注入该数组。这在Spring documentation discussing @Autowired中有所描述。类似行为适用于MovieCatalogSetList注入目标。

    对于Collection注入目标,如果密钥类型为Map,Spring也会以这种方式运行。例如,如果你有

    String

    Spring会找到@Autowired private Map<String, MovieCatalog> movies; 类型的所有bean,并将它们作为值添加到MovieCatalog,其中相应的键将是它们的bean名称。

    如前所述,如果没有所请求类型的bean可用,Spring将抛出Map。但是,有时您只想声明这些集合类型的bean,如

    NoSuchBeanDefinitionException

    并注入它们

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

    在此示例中,Spring将失败并显示@Autowired private List<Foo> foos; ,因为您的上下文中没有NoSuchBeanDefinitionException个bean。但是你不想要一个Foo bean,你想要一个Foo bean。 Before Spring 4.3, you'd have to use @Resource

      

    对于自身定义为集合/映射或数组的bean   类型,List<Foo>是一个很好的解决方案,指的是具体的   集合或数组bean按唯一名称。那就是说,截至4.3 ,   可以通过Spring匹配集合/映射和数组类型   @Resource类型匹配算法也是如此,只要元素   类型信息保存在@Autowired返回类型签名或   集合继承层次结构。在这种情况下,限定符值可以   用于在相同类型的集合中进行选择,如中所述   前一段。

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

    @Bean

    但是,@Resource private List<Foo> foos; // or since 4.3 public Example(@Autowired List<Foo> foos) {} 方法会失败,即

    @Bean

    在这里,Spring忽略了注释方法的任何@Bean public Bar other(List<Foo> foos) { new Bar(foos); } @Resource,因为它是@Autowired方法,因此无法应用文档中描述的行为。但是,您可以使用Spring Expression Language(SpEL)按名称引用bean。在上面的示例中,您可以使用

    @Bean

    引用名为@Bean public Bar other(@Value("#{fooList}") List<Foo> foos) { new Bar(foos); } 的bean并注入它。