SpringNotes

第 02 章:IoC 容器

zjc 于 2026-01-02 发布

这是《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 在其上增加了:

  1. 资源加载;
  2. 环境抽象;
  3. 事件机制;
  4. 国际化;
  5. 注解驱动;
  6. 自动后处理器注册。

常见实现:

容器 常见场景
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 的真实容器还必须处理:

  1. 接口到实现的候选关系;
  2. 构造参数解析;
  3. 循环依赖;
  4. 作用域;
  5. 代理;
  6. 生命周期回调;
  7. 条件装配;
  8. 线程安全;
  9. FactoryBean;
  10. 类加载器。

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 完成

限制:

  1. 构造器循环依赖无法通过该机制解决;
  2. prototype 作用域不适用;
  3. 如果 Bean 需要 AOP,提前暴露的可能是代理引用;
  4. Spring Boot 2.6+ 默认禁止循环依赖;
  5. 循环依赖通常说明职责边界不清晰。

更好的修复:

  1. 抽出第三个服务;
  2. 使用事件解耦;
  3. 延迟调用;
  4. 重新划分模块;
  5. 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 的关键一步。

思考题

  1. BeanDefinition 和 Bean 实例有什么区别?
  2. BeanFactoryPostProcessorBeanPostProcessor 的执行时机有何不同?
  3. 为什么 Spring Boot 2.6+ 默认禁止循环依赖?
  4. FactoryBean 和普通 Bean 有什么区别?
  5. 遇到 NoSuchBeanDefinitionException 时如何排查?