Spring IOC 容器 默认注册 Bean 的 8 条规则
(Spring Framework 6.x 源码级总结)
阅读提示:把下面 8 条规则背下来,再读 Spring 源码时,你会在任何一行代码里立刻知道「这个 BeanDefinition 是从哪儿来的」。
1️⃣ 环境扫描:从 启动类所在包 向下递归
@SpringBootApplication
内隐含@ComponentScan
无参数 → 扫描当前包及其子包。- 源码位置:
ComponentScanAnnotationParser#parse()
→ClassPathBeanDefinitionScanner#doScan()
。
2️⃣ 类级别注解 自动注册
遇到下列注解即注册为 singleton Bean:
@Component
(含@Service
、@Repository
、@Controller
等元注解)@Configuration
(特殊:还会生成 CGLIB 代理,见规则 4)@Bean
方法所在类如果被@Configuration
标注,则方法返回值也被注册。
3️⃣ @Bean 方法 注册规则
- 作用域:默认
singleton
;可通过@Scope
覆盖。 - 名字:
- 显式
value/name
→ 直接使用 - 空 → 方法名首字母小写
- 显式
- 重载保护:同名 Bean 后注册的覆盖先注册的(
DefaultListableBeanFactory#registerBeanDefinition
抛异常 → 允许覆盖开关spring.main.allow-bean-definition-overriding=true
)。
4️⃣ @Configuration
特殊逻辑
- 类本身注册为 lite
@Component
- 同时注册
ConfigurationClassPostProcessor
,它会:- 解析
@Bean
方法 - 解析
@Import
(普通类、ImportSelector、ImportBeanDefinitionRegistrar) - 解析
@ComponentScan
- 生成 CGLIB 代理 保证
@Bean
方法内部依赖仍是单例。
- 解析
5️⃣ @Import 的三类导入
类型 | 注册方式 | 示例 |
---|---|---|
普通类 | 直接注册 singleton | @Import(MyConfig.class) |
ImportSelector | selectImports() 返回 String[] | @EnableCaching |
ImportBeanDefinitionRegistrar | 手动 registry.registerBeanDefinition() | @EnableAspectJAutoProxy |
6️⃣ SPI 机制:spring.factories
& META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- Spring Boot 启动时读取
spring.factories
→EnableAutoConfiguration
列表 - 每个自动配置类内部再用
@Configuration
+@Bean
注册大量基础设施(DataSource、WebMvc、Security…)。
7️⃣ FactoryBean & BeanFactoryPostProcessor
FactoryBean#getObject()
的返回值被注册,名字为&beanName
(取实际对象去掉&
)。BeanFactoryPostProcessor
(如PropertySourcesPlaceholderConfigurer
)可在注册阶段 修改 或 新增 BeanDefinition。
8️⃣ 默认单例作用域 & 延迟初始化
- 默认作用域:
singleton
- 默认非延迟:容器启动即实例化;加
@Lazy
则延迟到首次调用getBean()
。 - 单例池:
DefaultSingletonBeanRegistry#singletonObjects
(ConcurrentHashMap)。
一张图总结
启动类包扫描 → 类注解(@Component/@Configuration) → @Bean方法↓@Import(三类) → @ComponentScan → spring.factories↓ConfigurationClassPostProcessor → 注册所有BeanDefinition↓DefaultListableBeanFactory#preInstantiateSingletons → 实例化
背完这 8 条,再读 Spring 源码时,你会在任意一行 registerBeanDefinition()
前立刻定位“这是哪条规则触发”。