java - 什么是 NoSuchBeanDefinitionException 以及如何修复它?

标签 java spring applicationcontext

请解释以下有关 Spring 中 NoSuchBeanDefinitionException 异常的信息:

  • 是什么意思?
  • 什么情况下会抛出?
  • 我该如何预防?


  • 这篇文章旨在对使用 Spring 的应用程序中出现的 NoSuchBeanDefinitionException 进行全面的问答。

    最佳答案

    javadoc of NoSuchBeanDefinitionException 解释

    Exception thrown when a BeanFactory is asked for a bean instance for which it cannot find a definition. This may point to a non-existing bean, a non-unique bean, or a manually registered singleton instance without an associated bean definition.


    A BeanFactory 基本上是代表 Spring's Inversion of Control container 的抽象。它在内部和外部向您的应用程序公开 bean。当它无法找到或检索这些 bean 时,它会抛出 NoSuchBeanDefinitionException
    以下是 BeanFactory(或相关类)无法找到 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 {}   
    
    我们还没有通过 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 定义。但是 Spring 对 Bar 一无所知。因此,在尝试 Autowiring 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 定义。
  • @Bean 类中的方法 @Configuration 或 XML 配置中的 <bean>
  • @Component(及其元注释,例如 @Repository )至 @ComponentScan 或 0x251843141 中的 0x25181223134319 XML2319 2319 19 231212124124
  • 手动通过 <context:component-scan ... />
  • 手动通过 GenericApplicationContext#registerBeanDefinition

  • ...和更多。
    确保您期望的 bean 已正确注册。
    一个常见的错误是多次注册bean,即。混合上述相同类型的选项。例如,我可能有
    @Component
    public class Foo {}
    
    和一个 XML 配置
    <context:component-scan base-packages="com.example" />
    <bean name="eg-different-name" class="com.example.Foo />
    
    这样的配置将注册两个类型为 BeanDefinitionRegistryPostProcessor 的 bean,一个名称为 Foo ,另一个名称为 foo 。确保您不会意外注册比您想要的更多的 bean。这导致我们...
    如果您同时使用 XML 和基于注释的配置,请确保从另一个中导入一个。 XML 提供
    <import resource=""/>
    
    而 Java 提供了 eg-different-name 注释。
    预期单个匹配 bean,但发现 2 个(或更多)
    有时您需要多个 bean 用于相同类型(或接口(interface))。例如,您的应用程序可能使用两个数据库,一个 MySQL 实例和一个 Oracle 实例。在这种情况下,您将有两个 @ImportResource 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 {}
    
    throw
    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
    
    因为通过 DataSource 方法注册的两个 bean 都满足 @Bean 的要求,即。他们都实现了 BeanFactory#getBean(Class) 。在这个例子中,Spring 没有机制来区分或优先考虑两者。但是这样的机制是存在的。
    您可以使用 DataSource (及其在 XML 中的等效项),如 documentationthis post 中所述。有了这个变化
    @Bean(name = "mysql")
    @Primary
    public DataSource mysql() { return new MySQL(); } 
    
    前面的代码片段不会抛出异常,而是返回 @Primary bean。
    您还可以使用 mysql(及其在 XML 中的等效项)来更好地控制 bean 选择过程,如 documentation 中所述。虽然 @Qualifier 主要用于按类型 Autowiring ,但 @Autowired 可让您按名称 Autowiring 。例如,
    @Bean(name = "mysql")
    @Qualifier(value = "main")
    public DataSource mysql() { return new MySQL(); }
    
    现在可以注入(inject)为
    @Qualifier("main") // or @Qualifier("mysql"), to use the bean name
    private DataSource dataSource;
    
    没有问题。 @Qualifier 也是一个选项。
    使用错误的 bean 名称
    正如注册 bean 的方法有多种一样,命名它们的方法也有多种。
    @Resource @Bean

    The name of this bean, or if plural, aliases for this bean. If left unspecified the name of the bean is the name of the annotated method. If specified, the method name is ignored.

    name 具有 <bean> 属性来表示 bean 的唯一标识符,id 可用于在 (XML) id 中创建一个或多个非法别名。
    name 及其元注释有 @Component

    The value may indicate a suggestion for a logical component name, to be turned into a Spring bean in case of an autodetected component.


    如果未指定,则会为带注释的类型自动生成一个 bean 名称,通常是类型名称的小驼峰版本。例如 value 变为 MyClassName 作为其 bean 名称。 Bean 名称区分大小写。另请注意,错误的名称/大写通常出现在由 myClassName 或 XML 配置文件等字符串引用的 bean 中。
    如前所述, @DependsOn("my BeanName") 允许您向 bean 添加更多别名。
    确保在引用 bean 时使用正确的名称。

    更高级的案例
    简介
    Bean definition profiles 允许您有条件地注册bean。 @Qualifier ,具体来说,

    Indicates that a component is eligible for registration when one or more specified profiles are active.

    A profile is a named logical grouping that may be activated programmatically via ConfigurableEnvironment.setActiveProfiles(java.lang.String...) or declaratively by setting the spring.profiles.active property as a JVM system property, as an environment variable, or as a Servlet context parameter in web.xml for web applications. Profiles may also be activated declaratively in integration tests via the @ActiveProfiles annotation.


    考虑未设置 @Profile 属性的示例。
    @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 {
    }
    
    这将显示没有 Activity 的配置文件,并为 spring.profiles.active bean 抛出 NoSuchBeanDefinitionException。由于 Foo 配置文件未处于 Activity 状态,因此该 bean 未注册。
    相反,如果我在注册适当的配置文件时初始化 StackOverflow
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.getEnvironment().setActiveProfiles("StackOverflow");
    ctx.register(Example.class);
    ctx.refresh();
    
    bean 已注册并可返回/注入(inject)。
    AOP 代理
    Spring 大量使用 AOP proxies 来实现高级行为。一些例子包括:
  • Transaction management ApplicationContext
  • Caching @Transactional
  • Scheduling and asynchronous execution @Cacheable @Async

  • 为此,Spring 有两种选择:
  • 使用 JDK 的 Proxy 类在运行时创建动态类的实例,其中 仅实现了 bean 的接口(interface) 0x25181921134112
  • 使用 CGLIB 代理在运行时创建动态类的实例,该实例实现目标 bean 的接口(interface)和具体类型,并将所有方法调用委托(delegate)给实际的 bean 实例。

  • 以 JDK 代理为例(通过 @Scheduled 的默认值 @EnableAsyncproxyTargetClass 实现)
    @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 试图找到一个类型为 false 的 bean,我们希望找到它,因为该类型清楚地用 HttpClientImpl 进行了注释。然而,相反,我们得到了一个异常(exception)
    Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: 
        No qualifying bean of type [com.example.HttpClientImpl] is defined
    
    Spring 包裹了 @Component bean 并通过一个仅实现 HttpClientImplProxy 对象暴露它。所以你可以用
    ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
    // or
    @Autowired private HttpClient httpClient;
    
    始终建议使用 program to interfaces 。如果不能,您可以告诉 Spring 使用 CGLIB 代理。例如,对于 HttpClient ,您可以将 @EnableAsync 设置为 proxyTargetClass 。类似的注释( true 等)具有类似的属性。 XML 也将具有等效的配置选项。EnableTransactionManagement 层次结构-Spring MVC
    Spring 允许您使用 ApplicationContext 构建 ApplicationContext 实例,其他 ApplicationContext 实例作为父实例。子上下文可以访问父上下文中的 bean,但反之则不然。 This post 详细介绍了这何时有用,尤其是在 Spring MVC 中。
    在典型的 Spring MVC 应用程序中,您定义两个上下文:一个用于整个应用程序(根),另一个专门用于 ConfigurableApplicationContext#setParent(ApplicationContext) (路由、处理程序方法、 Controller )。您可以在此处获得更多详细信息:
  • Difference between applicationContext.xml and spring-servlet.xml in Spring Framework

  • 在官方文档 here 中也有很好的解释。
    在Spring MVC配置一个常见的错误DispatcherServlet注解@EnableWebMvc类或在XML @Configuration,但 <mvc:annotation-driven /> bean 在servlet上下文声明在根上下文的WebMVC配置。 由于根上下文无法进入 servlet 上下文以找到任何 bean,因此没有注册处理程序并且所有请求都以 404 失败。 你不会看到 @Controller ,但效果是一样的。
    确保您的 bean 已在适当的上下文中注册,即。可以通过为 WebMVC 注册的 bean 找到它们( NoSuchBeanDefinitionExceptionHandlerMappingHandlerAdapterViewResolver 等)。最好的解决方案是正确隔离bean。 ExceptionResolver 负责路由和处理请求,因此所有相关的 bean 都应该进入其上下文。加载根上下文的 DispatcherServlet 应该初始化应用程序其余部分所需的任何 bean:服务、存储库等。
    数组、集合和映射
    Spring 以特殊方式处理某些已知类型的 Bean。例如,如果您尝试将 ContextLoaderListener 的数组注入(inject)到字段中
    @Autowired
    private MovieCatalog[] movieCatalogs;
    
    Spring 将找到所有 MovieCatalog 类型的 bean,将它们包装在一个数组中,然后注入(inject)该数组。这在 Spring documentation discussing MovieCatalog 中有描述。类似的行为适用于 @AutowiredSetList 注入(inject)目标。
    对于 Collection 注入(inject)目标,如果键类型为 Map,Spring 也会以这种方式运行。例如,如果你有
    @Autowired
    private Map<String, MovieCatalog> movies;
    
    Spring 将找到所有类型为 String 的 bean,并将它们作为值添加到 MovieCatalog 中,其中相应的键将是它们的 bean 名称。
    如前所述,如果没有可用的请求类型的 bean,Spring 将抛出 Map 。但是,有时您只想声明这些集合类型的 bean,例如
    @Bean
    public List<Foo> fooList() {
        return Arrays.asList(new Foo());
    }
    
    并注入(inject)它们
    @Autowired
    private List<Foo> foos;
    
    在此示例中,Spring 将失败并返回 NoSuchBeanDefinitionException,因为您的上下文中没有 NoSuchBeanDefinitionException bean。但是你不想要一个 Foo bean,你想要一个 Foo bean。 Before Spring 4.3, you'd have to use List<Foo>

    For beans that are themselves defined as a collection/map or array type, @Resource is a fine solution, referring to the specific collection or array bean by unique name. That said, as of 4.3, collection/map and array types can be matched through Spring’s @Autowired type matching algorithm as well, as long as the element type information is preserved in @Bean return type signatures or collection inheritance hierarchies. In this case, qualifier values can be used to select among same-typed collections, as outlined in the previous paragraph.


    这适用于构造函数、setter 和字段注入(inject)。
    @Resource
    private List<Foo> foos;
    // or since 4.3
    public Example(@Autowired List<Foo> foos) {}
    
    但是,对于 @Resource 方法,即会失败。
    @Bean
    public Bar other(List<Foo> foos) {
        new Bar(foos);
    }
    
    在这里,Spring 忽略任何 @Bean@Resource 注释该方法,因为它是 @Autowired 方法,因此不能应用文档中描述的行为。但是,您可以使用 Spring 表达式语言 (SpEL) 通过名称来引用 bean。在上面的例子中,你可以使用
    @Bean
    public Bar other(@Value("#{fooList}") List<Foo> foos) {
        new Bar(foos);
    }
    
    引用名为 @Bean 的 bean 并注入(inject)它。

    关于java - 什么是 NoSuchBeanDefinitionException 以及如何修复它?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39173982/

    相关文章:

    java - 如何在Java中获得双显示器的分辨率

    java - 无法在jsp中下载.xlsx文件

    java - SQL - 在 DAO 中执行时列名无效

    java - 使用 Java 规范/谓词进行全文搜索

    java - 为什么没有创建bean?

    java - 使用 MYSQL 表结构创建 JPA 映射时遇到问题

    java - JMeter 计数 Controller

    java - 通过 https 给定 IP 地址的 Spring Boot 白名单

    java - Spring Framework 中的 Application Context 是用户特定的还是应用程序特定的?

    java - 如何在 Spring Boot 2 中检索应用程序上下文