Spring 实例化 bean 过程

这里以 AnnotationConfigApplicationContext 为例,有三种方法来注册 bean,分别是:

ini 复制代码
// 这种方法不需要显示调用 refersh() 方法
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyComponent.class);
​
// 这种方法需要显示调用 refersh() 方法
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyComponent.class);
context.refresh();
​
// 这种方法需要显示调用 refersh() 方法,它会扫描 classpath 下所有 basePackage 下所有的类,发现标注了 @Component 的类才注册到 BeanFactory 中
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.simple.package");
context.refresh();

refersh() 方法是 Spring 中非常重要的方法,它负责 ApplicationContext 初始化的所有逻辑,在 Spring 中,有种顶级容器,一个是 ApplicationContext,一个 是 BeanFactory,ApplicationContext 是 BeanFactory 的子接口,BeanFactory 是一个纯粹的 Bean 容器,它提供了注册实例化的逻辑,所有的 ApplicationContext 实现类如 AnnotationConfigApplicationContext 都会有一个 BeanFactory 类型的成员变量,在日常开发中,我们都是通过 ApplicationContext 来操作 BeanFactory 的。

Sping 提供了多种扩展点,一种最常用的就是 BeanPostProcessor 和 BeanFactoryPostProcessor,注册到 Spring 中的 bean 如果实现了这两个接口,那么会在实例化其他的 bean 之前实例化,BeanFactoryPostProcessor 用于在 ApplicationContext 启动的时候修改 BeanDefinition,而 BeanPostProcessor 用于在实例化 bean 的前后做一些额外的处理。

Spring 拿到所有注册的 bean 之后是通过一个 for 循环来实例化的(在这之前 BeanPostProcessor 和 BeanFactoryPostProcessor 实现类都已经实例化完成了):

scss 复制代码
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        if (isFactoryBean(beanName)) {
            Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
            if (bean instanceof FactoryBean) {
                FactoryBean<?> factory = (FactoryBean<?>) bean;
                boolean isEagerInit;
                if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                    isEagerInit = AccessController.doPrivileged(
                        (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                        getAccessControlContext());
                }
                else {
                    isEagerInit = (factory instanceof SmartFactoryBean &&
                                   ((SmartFactoryBean<?>) factory).isEagerInit());
                }
                if (isEagerInit) {
                    getBean(beanName);
                }
            }
        }
        else {
            getBean(beanName);
        }
    }
}

没有循环依赖的 singleton bean

我们 debug 的 bean 是通过包扫描注册的。整个方法调用顺序如下:

如果我们注册的 bean 是一个 singleton,且没有循环依赖,没有代理类的情况下,整个创建过程中并没有用到 2 级缓存,只用到了 1,3 级缓存。

有循环依赖的 singleton bean

如果一个 bean A 有通过 @Autowire 注解属性的循环依赖,即如下类结构:

less 复制代码
@Component
public class A {
    @Autowire
    private B b;
}
​
@Component
public class B {
    @Autowire
    private A a;
}

那么在实例化 A 对象,在上面方法调用顺序图中的 populateBean(beanName, mbd, instanceWrapper) 方法中完成属性的注入,整个依赖注入的调用关系图如下:

在实例化 B 步骤之后就和实例化 singleton bean 的步骤是一样的了,在实例化 B 的过程中,又会在 popluateBean() 方法中依赖注入 A,进入上面的步骤,然后到 beanFactory.getBean(beanName) 方法中获取实例 A,此时实例 A 已经实例化,但是还没有初始化,即它在 3 级缓存中,所以在调用 getSingleton(beanName) 方法的时候,会从三级缓存中获取到实例,然后从三级缓存中删除,并添加到二级缓存中。

三级缓存

三级缓存是 Spring 存储实例化 bean 的地方,之所以有三级缓存,是因为要解决循环依赖的问题,其实二级缓存就可以解决循环依赖,但是引入三级缓存的原因在网上都说是要延迟创建代理对象,但是都没说清楚,后续再写一篇关于三级缓存的。

相关推荐
无限大620 分钟前
计算机十万个为什么--数据库索引
后端
学历真的很重要42 分钟前
VsCode+Roo Code+Gemini 2.5 Pro+Gemini Balance AI辅助编程环境搭建(理论上通过多个Api Key负载均衡达到无限免费Gemini 2.5 Pro)
前端·人工智能·vscode·后端·语言模型·负载均衡·ai编程
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue心理健康管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
狂炫冰美式3 小时前
不谈技术,搞点文化 🧀 —— 从复活一句明代残诗破局产品迭代
前端·人工智能·后端
databook4 小时前
数据会说谎?三大推断方法帮你“审问”数据真相
后端·python·数据分析
代码栈上的思考5 小时前
深入解析Spring IoC核心与关键注解
java·后端·spring
expect7g5 小时前
Paimon源码解读 -- Compaction-2.KeyValueFileWriterFactory
大数据·后端·flink
小灰灰搞电子6 小时前
Rust 动态分发(dyn Trait)详解
开发语言·后端·rust
码事漫谈6 小时前
深入剖析进程、线程与虚拟内存
后端
码事漫谈6 小时前
MFC核心架构深度解析
后端