概述
org.springframework.beans.factory.NoSuchBeanDefinitionException
是很常见的异常,可以说绝大多数使用过 Spring 的人都曾遇到过它。本文旨在总结下NoSuchBeanDefinitionException(以下简称 NSBDE)的含义,哪些情况下可能抛出 NSBDE,和如何解决(文中配置均用 JavaConfig)。
什么是 NoSuchBeanDefinitionException
从字面其实就很好理解,NoSuchBeanDefinitionException 就是没有找到指定 Bean 的 Definition。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.
下面看看可能抛出 NSBDE 的一些情况。
情况1: No qualifying bean of type […] found for dependency
最常见的抛出 NSBDE 的情况就是在一个 BeanA 中注入 BeanB 时找不到 BeanB 的定义。例子如下:
1 2 3 4 5 6 | public class BeanA { private BeanB dependency; //... } |
当在 BeanA 中注入 BeanB 时,如果在 Spring 上下文中找不到 BeanB 的定义,就会抛出 NSBDE。异常信息如下:
1 2 3 4 5 6 7 | org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.baeldung.packageB.BeanB] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} |
抛异常的原因在异常信息中说的很清楚:expected at least 1 bean which qualifies as autowire candidate for this dependency
。所以要么是 BeanB 不存在在 Spring 上下文中(比如没有标注 @ Component,@Repository,@Service, @Controller等注解) ,要么就是 BeanB 所在的包没有被 Spring 扫描到。
解决办法就是先确认 BeanB 有没有被某些注解声明为 Bean:
1 2 3 | package org.baeldung.packageB; public class BeanB { ...} |
如果 BeanB 已经被声明为一个 Bean,就再确认 BeanB 所在的包有没有被扫描。
1 2 3 4 | "org.baeldung.packageB") (public class ContextWithJavaConfig { } |
情况2: No qualifying bean of type […] is defined
还有一种可能抛出 NSBDE 的情况是在上下文中存在着两个 Bean,比如有一个接口 IBeanB,它有两个实现类 BeanB1 和 BeanB2。
1 2 3 4 5 6 7 8 | public class BeanB1 implements IBeanB { // } public class BeanB2 implements IBeanB { // } |
现在,如果 BeanA 按照下面的方式注入,那么 Spring 将不知道要注入两个实现中的哪一个,就会抛出 NSBDE。
1 2 3 4 5 | public class BeanA { private IBeanB dependency; } |
异常信息如下:
1 2 3 4 | Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.baeldung.packageB.IBeanB] is defined: expected single matching bean but found 2: beanB1,beanB2 |
仔细看异常信息会发现,并不是直接抛出 NSBDE,而是它的子类 NoUniqueBeanDefinitionException
,这是 Spring 3.2.1 之后引入的新异常,目的就是为了和第一种找不到 Bean Definition 的情况作区分。
解决办法1就是利用 @Qualifier
注解,明确指定要注入的 Bean 的名字(BeanB2 默认的名字就是 beanB2)。
1 2 3 4 5 6 | public class BeanA { "beanB2") ( private IBeanB dependency; } |
除了指定名字,我们还可以将其中一个 Bean 加上 @Primary
的注解,这样会选择加了 Primary 注解的 Bean 来注入,而不会抛异常:
1 2 3 4 5 | public class BeanB1 implements IBeanB { // } |
这样 Spring 就能够知道到底应该注入哪个 Bean 了。
情况3: No Bean Named […] is defined
NSBDE 还可能在从 Spring 上下文中通过名字获取一个 Bean 时抛出。
1 2 3 4 5 6 7 8 9 | public class BeanA implements InitializingBean { private ApplicationContext context; public void afterPropertiesSet() { context.getBean("someBeanName"); } } |
在这种情况中,如果找不到指定名字 Bean 的 Definition,就会抛出如下异常:
1 2 | Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'someBeanName' is defined |
情况4: 代理 Beans
Spring 通过 AOP 代理 实现了许多高级功能,比如:
- 通过 @Transactional完成 事务管理
- 通过 @Cacheable实现缓存
- 通过 @Async和 @Scheduled实现任务调度和异步执行
Spring 有两种方式实现代理:
- 利用 JDK 动态代理机制 ,在运行时为
实现了某些接口
的类动态创建一个实现了同样接口的代理对象。 - 使用 CGLIB,CGLIB 可以在运行期扩展Java类与实现Java接口,也就是说当一个类没有实现接口时,必须用 CGLIB 生成代理对象。
所以,当 Spring 上下文中的一个实现了某个接口的 Bean 通过JDK 动态代理机制被代理时,代理类并不是继承了目标类,而是实现同样的接口。
也正因为如此,如果一个 Bean 通过接口注入时,可以成功被注入。但如果是通过真正的类注入,那么 Spring 将无法找到匹配这个类的 Definition——因为代理类并没有继承这个类。
以 Spring 中比较常见的事务管理为例,假设 ServiceA 中要注入 ServiceB,两个 Service 均标注了 @Transactional
注解来进行事务管理,那么下面的注入方式是不会正常 work 的。
1 2 3 4 5 6 7 8 9 10 11 12 | public class ServiceA implements IServiceA{ private ServiceB serviceB; ... } public class ServiceB implements IServiceB{ } |
解决办法就是通过接口来进行注入:
1 2 3 4 5 6 7 8 9 10 11 | public class ServiceA implements IServiceA{ private IServiceB serviceB; } public class ServiceB implements IServiceB{ } |