0
点赞
收藏
分享

微信扫一扫

谈谈 Spring 中的 NoSuchBeanDefinitionException


概述

​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 有两种方式实现代理:

  1. 利用 JDK 动态代理机制 ,在运行时为​​实现了某些接口​​的类动态创建一个实现了同样接口的代理对象。
  2. 使用 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;
...
}

@Service
@Transactional
public class ServiceB implements IServiceB{
}


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


@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private IServiceB serviceB;
}

@Service
@Transactional
public class ServiceB implements IServiceB{
}


原作者:Giraffe


谈谈 Spring 中的 NoSuchBeanDefinitionException_java


举报

相关推荐

0 条评论