请在Spring中解释以下关于NoSuchBeanDefinitionException
异常的内容:
本文旨在对使用Spring的应用程序中NoSuchBeanDefinitionException
的出现进行全面的问答。
javadoc of NoSuchBeanDefinitionException
解释道
当
BeanFactory
被要求提供无法找到定义的bean实例时抛出异常。这可能指向不存在的bean,非唯一bean或没有关联bean定义的手动注册的单例实例。
BeanFactory
基本上是代表Spring's Inversion of Control container的抽象。它将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)
要求从哪里获得豆子。上面的片段抛出
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定义有多种方法。
@Bean
类中的@Configuration
方法或XML配置中的<bean>
@Component
(及其meta-annotations,例如@Repository
)通过XML中的@ComponentScan
或<context:component-scan ... />
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用于相同类型(或接口)。例如,您的应用程序可能使用两个数据库,一个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
(及其在XML中的等效项),如documentation和this post中所述。随着这种变化
@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的名称,或者如果是复数,则为此bean的别名。如果未指定,则bean的名称是带注释的方法的名称。如果指定,则忽略方法名称。
<bean>
具有id
属性来表示bean的唯一标识符,name
可用于在(XML)id中创建一个或多个非法别名。
@Component
及其元注释有value
该值可以指示对逻辑组件名称的建议,在自动检测的组件的情况下将其转换为Spring bean。
如果未指定,则会自动为带注释的类型生成bean名称,通常是类型名称的较低驼峰案例版本。
如前所述,@Qualifier
允许您向bean添加更多别名。
确保在按名称自动装配时使用正确的名称。
Bean definition profiles允许您有条件地注册豆类。 @Profile
,具体来说,
表示当一个或多个指定的配置文件处于活动状态时,组件符合注册条件。
概要文件是一个命名的逻辑分组,可以通过
ConfigurableEnvironment.setActiveProfiles(java.lang.String...)
以编程方式激活,也可以通过将spring.profiles.active
属性设置为JVM系统属性,环境变量或web.xml中的Web应用程序的Servlet上下文参数来声明性地激活。也可以通过@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已注册,可以返回/注入。
Spring使用AOP proxies来实现高级行为。一些例子包括:
@Transactional
@Cacheable
@Async
和@Scheduled
为实现这一目标,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
的bean,因为该类型明显用@Component
注释。但是,相反,我们得到一个例外
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.example.HttpClientImpl] is defined
春天包裹了HttpClientImpl
豆并通过Proxy
对象暴露它,该对象只实现HttpClient
。所以你可以用它来检索它
ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;
它总是被推荐给program to interfaces。如果不能,您可以告诉Spring使用CGLIB代理。例如,使用@EnableAsync
,您可以将proxyTargetClass
设置为true
。类似的注释(EnableTransactionManagement
等)具有相似的属性。 XML也将具有等效的配置选项。
ApplicationContext
层次结构 - Spring MVCSpring允许您使用ApplicationContext
以其他ApplicationContext
实例作为父项构建ConfigurableApplicationContext#setParent(ApplicationContext)
实例。子上下文可以访问父上下文中的bean,但反之则不然。 This post详细介绍了它何时有用,特别是在Spring MVC中。
在典型的Spring MVC应用程序中,您定义了两个上下文:一个用于整个应用程序(根),另一个专门用于DispatcherServlet
(路由,处理程序方法,控制器)。您可以在此处获取更多详细信息
它在官方文档here中也得到了很好的解释。
Spring MVC配置中的常见错误是在根上下文中使用@EnableWebMvc
注释的@Configuration
类或<mvc:annotation-driven />
在XML中声明WebMVC配置,但是在servlet上下文中使用@Controller
bean。由于根上下文无法访问servlet上下文以查找任何bean,因此不会注册任何处理程序,并且所有请求都会因404而失败。你不会看到NoSuchBeanDefinitionException
,但效果是一样的。
确保您的bean在适当的上下文中注册,即。注册WebMVC(HandlerMapping
,HandlerAdapter
,ViewResolver
,ExceptionResolver
等)的豆子可以找到它们。最好的解决方案是正确隔离bean。 DispatcherServlet
负责路由和处理请求,因此所有相关的bean都应该进入其上下文。加载根上下文的ContextLoaderListener
应该初始化应用程序其余部分所需的任何bean:服务,存储库等。
一些已知类型的豆类由Spring以特殊方式处理。例如,如果您尝试将MovieCatalog
数组注入字段中
@Autowired
private MovieCatalog[] movieCatalogs;
Spring将找到MovieCatalog
类型的所有bean,将它们包装在一个数组中,然后注入该数组。这在Spring documentation discussing @Autowired
中有描述。类似的行为适用于Set
,List
和Collection
注射目标。
对于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
bean。但你不想要一个Foo
豆,你想要一个List<Foo>
豆。 Before Spring 4.3, you'd have to use @Resource
对于本身定义为集合/映射或数组类型的bean,
@Resource
是一个很好的解决方案,通过唯一名称引用特定集合或数组bean。也就是说,从4.3开始,只要元素类型信息保存在@Autowired
返回类型签名或集合继承层次结构中,就可以通过Spring的@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忽略了注释该方法的任何@Resource
或@Autowired
,因为它是一个@Bean
方法,因此不能应用文档中描述的行为。但是,您可以使用Spring Expression Language(SpEL)按名称引用bean。在上面的示例中,您可以使用
@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
new Bar(foos);
}
引用名为fooList
的bean并注入它。