本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
最近一个网友急冲冲的通过微信找到我,说“哥,我的 SpringBoot 项目启动报错 NoSuchBeanDefinitionException,我排查了很久没找到原因,帮我看看呗!”
这个网友,我细看了一下图像和微信名,虽然我们加过好友,但几乎没啥“交情”。这“交情”不是说嘘寒问暖、不是请茶吃饭也不是我需要红包套好处,而是和我的公众号没交集。即,他没点过赞,也没留过言,转发和在看也没有。到这个份上,实战找不出帮他的理由。
我说让你团队里的小伙伴看看呗,找 leader 也行。你 leader 不会只分任务“不管兵”吧。
后来,他找各种理由,磨我了很多次,我于心不忍,就帮他看了看问题。所以,今天我们就一起来聊聊这个让无数 Java 开发者“怀疑人生”的经典问题:NoSuchBeanDefinitionException。这个异常就像 Spring 世界的“404”,明明看着代码没问题,它就告诉你,Bean 找不到!
简化的案例
下面我简化了网友的代码,就先从他这个“诡异”的案例说起吧。
在他的代码中,确实定义了 UserService,也加了 @Service 注解,但在项目启动时,@Autowired 依赖的 bean 就是被 Spring 说找不到 bean?
简化的代码长这样。
@Service
public class UserService {
// ...
}
@RestController
public class UserController {
// 启动报错 NoSuchBeanDefinitionException
@Autowired
private UserService userService;
}
类似这种“明明有却找不到”的问题,我敢断言 90% 都和 Bean 的生命周期有关。接下来我们就一起抽丝剥茧,看看这背后到底藏着什么秘密。
NoSuchBeanDefinitionException 是什么?
简单来说,这是 Spring 容器在“依赖注入”阶段发出的“求救信号”。当 Spring 尝试:
- 按类型注入(
@Autowired) - 按名称注入(
@Resource) - 显式获取 Bean(
getBean(Class<?>))
但找不到符合条件的 Bean 时,就会抛出这个异常。它的核心信息是,容器里没有,我注入不了。
高频原因
下面是我总结的高频原因 Top 5,很可能 90% 的问题都在这里。
| 原因分类 | 具体表现 | 解决方案 |
|---|---|---|
| 1. 包扫描失效 | Bean 不在 @SpringBootApplication 扫描路径下 | 检查包结构,或使用 @ComponentScan手动指定 |
| 2. 条件装配的坑 | @ConditionalOnProperty 等条件不满足 | 检查 application.properties 配置是否匹配 |
| 3. 作用域冲突 | 多例 Bean 依赖单例 Bean 的特殊场景 | 使用 @Lazy 或 ObjectFactory 延迟获取 |
| 4. 循环依赖 | 两个 Bean 互相依赖,A 依赖 B,B 又依赖 A | 重构代码,或使用构造函数注入 + @Lazy |
| 5. 自定义生命周期处理器坏事情 | BeanPostProcessor 等实现不当 | 划重点!下面展开讲 |
下面我们慢慢展开。
排查思路
这里我推荐三步定位法。
第一步,确认 Bean 是否被加载过。
启动时加 VM 参数 -Ddebug,如-Dspring-boot.run.arguments=--debug或者配置logging.level.org.springframework.boot.autoconfigure=DEBUG等。然后启动项目,观察控制台打印的自动配置报告,看你关注的 Bean 是否在positive matches里。
第二步,检查 BeanDefinition 是否存在。
在启动类中加一段调试代码,如下所示。
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(DemoApp.class, args);
// 查看容器中所有UserService的BeanDefinition
String[] beanNames = ctx.getBeanNamesForType(UserService.class);
System.out.println("找到UserService Bean: " + Arrays.toString(beanNames));
}
如果前两步,还搞不定,则可以使用第三步,trace 日志全开。
在 application.properties 中配置以下内容。
logging.level.org.springframework.beans.factory.support=trace
logging.level.org.springframework.context.annotation=trace
观察日志,你会看到 Bean 的注册、实例化、注入全过程,就像看电影一样。
Bean生命周期中的定时炸弹
这个我们重点讲讲,算是今天的重头戏。
Spring Bean 的生命周期有11 个关键步骤,某些自定义实现如果没写好,就会在早期阶段导致其他 Bean 找不到。
Bean生命周期全景图
步骤描述如下,供参考。
1. 加载BeanDefinition →
2. 实例化 →
3. 填充属性 →
4. Aware接口回调 →
5. BeanPostProcessor.postProcessBeforeInitialization →
6. @PostConstruct →
7. InitializingBean →
8. init-method →
9. BeanPostProcessor.postProcessAfterInitialization →
10. Bean就绪 →
11. @PreDestroy → 销毁
手绘序列图参考https://mp.weixin.qq.com/s/WD8QBT_sc3rK4MiLc7CKsw中所示。
看懂了上面的 Bean 的生命周期后,其中有 3 个比较危险(或高危)的接口,下面我们展开说说。
最危险的 BeanPostProcessor
它的工作原理是,这个接口会在每个 Bean 初始化前后被调用。如果你在这里面尝试获取其他 Bean,容器可能还没准备好。
致命案例如下所示。
@Component
public class DangerousPostProcessor implements BeanPostProcessor {
// 危险!BeanPostProcessor 会提前实例化
@Autowired
private UserService userService;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 如果在这里调用getBean()找其他Bean,而那个Bean依赖UserService...
// 就会循环报错:NoSuchBeanDefinitionException
return bean;
}
}
为什么出问题?因为BeanPostProcessor本身会在容器早期阶段就被实例化,此时它依赖的 Bean 可能还没完成注册,导致连锁反应。
对应的解决方案如下。
@Component
public class SafePostProcessor implements BeanPostProcessor {
// 不要直接注入,改用BeanFactory延迟获取
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 等容器稳定后再获取
if (beanFactory.containsBean("userService")) {
UserService userService = beanFactory.getBean(UserService.class);
// ... 使用
}
return bean;
}
}
ImportBeanDefinitionRegistrar
它的工作原理是,用于动态注册 BeanDefinition。如果注册时机不对,后续@Autowired时 Bean 还未完成配置。所以,它对注册时机非常敏感。
看下面这个致命案例。
public class BadRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 错误:直接注册一个依赖其他 Bean 的配置类
registry.registerBeanDefinition("myConfig", new RootBeanDefinition(MyConfig.class));
}
}
@Configuration
public class MyConfig {
// 可能还没注册!启动时报错
@Autowired
private DataSource dataSource;
}
对应的解决方案是,使用BeanFactoryPostProcessor确保在标准 Bean 注册完成后执行。
@Component
public class SafeRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 此时其他BeanDefinition已基本加载完毕
if (registry.containsBeanDefinition("dataSource")) {
registry.registerBeanDefinition("myBean", ...);
}
}
}
FactoryBean
注意,这里的工厂模式陷阱。它的工作原理是,FactoryBean 的getObject()返回实际 Bean。如果这个方法依赖其他 Bean,但工厂本身初始化过早…
对应致命案例。
@Component
public class MyFactoryBean implements FactoryBean<TargetBean> {
// 可能还没就绪
@Autowired
private DependencyBean dependency;
@Override
public TargetBean getObject() {
// 如果dependency为null,创建出来的TargetBean可能不完整
return new TargetBean(dependency);
}
}
对应的解决方案是,让 FactoryBean 本身延迟加载,或不用@Autowired改用ObjectFactory。
@Component
@Lazy // 关键!让工厂延迟初始化
public class MyFactoryBean implements FactoryBean<TargetBean> {
// 延迟获取
@Autowired
private ObjectProvider<DependencyBean> dependencyProvider;
@Override
public TargetBean getObject() {
return new TargetBean(dependencyProvider.getIfAvailable());
}
}
生命周期中的蝴蝶效应
注意,看下面这个问题传播路径。
[你的自定义处理器]
→ 提前触发实例化
→ 依赖其他 Bean
→ 那些 Bean 还没注册/初始化
→ 容器放弃治疗:NoSuchBeanDefinitionException
自定义处理器要千万小心,时刻提防 bean 的生命周期。
搞懂这些后,我们回到开头的案例,经过我的排查,发现真相是他自定义了一个注解,里面用到了注册后置处理器 BeanDefinitionRegistryPostProcessor,在实现里面不小心把一些 bean 搞丢了。
最佳实践
预防大于治疗,扁鹊的大哥医术最高超。
- 谨慎实现生命周期接口:90% 的需求用
@PostConstruct就能解决 - BeanPostProcessor 中不要注入 Bean:用
BeanFactory或ApplicationContext延迟获取 - 善用
@Lazy:对不确定的依赖加@Lazy打破循环 - 日志是王道:启动时打开 trace 日志,看清 Bean 的加载顺序
- 单测验证:用
ApplicationContextRunner测试配置类
@Test
void testMyConfiguration() {
new ApplicationContextRunner()
.withUserConfiguration(MyConfig.class)
.run(context -> {
assertThat(context).hasSingleBean(UserService.class);
});
}
总结
以后再遇到NoSuchBeanDefinitionException问题,可以像 Spring 一样思考,记住这个排查口诀:
先确认存不存在,再看加载顺序对不对,最后检查自定义处理器
Spring 容器就像一个精密钟表,每个齿轮(Bean)都有固定位置和时间。你的自定义代码如果打乱了节奏,钟表就会罢工。
下次再遇到这个问题,别急着重构,先问自己三个问题。
- 我的 Bean 在扫描路径里吗?
- 有没有条件注解把它“过滤”了?
- 是不是我的 Processor 手伸太长了?
以上,希望大家都能学到经验,能帮大家在 Spring 的世界里少踩坑、多优雅。我们明天见!

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » Spring Boot爆错NoSuchBeanDefinitionException,找不到Bean的11步生死劫