谈谈 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 的定义。例子如下:

1
2
3
4
5
6
@Component
public class BeanA {
@Autowired
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;
@Component
public class BeanB { ...}

如果 BeanB 已经被声明为一个 Bean,就再确认 BeanB 所在的包有没有被扫描。

1
2
3
4
@Configuration
@ComponentScan("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
@Component
public class BeanB1 implements IBeanB {
//
}
@Component
public class BeanB2 implements IBeanB {
//
}

现在,如果 BeanA 按照下面的方式注入,那么 Spring 将不知道要注入两个实现中的哪一个,就会抛出 NSBDE。

1
2
3
4
5
@Component
public class BeanA {
@Autowired
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
@Component
public class BeanA {
@Autowired
@Qualifier("beanB2")
private IBeanB dependency;
}

除了指定名字,我们还可以将其中一个 Bean 加上 @Primary的注解,这样会选择加了 Primary 注解的 Bean 来注入,而不会抛异常:

1
2
3
4
5
@Component
@Primary
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
@Component
public class BeanA implements InitializingBean {
@Autowired
private ApplicationContext context;
@Override
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 代理 实现了许多高级功能,比如:

Spring 有两种方式实现代理:

  1. 利用 JDK 动态代理机制 ,在运行时为实现了某些接口的类动态创建一个实现了同样接口的代理对象。
  2. 使用 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
@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private ServiceB serviceB;
...
}
@Service
@Transactional
public class ServiceB implements IServiceB{
}

解决办法就是通过接口来进行注入:

1
2
3
4
5
6
7
8
9
10
11
@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private IServiceB serviceB;
}
@Service
@Transactional
public class ServiceB implements IServiceB{
}

总结

今天天气好好啊~
小脑腐

分享到 评论