这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 IoC 容器是 Spring 的地基。它负责加载 Bean 定义、实例化对象、注入依赖、处理生命周期,并在合适时机销毁对象。理解容器的工作方式,后面的事务失效、循环依赖、条件装配、作用域问题都会变得容易解释。
2.1 容器的基本职责
BeanDefinition
-> BeanFactory
-> 实例化
-> 属性填充
-> 初始化
-> 放入单例池
-> 销毁回调
核心职责:
| 职责 | 说明 |
|---|---|
| 注册 Bean 定义 | 注解、XML、Java Config 都会转成 BeanDefinition |
| 实例化 Bean | 反射或工厂方法创建对象 |
| 依赖注入 | 构造器、字段、setter 等方式 |
| 生命周期回调 | Aware、BeanPostProcessor、InitializingBean 等 |
| 作用域管理 | singleton、prototype、request、session 等 |
| 事件发布 | ApplicationEvent 与监听器 |
2.2 BeanFactory 与 ApplicationContext
BeanFactory 提供基础容器能力,ApplicationContext 在其上增加了:
- 资源加载;
- 环境抽象;
- 事件机制;
- 国际化;
- 注解驱动;
- 自动后处理器注册。
常见实现:
| 容器 | 常见场景 |
|---|---|
| AnnotationConfigApplicationContext | 普通 Java 配置 |
| AnnotationConfigServletWebApplicationContext | Servlet Web |
| GenericWebApplicationContext | 通用 Web 容器 |
| ReactiveWebServerApplicationContext | Reactive 应用 |
Spring Boot 不会让开发者手工 new 容器,而是通过 SpringApplication.run 创建并刷新合适类型的 ApplicationContext。
2.3 BeanDefinition
BeanDefinition 是 Spring 的“对象图纸”,记录:
beanClassName
scope
lazyInit
primary
dependsOn
autowireCandidate
factoryBeanName
factoryMethodName
initMethodName
destroyMethodName
propertyValues
constructorArgumentValues
不同来源会被统一解析:
| 来源 | 解析方式 |
|---|---|
@Component |
ClassPathScanningCandidateComponentProvider 扫描 |
@Bean |
ConfigurationClassPostProcessor 解析 |
| XML | XmlBeanDefinitionReader |
ImportBeanDefinitionRegistrar |
编程式注册 |
BeanDefinitionRegistryPostProcessor |
容器早期扩展 |
这也是 MyBatis、Dubbo 等框架能与 Spring 集成的基础。
2.4 refresh 流程
AbstractApplicationContext.refresh() 是容器启动主线:
1. prepareRefresh
2. obtainFreshBeanFactory
3. prepareBeanFactory
4. postProcessBeanFactory
5. invokeBeanFactoryPostProcessors
6. registerBeanPostProcessors
7. initMessageSource
8. initApplicationEventMulticaster
9. onRefresh
10. registerListeners
11. finishBeanFactoryInitialization
12. finishRefresh
关键阶段:
| 阶段 | 作用 |
|---|---|
| invokeBeanFactoryPostProcessors | 修改或补充 BeanDefinition |
| registerBeanPostProcessors | 注册前后处理器 |
| onRefresh | Web 容器创建内嵌服务器 |
| finishBeanFactoryInitialization | 初始化非懒加载单例 |
| finishRefresh | 发布容器就绪事件 |
Spring Boot 3.x 的内部实现仍有调整,但主线思想稳定。
2.5 手写一个最小容器
为了去掉魔法,可以手写一个极简版 IoC:
class Container {
private final Map<String, Object> singletons = new ConcurrentHashMap<>();
private final Map<String, Class<?>> definitions = new ConcurrentHashMap<>();
void register(Class<?> type) {
definitions.put(type.getName(), type);
}
synchronized Object getBean(Class<?> type) throws Exception {
String name = type.getName();
if (singletons.containsKey(name)) {
return singletons.get(name);
}
Class<?> impl = definitions.get(name);
Object bean = impl.getDeclaredConstructor().newInstance();
singletons.put(name, bean);
return bean;
}
}
Spring 的真实容器还必须处理:
- 接口到实现的候选关系;
- 构造参数解析;
- 循环依赖;
- 作用域;
- 代理;
- 生命周期回调;
- 条件装配;
- 线程安全;
- FactoryBean;
- 类加载器。
2.6 Bean 名称与别名
默认命名规则:
| 定义方式 | 默认名 |
|---|---|
@Component |
类名首字母小写 |
@Component("orderService") |
显式指定 |
@Bean |
方法名 |
@Bean("mapper") |
显式指定 |
XML id |
显式指定 |
特殊情况:如果类名前两个字母都是大写,名称可能保持原类名。不要依赖这类边界规则,重要 Bean 应显式命名。
别名示例:
@Bean(name = {"objectMapper", "jsonMapper"})
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
2.7 条件装配
常用条件注解:
| 注解 | 含义 |
|---|---|
@ConditionalOnClass |
类存在 |
@ConditionalOnMissingBean |
容器中没有指定 Bean |
@ConditionalOnProperty |
属性匹配 |
@ConditionalOnBean |
指定 Bean 存在 |
@ConditionalOnWebApplication |
是 Web 应用 |
@Profile |
环境匹配 |
示例:
@Configuration
public class IdGeneratorConfig {
@Bean
@ConditionalOnMissingBean(IdGenerator.class)
public IdGenerator uuidGenerator() {
return UUID::randomUUID;
}
}
这类代码常见于 Starter:框架给默认实现,业务覆盖后默认实现退出。
2.8 FactoryBean
FactoryBean 是创建 Bean 的工厂对象。
@Component
public class PaymentClientFactoryBean implements FactoryBean<PaymentClient> {
@Override
public PaymentClient getObject() {
return new PaymentClient("https://payment.example.com");
}
@Override
public Class<?> getObjectType() {
return PaymentClient.class;
}
}
注入的是 getObject() 返回的对象;如果想拿工厂本身,需要在名称前加 &:
Object factory = applicationContext.getBean("&paymentClientFactoryBean");
典型用途:代理对象、第三方客户端、复杂构建流程。
2.9 循环依赖
典型场景:
A 依赖 B
B 依赖 A
Spring 对“单例 + setter/字段注入”的三级缓存处理:
| 缓存 | 存放内容 |
|---|---|
| singletonObjects | 完整 Bean |
| earlySingletonObjects | 提前暴露的早期 Bean |
| singletonFactories | 可以生成早期引用的工厂 |
流程:
创建 A
-> 提前暴露 A 的工厂
-> 注入 B
-> 创建 B
-> B 注入 A 的早期引用
-> B 完成
-> A 完成
限制:
- 构造器循环依赖无法通过该机制解决;
- prototype 作用域不适用;
- 如果 Bean 需要 AOP,提前暴露的可能是代理引用;
- Spring Boot 2.6+ 默认禁止循环依赖;
- 循环依赖通常说明职责边界不清晰。
更好的修复:
- 抽出第三个服务;
- 使用事件解耦;
- 延迟调用;
- 重新划分模块;
- 用
ObjectProvider只在必要场景延迟获取。
2.10 常见问题排查
| 异常 | 常见原因 |
|---|---|
| NoSuchBeanDefinitionException | 未扫描、条件不满足、名字不匹配 |
| NoUniqueBeanDefinitionException | 多个候选缺少 @Primary 或 @Qualifier |
| BeanCurrentlyInCreationException | 循环依赖 |
| UnsatisfiedDependencyException | 缺少依赖或构造器无法解析 |
| BeanDefinitionStoreException | 配置类或扫描路径错误 |
| BeanIsAbstractException | 注入抽象 Bean |
排查命令:
applicationContext.containsBean("orderService");
Arrays.toString(applicationContext.getBeanDefinitionNames());
applicationContext.getBeansOfType(OrderRepository.class);
Actuator 也可查看:
/actuator/beans
生产环境应谨慎暴露该端点,因为它会暴露内部结构。
本章小结
IoC 容器以 BeanDefinition 为输入,经过容器刷新、BeanFactoryPostProcessor、BeanPostProcessor 和单例初始化,把对象组装成可用应用。BeanFactory 是基础容器,ApplicationContext 是面向应用的增强实现。理解 refresh 主线、条件装配、FactoryBean 和循环依赖边界,是从会用 Spring 到理解 Spring 的关键一步。
思考题
- BeanDefinition 和 Bean 实例有什么区别?
BeanFactoryPostProcessor和BeanPostProcessor的执行时机有何不同?- 为什么 Spring Boot 2.6+ 默认禁止循环依赖?
- FactoryBean 和普通 Bean 有什么区别?
- 遇到
NoSuchBeanDefinitionException时如何排查?