请解释Spring中NoSuchBeanDefinitionException
异常的以下内容:
- 它是什么意思?
- 在什么条件下会抛出该异常?
- 如何避免它?
本文章旨在全面介绍使用Spring框架的应用程序中出现NoSuchBeanDefinitionException
的相关问题。
请解释Spring中NoSuchBeanDefinitionException
异常的以下内容:
本文章旨在全面介绍使用Spring框架的应用程序中出现NoSuchBeanDefinitionException
的相关问题。
NoSuchBeanDefinitionException
的{{javadoc文档}}解释了:
当
BeanFactory
被要求提供一个无法找到定义的bean实例时抛出异常。这可能指向一个不存在的bean,一个不唯一的bean,或一个手动注册的没有关联bean定义的单例实例。
BeanFactory
基本上是表示Spring的控制反转容器的抽象。它在应用程序内部和外部公开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 {}
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 { }
这里通过@ComponentScan
注册了一个Foo
的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定义。
@Configuration
类中的@Bean
方法或XML配置中的<bean>
@Component
(和其元注释,例如@Repository
)通过@ComponentScan
或XML中的<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 />
Foo
的bean,一个名为foo
,另一个名为eg-different-name
。确保您不会意外地注册更多的bean。这就引出了下面的问题...<import resource=""/>
Java提供了@ImportResource
注释。
有时您需要为相同类型(或接口)使用多个bean。例如,您的应用程序可能使用两个数据库,一个MySQL实例和一个Oracle实例。在这种情况下,您将有两个DataSource
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没有区分或优先处理这两个bean的机制。但是,这样的机制是存在的。@Primary
(以及其在XML中的等效方式),如文档和这篇文章所述。通过这种更改。@Bean(name = "mysql")
@Primary
public DataSource mysql() { return new MySQL(); }
前面的代码片段不会抛出异常,而是返回mysql
bean。
您还可以使用@Qualifier
(以及其在XML中的等效项)来更好地控制bean选择过程,如文档所述。虽然@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的名称是带注释的方法的名称。如果指定,则忽略方法名称。
<bean>
有 id
属性来表示 bean的唯一标识符,而 name
可以用于创建一个或多个别名,但在(XML)id中是非法的。
@Component
和它的元注解value
该值可能表示逻辑组件名称的建议,如果自动检测到组件,则将其转换为Spring Bean。
如果未指定,自动为注释类型生成Bean名称,通常是类型名称的小驼峰版本。例如,MyClassName
变成myClassName
作为其Bean名称。Bean名称区分大小写。还要注意,通过字符串引用@DependsOn("my BeanName")
或XML配置文件引用的Bean名称/大写错误通常会发生。
@Qualifier
,如前所述,允许您向Bean添加更多别名。
确保在引用Bean时使用正确的名称。
Bean定义配置文件 允许您有条件地注册bean。 @Profile
, 特别是,
ConfigurableEnvironment.setActiveProfiles(java.lang.String...)
激活,也可以通过在JVM系统属性、环境变量或web.xml中作为Servlet上下文参数设置spring.profiles.active
属性来声明性地激活。在集成测试中,配置文件还可以通过@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 {
}
Foo
bean 抛出 NoSuchBeanDefinitionException
异常。由于未激活 StackOverflow
配置文件,因此该 bean 未注册。ApplicationContext
。AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("StackOverflow");
ctx.register(Example.class);
ctx.refresh();
这个bean已经被注册,可以被返回或注入。
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());
}
}
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封装并通过一个仅实现了HttpClient
接口的Proxy
对象进行暴露。因此,您可以使用以下方式检索它:
ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;
建议始终面向接口编程。当你无法这样做时,你可以告诉Spring使用CGLIB代理。例如,使用@EnableAsync
,你可以将proxyTargetClass
设置为true
。类似的注释(EnableTransactionManagement
等)也有类似的属性。XML也有相应的配置选项。
ApplicationContext
层次结构 - Spring MVCConfigurableApplicationContext#setParent(ApplicationContext)
方法生成具有父级ApplicationContext的ApplicationContext。子Context将能够访问父Context中的Bean,但反之则不行。这篇文章详细介绍了何时使用此功能,特别是在Spring MVC中。DispatcherServlet
(路由、处理方法、控制器)定义的。您可以在这里获取更多细节:
官方文档这里也有详细的解释。
在Spring MVC配置中,一个常见错误是将WebMVC配置声明在根上下文中,使用带有@EnableWebMvc
注释的@Configuration
类或者XML中的<mvc:annotation-driven />
,但将@Controller
bean放在servlet上下文中。 由于根上下文无法访问servlet上下文查找任何bean,因此不会注册任何处理程序,所有请求都将失败并返回404。 您不会看到NoSuchBeanDefinitionException
,但效果相同。
@Autowired
private MovieCatalog[] movieCatalogs;
MovieCatalog
的bean,将它们封装在一个数组中,并注入该数组。这在Spring文档中讨论的@Autowired
中有描述。类似的行为也适用于Set
、List
和Collection
注入目标。Map
注入目标,如果键类型是String
,Spring也会以同样的方式处理。例如,如果你有:@Autowired
private Map<String, MovieCatalog> movies;
MovieCatalog
的bean,并将它们作为值添加到一个Map
中,相应的键将是它们的bean名称。NoSuchBeanDefinitionException
异常。然而,有时你只想声明这些集合类型的bean。@Bean
public List<Foo> fooList() {
return Arrays.asList(new Foo());
}
并将它们注入
@Autowired
private List<Foo> foos;
在这个例子中,Spring 会因为上下文中没有 Foo
类型的 bean 而出现 NoSuchBeanDefinitionException
异常。但是你需要的不是一个 Foo
类型的 bean,而是一个 List<Foo>
类型的 bean。在 Spring 4.3 之前,你需要使用 @Resource
注解
对于那些本身定义为集合/映射或数组类型的 bean,
@Resource
是一个很好的解决方案,通过唯一名称引用特定的集合或数组类型的 bean。也就是说,从 4.3 版本开始,只要在@Bean
返回类型签名或集合继承层次结构中保留元素类型信息,集合/映射和数组类型也可以通过 Spring 的@Autowired
类型匹配算法进行匹配。在这种情况下,可以使用限定符值在同类型的集合之间进行选择,如前一段所述。
这适用于构造函数、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);
}
@Resource
或 @Autowired
注解的操作,因为它是一个 @Bean
方法,所以无法应用文档中描述的行为。但是,您可以使用Spring表达式语言(SpEL)按名称引用bean。在上面的示例中,您可以这样做。@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
new Bar(foos);
}
引用名为fooList
的bean并注入它。