谈谈 Spring 中的 NoSuchBeanDefinitionException
这篇博客是来自对两篇文章的翻译,原文链接如下。这两篇文章都总结了在使用 Spring 框架时可能造成
NoSuchBeanDefinitionException
的情况,以及应该如何解决。
原文链接
java - What is a NoSuchBeanDefinitionException and how do I fix it? - Stack Overflow Spring NoSuchBeanDefinitionException | Baeldung
概述
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 的定义。例子如下:
@Component
public class BeanA {
@Autowired
private BeanB dependency;
//...
}
当在 BeanA 中注入 BeanB 时,如果在 Spring 上下文中找不到 BeanB 的定义,就会抛出 NSBDE。异常信息如下:
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:
package org.baeldung.packageB;
@Component
public class BeanB { ...}
如果 BeanB 已经被声明为一个 Bean,就再确认 BeanB 所在的包有没有被扫描。
@Configuration
@ComponentScan("org.baeldung.packageB")
public class ContextWithJavaConfig {
}
情况2: No qualifying bean of type […] is defined
还有一种可能抛出 NSBDE 的情况是在上下文中存在着两个 Bean,比如有一个接口 IBeanB,它有两个实现类 BeanB1 和 BeanB2。
@Component
public class BeanB1 implements IBeanB {
//
}
@Component
public class BeanB2 implements IBeanB {
//
}
现在,如果 BeanA 按照下面的方式注入,那么 Spring 将不知道要注入两个实现中的哪一个,就会抛出 NSBDE。
@Component
public class BeanA {
@Autowired
private IBeanB dependency;
}
异常信息如下:
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)。
@Component
public class BeanA {
@Autowired
@Qualifier("beanB2")
private IBeanB dependency;
}
除了指定名字,我们还可以将其中一个 Bean 加上 @Primary
的注解,这样会选择加了 Primary 注解的 Bean 来注入,而不会抛异常:
@Component
@Primary
public class BeanB1 implements IBeanB {
//
}
这样 Spring 就能够知道到底应该注入哪个 Bean 了。
情况3: No Bean Named […] is defined
NSBDE 还可能在从 Spring 上下文中通过名字获取一个 Bean 时抛出。
@Component
public class BeanA implements InitializingBean {
@Autowired
private ApplicationContext context;
@Override
public void afterPropertiesSet() {
context.getBean("someBeanName");
}
}
在这种情况中,如果找不到指定名字 Bean 的 Definition,就会抛出如下异常:
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 的。
@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private ServiceB serviceB;
...
}