Sot*_*lis 51 java spring applicationcontext
请NoSuchBeanDefinitionException在Spring中解释以下关于异常的内容:
本文旨在对NoSuchBeanDefinitionException使用Spring的应用程序中出现的问题进行全面的问答.
Sot*_*lis 85
The javadoc of NoSuchBeanDefinitionException explains
Exception thrown when a
BeanFactoryis 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.
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.
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.
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 {}
Run Code Online (Sandbox Code Playgroud)
我们还没有Foo通过@Bean方法,@Component扫描,XML定义或任何其他方式为该类型注册bean定义.由此BeanFactory管理的AnnotationConfigApplicationContext没有迹象表明在哪里获得所请求的bean getBean(Foo.class).上面的片段抛出
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.example.Foo] is defined
Run Code Online (Sandbox Code Playgroud)
同样,在尝试满足@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 { }
Run Code Online (Sandbox Code Playgroud)
这里,Foo通过注册bean定义@ComponentScan.但春天一无所知Bar.因此,在尝试自动装配bean实例的bar字段时,它无法找到相应的Foobean.它抛出(嵌套在一个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)}
Run Code Online (Sandbox Code Playgroud)
注册bean定义有多种方法.
@Bean方法在一@Configuration类或<bean>在XML配置@Component(以及它的元注释,例如.@Repository)通过@ComponentScan或<context:component-scan ... />在XML中GenericApplicationContext#registerBeanDefinitionBeanDefinitionRegistryPostProcessor...和更多.
确保您期望的bean已正确注册.
一个常见的错误是多次注册bean,即.将上述选项混合为相同类型.例如,我可能有
@Component
public class Foo {}
Run Code Online (Sandbox Code Playgroud)
和XML配置
<context:component-scan base-packages="com.example" />
<bean name="eg-different-name" class="com.example.Foo />
Run Code Online (Sandbox Code Playgroud)
这样的配置将注册两个类型的bean Foo,一个具有名称foo,另一个具有名称eg-different-name.确保你没有意外地注册超过你想要的豆子.这导致我们......
如果您同时使用基于XML和注释的配置,请确保从另一个配置中导入一个.XML提供
<import resource=""/>
Run Code Online (Sandbox Code Playgroud)
而Java提供了@ImportResource注释.
有时您需要多个bean用于相同类型(或接口).例如,您的应用程序可能使用两个数据库,一个MySQL实例和一个Oracle实例.在这种情况下,您将有两个DataSourcebean来管理每个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 {}
Run Code Online (Sandbox Code Playgroud)
投
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
Run Code Online (Sandbox Code Playgroud)
因为通过@Bean方法注册的两个bean都满足了要求BeanFactory#getBean(Class),即.他们都实施了DataSource.在这个例子中,Spring没有机制来区分或优先考虑两者.但这种机制存在.
您可以使用@Primary(和它等价于XML)的描述文件,并在这个职位.随着这种变化
@Bean(name = "mysql")
@Primary
public DataSource mysql() { return new MySQL(); }
Run Code Online (Sandbox Code Playgroud)
the previous snippet would not throw the exception and would instead return the 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,
@Bean(name = "mysql")
@Qualifier(value = "main")
public DataSource mysql() { return new MySQL(); }
Run Code Online (Sandbox Code Playgroud)
could now be injected as
@Qualifier("main") // or @Qualifier("mysql"), to use the bean name
private DataSource dataSource;
Run Code Online (Sandbox Code Playgroud)
without issue. @Resource is also an option.
Just as there are multiple ways to register beans, there are also multiple ways to name them.
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> 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
The value may indicate a suggestion for a logical component name, to be turned into a Spring bean in case of an autodetected component.
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.
@Qualifier, as mentioned earlier, lets you add more aliases to a bean.
Make sure you use the right name when autowiring by name.
Bean definition profiles allow you to register beans conditionally. @Profile, specifically,
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.activeproperty 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@ActiveProfilesannotation.
Consider this examples where the spring.profiles.active property is not set.
@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 {
}
Run Code Online (Sandbox Code Playgroud)
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.
Instead, if I initialize the ApplicationContext while registering the appropriate profile
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("StackOverflow");
ctx.register(Example.class);
ctx.refresh();
Run Code Online (Sandbox Code Playgroud)
the bean is registered and can be returned/injected.
Spring uses AOP proxies a lot to implement advanced behavior. Some examples include:
@Transactional @Cacheable@Async and @ScheduledTo achieve this, Spring has two options:
Take this example of JDK proxies (achieved through @EnableAsync's default 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());
}
}
Run Code Online (Sandbox Code Playgroud)
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
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.example.HttpClientImpl] is defined
Run Code Online (Sandbox Code Playgroud)
Spring wrapped the HttpClientImpl bean and exposed it through a Proxy object that only implements HttpClient. So you could retrieve it with
ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;
Run Code Online (Sandbox Code Playgroud)
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.
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.
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:
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.
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.
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
@Autowired
private MovieCatalog[] movieCatalogs;
Run Code Online (Sandbox Code Playgroud)
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.
For a Map injection target, Spring will also behave this way if the key type is String. For example, if you have
@Autowired
private Map<String, MovieCatalog> movies;
Run Code Online (Sandbox Code Playgroud)
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.
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
public List<Foo> fooList() {
return Arrays.asList(new Foo());
}
Run Code Online (Sandbox Code Playgroud)
and inject them
@Autowired
private List<Foo> foos;
Run Code Online (Sandbox Code Playgroud)
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
For beans that are themselves defined as a collection/map or array type,
@Resourceis 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@Autowiredtype matching algorithm as well, as long as the element type information is preserved in@Beanreturn 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.
This works for constructor, setter, and field injection.
@Resource
private List<Foo> foos;
// or since 4.3
public Example(@Autowired List<Foo> foos) {}
Run Code Online (Sandbox Code Playgroud)
However, it will fail for @Bean methods, ie.
@Bean
public Bar other(List<Foo> foos) {
new Bar(foos);
}
Run Code Online (Sandbox Code Playgroud)
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
@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
new Bar(foos);
}
Run Code Online (Sandbox Code Playgroud)
to refer to the bean named fooList and inject that.
| 归档时间: |
|
| 查看次数: |
63757 次 |
| 最近记录: |