mini-spring 学习笔记—高级篇
最近在学习 mini-spring 项目,记录笔记以总结心得
IoC篇:mini-spring 学习笔记—IoC
AOP篇:mini-spring 学习笔记—AOP
解决循环依赖问题(一):没有代理对象
DefaultSingletonBeanRegistry
增加 Map
类型的属性 earlySingletonObjects
,用作二级缓存,存放提前实例化(设置属性之前)的 bean
protected Map<String, Object> earlySingletonObjects = new HashMap<>();
getSingleton
方法增加检查二级缓存的代码
// getSingleton 方法
Object bean = singletonObjects.get(beanName);
if (bean == null) {
bean = earlySingletonObjects.get(beanName);
}
return bean;
AbstractAutowireCapableBeanFactory
在 doCreateBean
方法中提前将 bean 放入二级缓存中
// doCreateBean
earlySingletonObjects.put(beanName, bean);
解决循环依赖问题(二):有代理对象
本章是 Spring 解决循环问题的核心,为了方便阅读,首先介绍一下 Spring 三级缓存各自的作用
singletonObjects
:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用earlySingletonObjects
:提前曝光的单例对象的 cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖singletonFactories
:单例对象工厂的 cache,存放 bean 工厂对象,用于解决循环依赖
DefaultSingletonBeanRegistry
增加三级缓存 DefaultSingletonBeanRegistry
,用于存放单例 bean 的工厂
private Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
从代码可以知道,String
类型代表 bean 的名字,也就是说为每一种 bean 存放一个 bean 工厂
在 getSingleton
方法中增加查询三级缓存的代码
// getSingleton 方法
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject == null) {
// 查询二级缓存
singletonObject = earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 查询三级缓存
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//从三级缓存放进二级缓存
earlySingletonObjects.put(beanName, singletonObject);
singletonFactories.remove(beanName);
}
}
}
return singletonObject;
singletonFactories.get(beanName)
通过 bean 名称获取制造该 bean 的工厂,然后让工厂制造该 bean 并返回
AbstractAutowireCapableBeanFactory
getObject
方法的具体实现在该类的 doCreateBean
方法中
// doCreateBean 方法
// 为解决循环依赖问题,将实例化后的bean放进缓存中提前暴露
if (beanDefinition.isSingleton()) {
Object finalBean = bean;
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, beanDefinition, finalBean);
}
});
}
getEarlyBeanReference
方法用于返回 bean 的代理对象
protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) {
Object exposedObject = bean;
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
// 获得代理对象
// 此时代理对象在 earlyProxyReferences 中
exposedObject = ((InstantiationAwareBeanPostProcessor) bp).getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return exposedObject;
}
}
}
return exposedObject;
}
比如在本章的测试中,传入了 bean a
,那么在实例化 a
的过程中,这个方法会产生如下的运行结果
而 exposedObject
的具体字段内容如下
可以看到 exposedObject
中 target
成员的自我描述为 A@1224
和传入的 bean
变量自我描述相同,所以 exposedObject
是 a
的代理对象
DefaultAdvisorAutoProxyCreator
增加 Set
类型的成员变量 earlyProxyReferences
,用于保存提前实例化且未被代理包裹的 bean
private Set<Object> earlyProxyReferences = new HashSet<>();
关于原文对于 getBean
方法的描述
原文在这一章对于 getBean
方法的描述有歧义
getBean()
时依次检查一级缓存singletonObjects
、二级缓存earlySingletonObjects
和三级缓存singletonFactories
中是否包含该bean。如果三级缓存中包含该 bean,则挪至二级缓存中,然后直接返回该 bean。见AbstractBeanFactory#getBean
方法第1行。
定位到 AbstractBeanFactory
类的 getBean
方法的第一行
Object sharedInstance = getSingleton(name);
调用的是 getSingleton
方法,而在上文的分析中, getSingleton
方法对第三级缓存的操作是让 singletonObject
的工厂来制造一个 singletonObject
以获取 singletonObject
。也就是说,原文“如果三级缓存中包含该 bean,则挪至二级缓存中,然后直接返回该 bean”中“三级缓存包含该 bean” 指的是三级缓存能够制造该 bean。
本章小结
详细步骤
此时梳理一下循环依赖情况下的 bean 创建过程
在 AbstractApplicationContext
类的 finishBeanFactoryInitialization
方法打断点
preInstantiateSingletons
方法对 bean 定义容器中的每个定义调用 getBean
方法,此时容器中有六个 bean 定义
首先获取 a
进入 getSingleton
方法,查询完三级缓存之后,发现没有制造 a
的工厂,该方法返回 null
回到 getBean
方法中,调用 createBean
方法,之后调用 doCreateBean
方法
在 doCreateBean
方法中获取实例化后的 a
,判断如果是单例 bean,则进入 addSingletonFactory
方法,在三级缓存中添加该 bean 的制造工厂,添加之后三级缓存 size = 1
之后返回 doCreateBean
方法中,进行填充属性
发现 a
依赖于 b
,对 b
进行 getBean
操作
同样进入 getSingleton
方法
查询完三级缓存,发现没有 b
的工厂
为 b
调用 createBean
同样进入到 doCreateBean
方法,再调用 addsingletonFactory
方法为 b
添加制造工厂,此时三级缓存 size = 2
为 b
填充属性,发现依赖于 a
,尝试对 a
进行 getBean
操作
进入 getSingleton
方法
此时发现三级缓存中有制造 a
的工厂,便让该工厂使用 getObject
方法制造 a
进入 getObject
方法,调用 getEarlyBeanReference
调用 getEarlyBeanReference
方法,获取 a
的代理对象
在 getEarlyBeanReference
方法中,先往 earlyProxyReferences
集合中添加 bean 名称
之后进入 wrapIfNecessary
方法,为 a
设置代理
回到 getEarlyBeanReference
方法,此时的 exposedObject
对象是 a
的 CGLIB 代理
回到 getSingleton
方法,此时 singletonObject
对象是 a
的代理对象,并把 a
的工厂从三级缓存中删除,然后把 a
的代理对象 singletonObject
放入二级缓存中
将 a
添加到二级缓存,并把 a
的工厂从三级缓存中删除
为 b
设置属性 a
为 b
获取代理对象,调用 getSingleton
方法
此时三级缓存中已经有了 b
的工厂
依然调用 getObject
方法,进入 getEarlyBeanReference
方法,但是返回的是非代理对象
对三级缓存和二级缓存进行操作后
回到 doCreateBean
方法,对 b
调用 addSingleton
方法,将初始化好的 b
放入一级缓存,从二级缓存中删除 b
回到 b
的 getBean
方法,再返回到 applyPropertyValues
,给 a
设置 b
属性
再次回到 doCreateBean
方法中,获取 a
的代理对象
再次进入 getSingleton
方法,此时已经能够在二级缓存中找到 a
进入 addSingleton
方法,此时 exposedObject
已经是代理对象了
此时所有的 bean 都在一级缓存中了
太长不看总结版
总的来说,当我们要实例化 a
的时候,会经历以下步骤:
- 实例化
a
,将a
工厂放入三级缓存中 - 为
a
设置属性时发现依赖于b
- 实例化
b
,将b
工厂放入三级缓存中 - 为
b
设置属性时发现依赖于a
,且三级缓存中有a
工厂 - 从
a
工厂中获取a
的代理对象,并将其放入二级缓存,删除三级缓存中的a
工厂,并返回a
- 设置
b
的属性 - 获取
b
的代理对象,将b
的代理对象放入二级缓存,删除三级缓存中的b
工厂 - 将
b
的代理对象放入一级缓存,从二级缓存中删除b
的代理对象,并返回b
- 通过反射为最开始的
a
设置属性b
,此时二级缓存中的a
也具有了相同的b
属性(因为缓存中的a
和最初的a
是同一个对象) - 从二级缓存中能找到
a
的代理对象并返回 - 将
a
的代理对象放入一级缓存,并从二级缓存中删除
此时会有一个疑问:为什么缓存中的 a
和最初的 a
是同一个对象?因为在 b
调用 getObject
函数的时候,getObject
中 getEarlyBeanReference
函数的 beanDefinition
参数值和 finalBean
参数值在添加进三级缓存的时候已经固定好了,所以 finalBean
指向的其实是第一个 a
支持懒加载和多切面增强
这一章下作者合并了多个提交,但主要改动在“增加懒加载,重写了 AOP”提交记录
懒加载
BeanDefinition
增加 Boolean
类型的成员变量 lazyInit
,用于标记该 bean 是否懒加载
private boolean lazyInit=false;
DefaultListableBeanFactory
preInstantiateSingletons
方法添加了关于 bean 是否是懒加载的判断
public void preInstantiateSingletons() throws BeansException {
beanDefinitionMap.forEach((beanName, beanDefinition) -> {
if(beanDefinition.isSingleton()&&!beanDefinition.isLazyInit()){
getBean(beanName);
}
});
}
多个切面匹配同一个方法
ProxyFactory
这一节讲的挺清楚的,就不再赘述
基于JDK动态代理
这一节讲的挺清楚的,就不再赘述
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
2022-12-09 2022秋 矩阵论考试准备