Please explain the following about NoSuchBeanDefinitionException
exception in Spring:
请在Spring中解释以下关于NoSuchBeanDefinitionException异常的内容:
这是什么意思?
它会在什么条件下抛出?
我该怎样预防呢?
This post is designed to be a comprehensive Q&A about occurrences of NoSuchBeanDefinitionException
in applications using Spring.
本文旨在对使用Spring的应用程序中出现NoSuchBeanDefinitionException进行全面的问答。
61
The javadoc of NoSuchBeanDefinitionException
explains
NoSuchBeanDefinitionException的javadoc解释了
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被要求提供无法找到定义的bean实例时抛出异常。这可能指向不存在的bean,非唯一bean或没有关联bean定义的手动注册的单例实例。
A BeanFactory
is basically the abstraction representing Spring's Inversion of Control container. It exposes beans internally and externally, to your application. When it cannot find or retrieve these beans, it throws a NoSuchBeanDefinitionException
.
BeanFactory基本上是表示Spring的Inversion of Control容器的抽象。它将bean内部和外部暴露给您的应用程序。当它无法找到或检索这些bean时,它会抛出NoSuchBeanDefinitionException。
Below are simple reasons why a BeanFactory
(or related classes) would not be able to find a bean and how you can make sure it does.
以下是BeanFactory(或相关类)无法找到bean以及如何确保它的原因的简单原因。
In the example below
在下面的例子中
@Configuration
public class Example {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
ctx.getBean(Foo.class);
}
}
class Foo {
}
we haven't registered a bean definition for the type Foo
either through a @Bean
method, @Component
scanning, an XML definition, or any other way. The BeanFactory
managed by the AnnotationConfigApplicationContext
therefore has no indication of where to get the bean requested by getBean(Foo.class)
. The snippet above throws
我们没有通过@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
Similarly, the exception could have been thrown while trying to satisfy an @Autowired
dependency. For example,
同样,在尝试满足@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 { }
Here, a bean definition is registered for Foo
through @ComponentScan
. But Spring knows nothing of Bar
. It therefore fails to find a corresponding bean while trying to autowire the bar
field of the Foo
bean instance. It throws (nested inside a UnsatisfiedDependencyException
)
这里,通过@ComponentScan为Foo注册了bean定义。但春天对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)}
There are multiple ways to register bean definitions.
注册bean定义有多种方法。
@Bean
method in a @Configuration
class or <bean>
in XML configuration@Configuration类中的@Bean方法或XML配置中的
@Component
(and its meta-annotations, eg. @Repository
) through @ComponentScan
or <context:component-scan ... />
in XML@Component(及其meta-annotations,例如@Repository)通过XML中的@ComponentScan或
GenericApplicationContext#registerBeanDefinition
手动通过GenericApplicationContext #registerBeanDefinition
BeanDefinitionRegistryPostProcessor
手动通过BeanDefinitionRegistryPostProcessor
...and more.
Make sure the beans you expect are properly registered.
确保您期望的bean已正确注册。
A common error is to register beans multiple times, ie. mixing the options above for the same type. For example, I might have
一个常见的错误是多次注册bean,即。将上述选项混合为相同类型。例如,我可能有
@Component
public class Foo {}
and an XML configuration with
和XML配置
<context:component-scan base-packages="com.example" />
<bean name="eg-different-name" class="com.example.Foo />
Such a configuration would register two beans of type Foo
, one with name foo
and another with name eg-different-name
. Make sure you're not accidentally registering more beans than you wanted. Which leads us to...
这样的配置将注册两个类型为Foo的bean,一个名为foo,另一个名为eg-different-name。确保你没有意外地注册超过你想要的豆子。这导致我们......
If you're using both XML and annotation-based configurations, make sure you import one from the other. XML provides
如果您同时使用基于XML和注释的配置,请确保从另一个配置中导入一个。 XML提供
<import resource=""/>
while Java provides the @ImportResource
annotation.
而Java提供@ImportResource注释。
There are times when you need multiple beans for the same type (or interface). For example, your application may use two databases, a MySQL instance and an Oracle one. In such a case, you'd have two DataSource
beans to manage connections to each one. For (simplified) example, the following
有时您需要多个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 {}
throws
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
because both beans registered through @Bean
methods satisfied the requirement of BeanFactory#getBean(Class)
, ie. they both implement DataSource
. In this example, Spring has no mechanism to differentiate or prioritize between the two. But such mechanisms exists.
因为通过@Bean方法注册的两个bean都满足了BeanFactory#getBean(Class)的要求,即。他们都实现了DataSource。在这个例子中,Spring没有机制来区分或优先考虑两者。但这种机制存在。
You could use @Primary
(and its equivalent in XML) as described in the documentation and in this post. With this change
您可以使用文档和本文中所述的@Primary(及其在XML中的等效项)。随着这种变化
@Bean(name = "mysql")
@Primary
public DataSource mysql() { return new MySQL(); }
the previous snippet would not throw the exception and would instead return the mysql
bean.
前一个代码片段不会抛出异常,而是返回mysql bean。
You can also use @Qualifier
(and its equivalent in XML) to have more control over the bean selection process, as described in the documentation. While @Autowired
is primarily used to autowire by type, @Qualifier
lets you autowire by name. For example,
您还可以使用@Qualifier(及其在XML中的等效项)来更好地控制bean选择过程,如文档中所述。虽然@Autowired主要用于按类型自动装配,但@Qualifier允许您按名称自动装配。例如,
@Bean(name = "mysql")
@Qualifier(value = "main")
public DataSource mysql() { return new MySQL(); }
could now be injected as
现在可以注入
@Qualifier("main") // or @Qualifier("mysql"), to use the bean name
private DataSource dataSource;
without issue. @Resource
is also an option.
没有问题。 @Resource也是一个选择。
Just as there are multiple ways to register beans, there are also multiple ways to name them.
正如注册bean有多种方法一样,也有多种方法可以命名它们。
@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.
此bean的名称,或者如果是复数,则为此bean的别名。如果未指定,则bean的名称是带注释的方法的名称。如果指定,则忽略方法名称。
<bean>
has the id
attribute to represent the unique identifier for a bean and name
can be used to create one or more aliases illegal in an (XML) id.
@Component
and its meta annotations have value
@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.
该值可以指示对逻辑组件名称的建议,在自动检测的组件的情况下将其转换为Spring bean。
If that's left unspecified, a bean name is automatically generated for the annotated type, typically the lower camel case version of the type name.
如果未指定,则会自动为带注释的类型生成bean名称,通常是类型名称的较低驼峰案例版本。
@Qualifier
, as mentioned earlier, lets you add more aliases to a bean.
如前所述,@ Qualifier允许您向bean添加更多别名。
Make sure you use the right name when autowiring by name.
确保在按名称自动装配时使用正确的名称。
Bean definition profiles allow you to register beans conditionally. @Profile
, specifically,
Bean定义配置文件允许您有条件地注册bean。 @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 thespring.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.概要文件是一个命名的逻辑分组,可以通过ConfigurableEnvironment.setActiveProfiles(java.lang.String ...)以编程方式激活,也可以通过将spring.profiles.active属性设置为JVM系统属性,作为环境变量或者以声明方式激活。 Web应用程序的web.xml中的Servlet上下文参数。也可以通过@ActiveProfiles注释在集成测试中以声明方式激活配置文件。
Consider this examples where the spring.profiles.active
property is not set.
请考虑未设置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 {
}
This will show no active profiles and throw a NoSuchBeanDefinitionException
for a Foo
bean. Since the StackOverflow
profile wasn't active, the bean wasn't registered.
这将显示没有活动的配置文件,并为Foo bean抛出NoSuchBeanDefinitionException。由于StackOverflow配置文件未处于活动状态,因此未注册该Bean。
Instead, if I initialize the ApplicationContext
while registering the appropriate profile
相反,如果我在注册相应的配置文件时初始化ApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("StackOverflow");
ctx.register(Example.class);
ctx.refresh();
the bean is registered and can be returned/injected.
bean已注册,可以返回/注入。
Spring uses AOP proxies a lot to implement advanced behavior. Some examples include:
Spring大量使用AOP代理来实现高级行为。一些例子包括:
@Transactional
使用@Transactional进行事务管理
@Cacheable
使用@Cacheable进行缓存
@Async
and @Scheduled
使用@Async和@Scheduled进行调度和异步执行
To achieve this, Spring has two options:
为实现这一目标,Spring有两个选择:
使用JDK的Proxy类在运行时创建动态类的实例,该实例仅实现bean的接口并将所有方法调用委托给实际的bean实例。
使用CGLIB代理在运行时创建动态类的实例,该实例实现目标bean的接口和具体类型,并将所有方法调用委托给实际的bean实例。
Take this example of JDK proxies (achieved through @EnableAsync
's default proxyTargetClass
of false
)
以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());
}
}
Here, Spring attempts to find a bean of type HttpClientImpl
which we expect to find because the type is clearly annotated with @Component
. However, instead, we get an exception
在这里,Spring试图找到一个我们希望找到的类型为HttpClientImpl的bean,因为该类型明显用@Component注释。但是,相反,我们得到一个例外
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.example.HttpClientImpl] is defined
Spring wrapped the HttpClientImpl
bean and exposed it through a Proxy
object that only implements HttpClient
. So you could retrieve it with
Spring包装了HttpClientImpl bean并通过仅实现HttpClient的Proxy对象公开它。所以你可以用它来检索它
ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;
It's always recommended to program to interfaces. When you can't, you can tell Spring to use CGLIB proxies. For example, with @EnableAsync
, you can set proxyTargetClass
to true
. Similar annotations (EnableTransactionManagement
, etc.) have similar attributes. XML will also have equivalent configuration options.
始终建议对接口进行编程。如果不能,您可以告诉Spring使用CGLIB代理。例如,使用@EnableAsync,您可以将proxyTargetClass设置为true。类似的注释(EnableTransactionManagement等)具有类似的属性。 XML也将具有等效的配置选项。
ApplicationContext
Hierarchies - Spring MVCSpring lets you build ApplicationContext
instances with other ApplicationContext
instances as parents, using ConfigurableApplicationContext#setParent(ApplicationContext)
. A child context will have access to beans in the parent context, but the opposite is not true. This post goes into detail about when this is useful, particularly in Spring MVC.
Spring允许您使用ConfigurableApplicationContext #setParent(ApplicationContext)将其他ApplicationContext实例作为父项构建ApplicationContext实例。子上下文可以访问父上下文中的bean,但反之则不然。这篇文章详细介绍了它何时有用,特别是在Spring MVC中。
In a typical Spring MVC application, you define two contexts: one for the entire application (the root) and one specifically for the DispatcherServlet
(routing, handler methods, controllers). You can get more details here:
在典型的Spring MVC应用程序中,您定义了两个上下文:一个用于整个应用程序(根),另一个专门用于DispatcherServlet(路由,处理程序方法,控制器)。您可以在此处获取更多详细信息
Spring Framework中applicationContext.xml和spring-servlet.xml之间的区别
It's also very well explained in the official documentation, here.
这里的官方文档也对它进行了很好的解释。
A common error in Spring MVC configurations is to declare the WebMVC configuration in the root context with @EnableWebMvc
annotated @Configuration
classes or <mvc:annotation-driven />
in XML, but the @Controller
beans in the servlet context. Since the root context cannot reach into the servlet context to find any beans, no handlers are registered and all requests fail with 404s. You won't see a NoSuchBeanDefinitionException
, but the effect is the same.
Spring MVC配置中的常见错误是在根上下文中使用@EnableWebMvc注释的@Configuration类或XML中的
Make sure your beans are registered in the appropriate context, ie. where they can be found by the beans registered for WebMVC (HandlerMapping
, HandlerAdapter
, ViewResolver
, ExceptionResolver
, etc.). The best solution is to properly isolate beans. The DispatcherServlet
is responsible for routing and handling requests so all related beans should go into its context. The ContextLoaderListener
, which loads the root context, should initialize any beans the rest of your application needs: services, repositories, etc.
确保您的bean在适当的上下文中注册,即。可以通过为WebMVC注册的bean找到它们(HandlerMapping,HandlerAdapter,ViewResolver,ExceptionResolver等)。最好的解决方案是正确隔离bean。 DispatcherServlet负责路由和处理请求,因此所有相关的bean都应该进入其上下文。加载根上下文的ContextLoaderListener应初始化应用程序其余部分所需的任何bean:服务,存储库等。
Beans of some known types are handled in special ways by Spring. For example, if you tried to inject an array of MovieCatalog
into a field
一些已知类型的豆类由Spring以特殊方式处理。例如,如果您尝试将MovieCatalog数组注入字段
@Autowired
private MovieCatalog[] movieCatalogs;
Spring will find all beans of type MovieCatalog
, wrap them in an array, and inject that array. This is described in the Spring documentation discussing @Autowired
. Similar behavior applies to Set
, List
, and Collection
injection targets.
Spring将找到MovieCatalog类型的所有bean,将它们包装在一个数组中,然后注入该数组。这在Spring讨论@Autowired的文档中有所描述。类似的行为适用于Set,List和Collection注入目标。
For a Map
injection target, Spring will also behave this way if the key type is String
. For example, if you have
对于Map注入目标,如果键类型为String,Spring也会以这种方式运行。例如,如果你有
@Autowired
private Map<String, MovieCatalog> movies;
Spring will find all beans of type MovieCatalog
and add them as values to a Map
, where the corresponding key will be their bean name.
Spring将找到MovieCatalog类型的所有bean,并将它们作为值添加到Map中,其中相应的键将是它们的bean名称。
As described previously, if no beans of the requested type are available, Spring will throw a NoSuchBeanDefinitionException
. Sometimes, however, you just want to declare a bean of these collection types like
如前所述,如果没有所请求类型的bean可用,Spring将抛出NoSuchBeanDefinitionException。但是,有时您只想声明这些集合类型的bean
@Bean
public List<Foo> fooList() {
return Arrays.asList(new Foo());
}
and inject them
并注入它们
@Autowired
private List<Foo> foos;
In this example, Spring would fail with a NoSuchBeanDefinitionException
because there are no Foo
beans in your context. But you didn't want a Foo
bean, you wanted a List<Foo>
bean. Before Spring 4.3, you'd have to use @Resource
在此示例中,Spring将因NoSuchBeanDefinitionException而失败,因为您的上下文中没有Foo bean。但是你不想要一个Foo bean,你想要一个List
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.对于本身定义为集合/映射或数组类型的bean,@ Resource是一个很好的解决方案,通过唯一名称引用特定的集合或数组bean。也就是说,从4.3开始,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中,集合/映射和数组类型也可以通过Spring的@Autowired类型匹配算法进行匹配。在这种情况下,限定符值可用于在相同类型的集合中进行选择,如上一段所述。
This works for constructor, setter, and field injection.
这适用于构造函数,setter和字段注入。
@Resource
private List<Foo> foos;
// or since 4.3
public Example(@Autowired List<Foo> foos) {}
However, it will fail for @Bean
methods, ie.
但是,对于@Bean方法,它将失败,即。
@Bean
public Bar other(List<Foo> foos) {
new Bar(foos);
}
Here, Spring ignores any @Resource
or @Autowired
annotating the method, because it's a @Bean
method, and therefore can't apply the behavior described in the documentation. However, you can use Spring Expression Language (SpEL) to refer to beans by their name. In the example above, you could use
这里,Spring忽略任何@Resource或@Autowired注释方法,因为它是@Bean方法,因此无法应用文档中描述的行为。但是,您可以使用Spring Expression Language(SpEL)按名称引用bean。在上面的示例中,您可以使用
@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
new Bar(foos);
}
to refer to the bean named fooList
and inject that.
引用名为fooList的bean并注入它。
本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:http://www.silva-art.net/blog/2016/08/26/9a87369dbbcad27de889edf2b603e3b1.html。