03.spring实现创建单例原理
引例
A 依赖 B,B 依赖 A 的循环依赖,通过 XML 配置依赖(A 的 setB 注入 B,B 的 setA 注入 A),初始化的时候,四大皆空
singletonObjects(一级);
earlySingletonObjects(二级);// key:beanName value:未完成依赖注入的bean
beansingletonFactories(三级); //key:beanName value:可以返回bean实例的函数,该实例未完成依赖注入
singletonCurrentlyInCreation(正在创建中标记) //bean 处于二、三级缓存中,均为创建中
代码调用链路:getBean ("a") → DefaultSingletonBeanRegistry#getSingleton ("a")
实现三级缓存原理
- getSingleton ("a") 首次查询缓存,三缓存都未命中(即缓存未命中),则进入创建 A 的实例中,并注册三级缓存,进入 AbstractAutowireCapableBeanFactory#doCreateBean 方法,执行 A 的实例化和加入三级缓存,在该方法内会使用 beforeSingletonCreation (beanName) 标记 A 为创建中,此时:
singletonFactories:{"a" : () -> getEarlyBeanReference("a",mbd,A@ram)} // 即 getEarlyBeanReference("a",mbd,A@ram),该函数返回早期的A实例 singletonCurrentlyInCreation:{"a"} - A 实例化后注入 B,触发 B 的实例化,即 A 实例化后进入属性注入阶段(populateBean),解析到依赖 B,调用 getBean ("b") [和getBean("a")一样],又来到 B 的属性注入阶段,调用 getBean ("a"),在三级缓存中查询到了 A,调用 getObject () [即 getEarlyBeanReference("a",mbd,A@ram)], 生成 A 的早期实例,( A 不需要代理,直接返回 A@ram,若 A 需要代理,则返回 A@proxy),返出
返回前,将 A 从三级缓存存至二级缓存(即 A@own 从三级缓存移到二级缓存后),并将
三级缓存中的 A@ram 移除。 - B 完成注入 A 后,即完成 B 的属性注入和初始化,调用 addSingleton ("b",b@ram) 存入一级缓存,同时移除二、三级缓存中 “b” 且移除 B 的正在创建标记。
AfterSingletonCreation("b") - A 继续注入 B,从一级缓存中获取 B@own,之后与步骤 3 一样
三级缓存完结
课后小问题
1.在查找 a 的自身时候,有没有可能存在命中二、三级缓存?
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReference && !isSingletonCurrentlyInCreation (beanName)
if (earlySingletonExposure) {
// 3. 条件满足:注册三级缓存(核心操作)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
- ① 无循环依赖时,查找 A 只会命中一级缓存 (创建后)。若第一次未命中,直接触发 A 的创建,直接加入一级缓存。
- ② 有循环依赖时,B 依赖于 A, 步骤 2 访问 A 命中三级缓存。若还有 C 依赖于 A (即 A 同时 B、C 循环依赖) 此时访问 A 命中二级缓存。
核心总结:在 Spring 的设计中,当从二级或三级缓存中找到 Bean 实例时,会直接返回,这是安全的,因为:
1.实例引用唯一,后续初始化会完善状态,依赖方持有的引用本身指向完整的对象。
2.容器启动过程中,依赖方不会提前使用初始化后的实例,最终所有的 Bean 都会在容器初始化后进入可用状态。
关于标记:
1.在实例化前就要标记,标记是为了防止多个相同的 beanName 重复创建。
2.当有一个 beanName (如 A) 被标记后,再去标记相同的 beanName 便会发生异常。
3.只有三级缓存均未命中时,才会触发 doCreateBean 进行标记,实例化,加入三级缓存。

浙公网安备 33010602011771号