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

What is a NoSuchBeanDefinitionException and how do I fix it?

请解释以下关于 Spring 中的 NoSuchBeanDefinitionException 异常:


此 post 旨在成为关于使用 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.

一个BeanFactory is basically the abstraction representing Spring's Inversion of Control container。它在内部和外部向您的应用程序公开 bean。当它找不到或检索这些 beans 时,它会抛出一个 NoSuchBeanDefinitionException.

以下是 BeanFactory(或相关 classes)无法找到 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 扫描、XML 定义或任何其他方式为类型 Foo 注册 bean 定义.因此,由 AnnotationConfigApplicationContext 管理的 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 依赖项时可能会抛出异常。例如,

@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 一无所知。因此,它在尝试自动装配 Foo bean 实例的 bar 字段时找不到相应的 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 中的方法 class 或 XML 配置中的 <bean>
  • @Component(及其元注释,例如 @Repository)通过 @ComponentScan<context:component-scan ... /> in 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。确保您没有意外注册比您想要的更多的 beans。这导致我们...

如果您同时使用 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 没有区分或区分两者优先级的机制。但是这样的机制是存在的。

您可以使用 @Primary (and its equivalent in XML) as described in the documentation and in this post。有了这个改变

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

前面的代码片段不会抛出异常,而是 return 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 有多种方式一样,命名 bean 的方式也有多种。

@Bean has name

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.

<bean>id属性表示bean的唯一标识符name可以使用在 (XML) id 中创建一个或多个非法别名。

@Component and its meta annotations have value

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 名称,通常是类型名称的小驼峰版本。例如 MyClassName 变成 myClassName 作为它的 bean 名称。 Bean 名称区分大小写。另请注意,错误的 names/capitalization 通常出现在由字符串引用的 bean 中,例如 @DependsOn("my BeanName") 或 XML 配置文件。

@Qualifier,如前所述,允许您向 bean 添加更多别名。

确保在引用 bean 时使用正确的名称。


更高级的案例

个人资料

Bean definition profiles allow you to register beans conditionally. @Profile,具体来说,

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.

考虑未设置 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 = "Whosebug")
@Component
class Foo {
}

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

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

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

bean已注册,可以returned/injected.

AOP 代理

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

为了达到这个目的,Spring有两个选择:

  1. 使用 JDK 的 Proxy class 在运行时创建一个动态 class 的实例,它 只实现你的 bean 的接口 并将所有方法调用委托给实际的 bean 实例。
  2. 使用 CGLIB 代理在运行时创建动态 class 的实例,它实现目标 bean 的接口和具体类型,并将所有方法调用委托给实际的 bean 实例。

以 JDK 代理为例(通过 @EnableAsync 默认的 proxyTargetClass of 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 并通过仅实现 HttpClientProxy 对象公开了它。所以你可以用

检索它
ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;

一直推荐program to interfaces. When you can't, you can tell Spring to use CGLIB proxies. For example, with @EnableAsync, you can set proxyTargetClasstrue。相似的注释(EnableTransactionManagement 等)具有相似的属性。 XML 也将有等效的配置选项。

ApplicationContext 层次结构 - Spring MVC

Spring 允许您构建 ApplicationContext 个实例,其他 ApplicationContext 个实例作为父实例,使用 ConfigurableApplicationContext#setParent(ApplicationContext). A child context will have access to beans in the parent context, but the opposite is not true. This post 详细说明何时有用,特别是在 Spring MVC.

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

  • Difference between applicationContext.xml and spring-servlet.xml in Spring Framework

官方文档中也有很好的解释,here

Spring MVC 配置中的一个常见错误 是在带有 @EnableWebMvc 注释的根上下文中声明 WebMVC 配置 @Configuration [= XML 中的 360=]es 或 <mvc:annotation-driven />,但 servlet 上下文中的 @Controller bean。 由于根上下文无法进入 servlet 上下文以找到任何 beans,因此没有注册任何处理程序并且所有请求都以 404s 失败。您不会看到 NoSuchBeanDefinitionException,但是效果是一样的。

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

数组、集合和映射

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

@Autowired
private MovieCatalog[] movieCatalogs;

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

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

@Autowired
private Map<String, MovieCatalog> movies;

Spring 将找到所有类型为 MovieCatalog 的 bean,并将它们作为值添加到 Map,其中相应的键将是它们的 bean 名称。

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

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

并注入它们

@Autowired
private List<Foo> foos;

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

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 和字段注入。

@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 方法,因此不能应用文档。但是,您可以使用 Spring 表达式语言 (SpEL) 按名称引用 bean。在上面的示例中,您可以使用

@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
    new Bar(foos);
}

引用名为 fooList 的 bean 并注入它。