什么是NoSuchBeanDefinitionException,我该如何修复它?豆子不存在,它没有被注册预期单个匹配的豆子,但发现 2 个(或更多)使用错误的豆名更高级的案例数组、集合和映射
关于春季的例外情况,请解释以下情况:NoSuchBeanDefinitionException
- 这是什么意思?
- 在什么条件下会被抛出?
- 我该如何预防?
这篇文章旨在成为关于NoSuchBeanDefinitionException
在使用Spring的应用程序中出现的全面问答。
关于春季的例外情况,请解释以下情况:NoSuchBeanDefinitionException
这篇文章旨在成为关于NoSuchBeanDefinitionException
在使用Spring的应用程序中出现的全面问答。
NoSuchBeanDefinitionException
的javadoc解释了
当 要求 a 提供找不到定义的 Bean 实例时引发异常。这可能指向不存在的 Bean、非唯一 Bean 或没有关联 Bean 定义的手动注册的单一实例。
BeanFactory
BeanFactory
基本上是代表Spring的控制反转容器的抽象。它在内部和外部向应用程序公开 Bean。当它无法找到或检索这些豆子时,它会抛出一个 .NoSuchBeanDefinitionException
以下是(或相关类)无法找到Bean的简单原因,以及如何确保它找到。BeanFactory
在下面的示例中
@Configuration
public class Example {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
ctx.getBean(Foo.class);
}
}
class Foo {}
我们还没有通过方法,扫描,XML定义或任何其他方式为该类型注册bean定义。因此,管理的 没有指示从何处获取 由 请求的 bean。上面的片段抛出Foo
@Bean
@Component
BeanFactory
AnnotationConfigApplicationContext
getBean(Foo.class)
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 { }
此处,通过 注册了 Bean 定义。但是春天对.因此,在尝试自动连接 Bean 实例的字段时,它找不到相应的 Bean。它抛出(嵌套在不满意的独立性异常中)
Foo
@ComponentScan
Bar
bar
Foo
)
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
类或 XML 配置中的方法@Configuration
<bean>
@Component
(及其元注释,例如)通过或以XML形式@Repository
@ComponentScan
<context:component-scan ... />
GenericApplicationContext#registerBeanDefinition
BeanDefinitionRegistryPostProcessor
...以及更多。
确保预期的豆子已正确注册。
一个常见的错误是多次注册bean,即。为同一类型混合上述选项。例如,我可能有
@Component
public class Foo {}
和 XML 配置
<context:component-scan base-packages="com.example" />
<bean name="eg-different-name" class="com.example.Foo />
这样的配置将注册两个类型的bean,一个带有name,另一个带有name。确保您没有意外注册的豆子比您想要的多。这导致我们...Foo
foo
eg-different-name
如果同时使用 XML 和基于批注的配置,请确保从另一个配置导入一个配置。XML 提供
<import resource=""/>
而Java提供了@ImportResource
注释。
有时,您需要为同一类型(或接口)使用多个Bean。例如,您的应用程序可能使用两个数据库,一个 MySQL 实例和一个 Oracle 实例。在这种情况下,您将有两个bean来管理与每个bean的连接。对于(简化的)示例,如下所示DataSource
@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都满足BeanFactory#getBean(Class)的要求
,即。它们都实现了 .在这个例子中,Spring没有机制来区分或优先考虑两者。但这种机制是存在的。@Bean
DataSource
您可以使用@Primary
(及其在 XML 中的等效项),如文档和本文中所述。通过此更改
@Bean(name = "mysql")
@Primary
public DataSource mysql() { return new MySQL(); }
前面的代码段不会引发异常,而是返回 bean。mysql
您还可以使用(及其在 XML 中的等效项)来更好地控制 Bean 选择过程,如文档中所述。虽然主要用于按类型自动连线,但允许您按名称自动连线。例如@Qualifier
@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 的唯一标识符的属性,并且可用于在 (XML) ID 中创建一个或多个非法别名。id
name
@Component
及其元注释具有价值
该值可能指示逻辑组件名称的建议,以便在自动检测到组件的情况下将其转换为Spring Bean。
如果未指定,则会为带注释的类型(通常是类型名称的驼峰大小写版本)自动生成 Bean 名称。例如,成为其豆名。Bean 名称区分大小写。另请注意,错误的名称/大小写通常发生在字符串 like 或 XML 配置文件引用的 Bean 中。MyClassName
myClassName
@DependsOn("my BeanName")
@Qualifier
,如前所述,允许您向 Bean 添加更多别名。
确保在引用 Bean 时使用正确的名称。
Bean 定义概要文件允许您有条件地注册 Bean。@Profile
,具体来说,
指示当一个或多个指定的配置文件处于活动状态时,组件符合注册条件。
概要文件是一种命名的逻辑分组,可以通过
ConfigurableEnvironment.setActiveProfiles(java.lang.String...)
以编程方式激活,也可以通过将属性设置为 JVM 系统属性、环境变量或 Web 应用程序中的 Servlet 上下文参数来声明性地.xml Web 应用程序。配置文件也可以在集成测试中通过@ActiveProfiles
注释以声明方式激活。spring.profiles.active
请考虑未设置属性的示例。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 {
}
这将显示没有活动的配置文件,并为bean抛出一个。由于配置文件未处于活动状态,因此未注册 Bean。NoSuchBeanDefinitionException
Foo
StackOverflow
相反,如果我在注册相应的配置文件时初始化ApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("StackOverflow");
ctx.register(Example.class);
ctx.refresh();
豆子已注册,可以返回/注入。
Spring经常使用AOP代理来实现高级行为。一些示例包括:
为了实现这一目标,Spring有两种选择:
以这个 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
@Component
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.example.HttpClientImpl] is defined
Spring包裹了豆子,并通过一个只实现的对象将其暴露出来。所以你可以用HttpClientImpl
Proxy
HttpClient
ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;
始终建议对接口进行编程。当你不能的时候,你可以告诉Spring使用CGLIM代理。例如,使用@EnableAsync
,可以将 proxyTargetClass
设置为 。类似的注释(等)具有相似的属性。XML 还将具有等效的配置选项。true
EnableTransactionManagement
ApplicationContext
层次结构 - 春季 MVCSpring允许您使用ConfigableApplicationContext#setParent(ApplicationContext)
构建具有其他实例作为父实例的实例。子上下文将有权访问父上下文中的 Bean,但事实并非如此。这篇文章详细介绍了何时有用,特别是在Spring MVC中。ApplicationContext
ApplicationContext
在典型的Spring MVC应用程序中,定义两个上下文:一个用于整个应用程序(根),另一个专门用于DispatcherServlet
(路由,处理程序方法,控制器)。您可以在此处获取更多详细信息:
在官方文档中也有很好的解释,在这里。
Spring MVC 配置中的一个常见错误是在带有注释的类的根上下文中或在 XML 中声明 WebMVC 配置,但@Controller
servlet 上下文中的 bean。由于根上下文无法到达 servlet 上下文以查找任何 Bean,因此不会注册任何处理程序,并且所有请求都失败,并显示 404。您不会看到 ,但效果是相同的。@EnableWebMvc
@Configuration
<mvc:annotation-driven />
NoSuchBeanDefinitionException
确保您的bean在适当的上下文中注册,即。在那里,它们可以通过为WebMVC注册的bean(,,等)找到。最好的解决方案是正确分离豆类。负责路由和处理请求,因此所有相关的 Bean 都应进入其上下文。加载根上下文的 应该初始化应用程序其余部分所需的任何 Bean:服务、存储库等。HandlerMapping
HandlerAdapter
ViewResolver
ExceptionResolver
DispatcherServlet
ContextLoaderListener
一些已知类型的豆类由Spring以特殊方式处理。例如,如果您尝试将 数组注入到字段中MovieCatalog
@Autowired
private MovieCatalog[] movieCatalogs;
Spring会找到所有类型的豆子,将它们包装在一个数组中,然后注入该数组。这在讨论@Autowired
的Spring文档中进行了描述。类似的行为也适用于 、 和注入目标。MovieCatalog
Set
List
Collection
对于注射目标,如果键类型为 .,则 Spring 也会以这种方式运行。例如,如果您有Map
String
@Autowired
private Map<String, MovieCatalog> movies;
Spring 将找到所有类型的 bean,并将它们作为值添加到 a 中,其中相应的键将是它们的 bean 名称。MovieCatalog
Map
如前所述,如果没有请求类型的豆子可用,则Spring将抛出一个.但是,有时,您只想声明这些集合类型的bean,例如NoSuchBeanDefinitionException
@Bean
public List<Foo> fooList() {
return Arrays.asList(new Foo());
}
并注射它们
@Autowired
private List<Foo> foos;
在这个例子中,Spring会失败,因为你的上下文中没有豆子。但你不想要一颗豆子,你想要一颗豆子。在 Spring 4.3 之前,您必须使用@Resource
NoSuchBeanDefinitionException
Foo
Foo
List<Foo>
对于本身定义为集合/映射或数组类型的 Bean,是一个很好的解决方案,通过唯一名称引用特定的集合或数组 Bean。也就是说,从4.3开始,集合/映射和数组类型也可以通过Spring的类型匹配算法进行匹配,只要元素类型信息保留在返回类型签名或集合继承层次结构中即可。在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如上一段所述。
@Resource
@Autowired
@Bean
这适用于构造函数、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 会忽略任何方法或对方法进行注释,因为它是一个方法,因此无法应用文档中描述的行为。但是,您可以使用Spring Expression Language(SpEL)通过其名称来指代bean。在上面的示例中,您可以使用@Resource
@Autowired
@Bean
@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
new Bar(foos);
}
引用命名的豆子并注入它。fooList