温故而知新,忆 Spring Bean 加载全流程

举报
不惑 发表于 2025/07/23 23:12:58 2025/07/23
【摘要】 一、Bean 加载只干三件事其实 Bean 的加载只做了三件事,解析 → 注册 → 实例化;解析配置: 无论 XML、注解还是 JavaConfig,本质都是把 <bean> 或 @Component 之类的信息解析成 BeanDefinition。注册元数据: BeanDefinition 统一塞进 BeanDefinitionRegistry(默认实现是 DefaultListableB...

一、Bean 加载只干三件事

其实 Bean 的加载只做了三件事,解析 → 注册 → 实例化;

  1. 解析配置: 无论 XML、注解还是 JavaConfig,本质都是把 <bean>@Component 之类的信息解析成 BeanDefinition

  2. 注册元数据: BeanDefinition 统一塞进 BeanDefinitionRegistry(默认实现是 DefaultListableBeanFactory)。

  3. 实例化 & 依赖注入: 真正 new 出对象,填充属性,执行各种回调,最后放进单例池。

二、refresh() 时序图

refresh() 几乎囊括了 IoC 全部门道,后面几段都从这里往下钻。接下来,我们通俗的解释一下,究竟都做了什么。

  1. refresh() 负责容器生命周期的「自举」。

  2. prepareRefresh() 校验环境、初始化属性占位符。

  3. obtainFreshBeanFactory() 创建或刷新 DefaultListableBeanFactory,确保是干净的工厂实例。

  4. prepareBeanFactory() 给工厂塞基础组件(如 EnvironmentSystemProperties 等)、注册内置 BeanPostProcessor

  5. postProcessBeanFactory() 是模板方法,留给 AbstractApplicationContext 的子类做额外处理。

  6. invokeBeanFactoryPostProcessors() → 修改/新增 BeanDefinition

  7. registerBeanPostProcessors() → 把实例阶段的增强器排好序。

  8. finishBeanFactoryInitialization() → 真正实例化非懒加载单例。

  9. finishRefresh() → 发布 ContextRefreshedEvent 等收尾动作。

想象你在租一间毛坯办公室:prepareRefresh() 像是验收水电、检查网络是否通。obtainFreshBeanFactory() 把空房打扫干净准备装修。prepareBeanFactory() 先把公共设施(饮水机、打印机)搬进来。postProcessBeanFactory() 如果你有特殊需求(比如隔出会议室),可以在这一步动手。接着两拨装修队依次进场:第一拨改图纸(BeanFactoryPostProcessor),第二拨负责软装(BeanPostProcessor)。finishBeanFactoryInitialization() 就是把桌椅板凳全部装配好。最后 finishRefresh() 剪彩开业,通知大家可以正常办公了。

三、BeanDefinition 从哪来?

3.1 XML 解析

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("beans.xml");

内部委托 BeanDefinitionParserDelegate,把 <bean><context:component-scan> 等标签转成 BeanDefinition

3.2 注解扫描

AnnotationConfigApplicationContext 启动后会触发 ConfigurationClassPostProcessor,扫描 @Configuration/@Component,同样得到 BeanDefinition
结论:资源形式千差万别,落地结果都在 BeanDefinitionRegistry 的那张 Map 里。

四、元数据阶段的扩展:BeanFactoryPostProcessor

调用栈:refresh()invokeBeanFactoryPostProcessors()PostProcessorRegistrationDelegate
执行顺序按接口排序:PriorityOrdered → Ordered → 普通

典型场景:

  • MyBatis 的 MapperScannerConfigurer

  • Spring Boot 的 ConfigurationClassPostProcessor(解析 @EnableAutoConfiguration

五、实例阶段的扩展:BeanPostProcessor

注册发生在 registerBeanPostProcessors(),之后每个 Bean 创建都会走一遍列表里的 Processor。

常见实现:

  • AutowiredAnnotationBeanPostProcessor:处理 @Autowired@Value

  • AopProxyCreator:生成 AOP 代理

  • ApplicationContextAwareProcessor:注入各种 xxxAware

六、doCreateBean 内幕

  1. A:某些 InstantiationAwareBeanPostProcessor(如 AOP)可在真正 new 对象前直接返回代理,跳过后续流程。

  2. B createBeanInstance:挑构造器、反射或 CGLIB 生成实例。

  3. C populateBean:完成依赖注入、@Autowired、字段填充。

  4. D initializeBean:执行各种回调:Aware@PostConstructBeanPostProcessor 的 before/after。

  5. E registerDisposableBeanIfNecessary:如是单例且实现了 DisposableBean,注册销毁回调。

我们用做菜来举例,A 厨子有时会发现食材已经是现成半成品(代理对象),就不用再切洗。B 选刀具 + 切配 = 创建实例。C 加盐、味精、下配料 = 属性注入。D 开火翻炒到收汁 = 初始化阶段。E 把锅盖好、记住关火步骤 = 注册销毁回调。最终一道成品菜端上桌(返回 Bean)。

源码(精简):

protected Object doCreateBean(String name, RootBeanDefinition mbd, Object[] args) {
    BeanWrapper bw = createBeanInstance(name, mbd, args);  // 构造
    populateBean(name, mbd, bw);                           // 属性注入
    return initializeBean(name, bw.getWrappedInstance(), mbd); // 回调 + BPP
}

七、生命周期回调速查

  1. 构造函数 / 工厂方法

  2. 依赖注入(@Autowired

  3. BeanNameAware / BeanFactoryAware / EnvironmentAware …

  4. BeanPostProcessor#postProcessBeforeInitialization

  5. InitializingBean#afterPropertiesSet@PostConstruct

  6. BeanPostProcessor#postProcessAfterInitialization

  7. 容器关闭:DisposableBean#destroy@PreDestroyDestructionAwareBeanPostProcessor

八、三级缓存与循环依赖

  • singletonFactories 保存的是 ObjectFactory,调用它可获得「只执行构造、不跑初始化」的早期对象。

  • earlySingletonObjects 放已经拿出来用过的早期对象。

  • singletonObjects 只有完成全部生命周期的 Bean 才能晋级。

  • 当 Bean A 构造完,但初始化前依赖了 Bean B,容器会把 A 的 ObjectFactory 暂存到三级缓存,让 B 可以先拿到「半成品 A」,避免死循环。

把三级缓存想成奶茶店的 3 个货架:三级货架 是奶茶原液,你自己加冰加料才喝得下。二级货架 是加好冰却没封口的杯子,员工可以先尝一口。一级货架 才是贴好封膜、可以递给顾客的成品。有顾客点了「连锁奶茶 + 甜品组合」导致两边互相依赖时,店员就会把「原液」先放到三级货架,另一边可以先用,等两杯都弄好再一起封口。

九、依赖解析小细节

调用链:resolveDependency()doResolveDependency()
关键点:

  • 同类型候选 Bean 名先拿出来

  • 如果参数带 @Qualifier,精确匹配

  • 对集合 / 数组参数按顺序注入

  • Optional<T>ObjectProvider<T> 也是这里处理

十、Spring Boot 与自动装配

Boot 在 SpringApplication#refreshContext() 里依旧调用 refresh(),只是多注册了若干 BeanFactoryPostProcessor

  1. ConfigurationClassPostProcessor:识别 @EnableAutoConfiguration

  2. AutoConfigurationImportSelector:从 spring.factories 里挑符合条件的配置类

  3. ConditionEvaluationReport:筛掉不满足 @Conditional 的 BeanDefinition

一句话:Boot 只是「写了很多 PostProcessor」,底层流程没变。

十一、自定义扩展示例

11.1 BeanFactoryPostProcessor 给 Mapper 加表名前缀

@Component
public class TablePrefixProcessor implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
        for (String name : bf.getBeanDefinitionNames()) {
            if (name.endsWith("Mapper")) {
                BeanDefinition bd = bf.getBeanDefinition(name);
                bd.getConstructorArgumentValues()
                  .addIndexedArgumentValue(0, "demo_" + name);
            }
        }
    }
}

11.2 BeanPostProcessor 统计初始化耗时

@Component
public class TimerPostProcessor implements BeanPostProcessor {
    private final Map<String, Long> ts = new ConcurrentHashMap<>();

    public Object postProcessBeforeInitialization(Object bean, String name) {
        ts.put(name, System.nanoTime());
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String name) {
        long cost = System.nanoTime() - ts.remove(name);
        System.out.printf("%s 初始化耗时 %d μs%n", name, cost / 1000);
        return bean;
    }
}

十二、知识卡片

场景

关键接口 / 类

元数据扩展

BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor

实例扩展

BeanPostProcessor, InstantiationAwareBeanPostProcessor

生命周期

InitializingBean, DisposableBean, *Aware, @PostConstruct

缓存

singletonObjects, earlySingletonObjects, singletonFactories

Boot 自动装配

ConfigurationClassPostProcessor, AutoConfigurationImportSelector

当你把 AbstractAutowireCapableBeanFactory#doCreateBean() 的调用栈捋顺,再配合两类 PostProcessor 的时机点,Spring 的「黑魔法」基本就拆完了。此后不管是写自定义 Starter,还是排查循环依赖、Bean 覆盖等疑难杂症,都能快速定位到源码里对应的阶段。

愿这份笔记能帮你把 IoC 容器的运行机理牢牢刻进肌肉记忆。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。