java - 什么是NoSuchBeanDefinitionException,我该如何解决?

标签 java spring applicationcontext

请在Spring中解释有关NoSuchBeanDefinitionException异常的以下内容:


这是什么意思?
在什么情况下会抛出?
我该如何预防?




这篇文章旨在对使用Spring的应用程序中NoSuchBeanDefinitionException的出现进行全面的问答。

最佳答案

javadoc of NoSuchBeanDefinitionException解释


  当请求BeanFactory的bean实例时引发异常
  它找不到定义。这可能表示不存在
  bean,非唯一bean或手动注册的单例实例
  没有关联的bean定义。


BeanFactory基本上是表示Spring's Inversion of Control container的抽象。它在内部和外部向您的应用程序公开bean。当无法找到或检索这些bean时,将抛出NoSuchBeanDefinitionException

以下是BeanFactory(或相关类)无法找到bean的简单原因,以及如何确保找到它的原因。



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)请求的bean。上面的代码片段

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定义。但是Spring对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>
XML中的@Component@Repository中的@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。确保您不会意外注册过多的bean。这导致我们...

如果同时使用基于XML和基于注释的配置,请确保从另一个导入。 XML提供

<import resource=""/>


而Java提供了@ImportResource注释。

预期使用单个匹配的Bean,但发现2个(或更多)

有时,您需要针对同一类型(或接口)的多个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没有机制来区分或区分两者。但是存在这样的机制。

您可以按照@Primarydocumentation中所述使用this post(及其在XML中的等效项)。有了这个改变

@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具有name


  此bean的名称,或者如果是复数,则为该bean的别名。如果离开
  未指定的Bean名称是带注释的方法的名称。
  如果指定,方法名称将被忽略。


<bean>具有id属性以表示bean的唯一标识符,并且name可用于创建一个或多个(XML)ID中非法的别名。

@Component及其元注释具有value


  该值可能指示对逻辑组件名称的建议,以
  如果有自动检测到的组件,则将其转换为Spring bean。


如果未指定,则会为带注释的类型(通常是该类型名称的小驼峰版本)自动生成一个Bean名称。

如前所述,@Qualifier使您可以向bean添加更多别名。

按名称自动布线时,请确保使用正确的名称。



更高级的案例

个人资料

Bean definition profiles允许您有条件地注册bean。 @Profile,具体地说,


  表示一个或多个组件可以注册
  更多指定的配置文件处于活动状态。
  
  配置文件是可以激活的命名逻辑组
  通过编程方式
  ConfigurableEnvironment.setActiveProfiles(java.lang.String...)
  通过将spring.profiles.active属性设置为JVM来声明式地
  系统属性,环境变量或Servlet上下文
  Web应用程序的web.xml中的参数。个人资料也可能是
  通过@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已注册,可以返回/注入。

AOP代理

Spring大量使用AOP proxies来实现高级行为。一些示例包括:


Transaction management@Transactional
Caching@Cacheable
Scheduling and asynchronous execution@Async@Scheduled


为此,Spring有两个选择:


使用JDK的Proxy类在运行时创建动态类的实例,该实例仅实现您的bean的接口并将所有方法调用委托给实际的bean实例。
使用CGLIB代理在运行时创建动态类的实例,该类同时实现目标bean的接口和具体类型,并将所有方法调用委托给实际的bean实例。


以这个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


Spring包装了HttpClientImpl bean,并通过仅实现ProxyHttpClient对象将其公开。所以你可以用

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 MVC

通过Spring,您可以使用ApplicationContext与其他ApplicationContext实例作为父级来构建ConfigurableApplicationContext#setParent(ApplicationContext)实例。子上下文可以在父上下文中访问bean,但事实并非如此。 This post详细介绍了何时使用此功能,尤其是在Spring MVC中。

在典型的Spring MVC应用程序中,您定义两个上下文:一个用于整个应用程序(根),另一个专门用于DispatcherServlet(路由,处理程序方法,控制器)。您可以在此处获得更多详细信息:


Difference between applicationContext.xml and spring-servlet.xml in Spring Framework


官方文档here中也对此做了很好的解释。

Spring MVC配置中的一个常见错误是在根上下文中用@EnableWebMvc注释的@Configuration类或XML中的<mvc:annotation-driven />声明WebMVC配置,而在servlet上下文中使用@Controller Bean声明WebMVC配置。由于根上下文无法进入servlet上下文以查找任何bean,因此没有注册任何处理程序,并且所有请求均以404失败。您不会看到NoSuchBeanDefinitionException,但是效果是一样的。

确保您的bean在适当的上下文中注册,即。在注册WebMVC的bean(HandlerMappingHandlerAdapterViewResolverExceptionResolver等)中可以找到它们。最好的解决方案是正确隔离豆。 DispatcherServlet负责路由和处理请求,因此所有相关的bean应该进入其上下文。加载根上下文的ContextLoaderListener应该初始化应用程序其余部分需要的所有bean:服务,存储库等。

数组,集合和地图

Spring会以特殊方式处理某些已知类型的Bean。例如,如果您尝试将MovieCatalog数组注入字段

@Autowired
private MovieCatalog[] movieCatalogs;


Spring将找到所有MovieCatalog类型的bean,将它们包装在一个数组中,然后注入该数组。 Spring documentation discussing @Autowired中对此进行了描述。类似的行为适用于SetListCollection注入目标。

对于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 bean,而是想要List<Foo> bean。 Before Spring 4.3, you'd have to use @Resource


  对于本身定义为集合/映射或数组的bean
  类型,@Resource是一个很好的解决方案,请参考具体
  唯一名称的collection或array bean。也就是说,从4.3开始,
  集合/地图和数组类型可以通过Spring的
  @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忽略了任何@Resource@Autowired注释方法,因为它是@Bean方法,因此无法应用文档中描述的行为。但是,您可以使用Spring Expression Language(SpEL)来按其名称引用bean。在上面的示例中,您可以使用

@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
    new Bar(foos);
}


引用名为fooList的bean并将其注入。

关于java - 什么是NoSuchBeanDefinitionException,我该如何解决?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45014287/

相关文章:

java - 使用单个注释在根和调度程序应用程序上下文中自动配置 beans

spring - 将 xml 定义中的其他 bean 添加到已在运行时初始化的应用程序上下文中

java - 将 Vec4i 转换为 Java openCV

java - 字符串内子字符串的计数

java - Spring Boot SpEL ConditionalOnExpression 检查特定属性的多个值

java - JPA问题@OneToMany - 如何从数据库获取特定对象?

java - 为什么 .NET 不从 Web 服务反序列化我的原始数组?

java - 为什么 Joda Time 序列化表单如此之大,该怎么办?

java - spring + hibernate 405 不支持删除方法

java - ConversionServiceFactoryBean 不起作用