框架源码专题-Spring-2、IOC
1. 容器 API
- BeanDefinitionRegistry: 定义对 BeanDefinition 的各种增删改查操作
- DefaultSingletonBeanRegistry: Bean 仓库
- AutowireCapableBeanFactory: 提供创建 Bean, 自动注入, 初始化已经应用 Bean 的后处理器
- ConfigurableListableBeanFactory: BeanFactory 配置清单, 指定忽略类型及接口等
- DefaultListableBeanFactory: Bean 工厂
2. 工作原理
2.1. 工作流程
- IOC 是什么
- Bean 的声明方式
- IOC 的工作流程
IOC 的全称是 Inversion Of Control, 也就是控制反转,它的核心思想是把对象的管理权限交给容器。应用程序如果需要使用到某个对象实例,直接从 IOC 容器中去获取就行,这样设计的好处是降低了程序里面对象与对象之间的耦合性。使得程序的整个体系结构变得更加灵活。
2.1.1. Bean 定义注册
Spring 里面很多方式去定义 Bean,(如图)比如 XML 里面的 <bean>
标签、@Service、 @Component、@Repository、@Configuration 配置类中的@Bean 注解等等。 Spring 在启动的时候,会去解析这些 Bean 然后保存到 IOC 容器里面。
2.1.2. IOC 容器的初始化
这个阶段主要是根据程序中定义的 XML 或者注解等 Bean 的声明方式 (如图)通过解析和加载后生成 BeanDefinition,然后把 BeanDefinition 注册到 IOC 容器
通过注解或者 xml 声明的 bean 都会解析得到一个 BeanDefinition 实体,实体中包含 这个 bean 中定义的基本属性。 最后把这个 BeanDefinition 保存到一个 Map 集合里面,从而完成了 IOC 的初始化。 IoC 容器的作用就是对这些注册的 Bean 的定义信息进行处理和维护,它 IoC 容器控制 反转的核心。
2.1.3. Bean 初始化及依赖注入
然后进入到第二个阶段,这个阶段会做两个事情(如图) 1. 通过反射针对没有设置 lazy-init 属性的单例 bean 进行初始化。 2. 完成 Bean 的依赖注入。
2.1.4. Bean 的使用
(如图)通常我们会通过@Autowired 或者 BeanFactory.getBean() 从 IOC 容器中获取指定的 bean 实例。另外,针对设置 lazy-init 属性以及非单例 bean 的实例化,是在每次获取 bean 对象的时候,调用 bean 的初始化方法来完成实例化的,并且 Spring IOC 容器不会去管理这些 Bean
2.2. 生命周期
Spring 加载流程图 (徐庶)
https://www.processon.com/view/link/5f15341b07912906d9ae8642
3. 容器刷新
Spring 容器刷新 12 步详细
https://www.processon.com/diagraming/639548487d9c084a6a3d4021
4. 循环依赖
4.1. 循环依赖的种类⭐️🔴
4.1.1. 属性注入依赖
通过三级缓存解决 ❕
4.1.2. 构造方法依赖
解决方案:Spring-2、IOC
懒加载原理:Spring-9、@Lazy
4.1.3. 多例模式
无解
4.2. 变更历史
大概是在Spring2.0.3加入了循环依赖的解决方案,在 springBean 的生命周期管理之后加入的新代码。其中涉及到了代理类生成时机的冲突问题
Spring 正常的代理应该是发生在 bean 初始化后,由 AbstractAutoProxyCreator.postProcessAfterInitialization 处理。而循环依赖要求 bean 在填充属性前就提前生成代理,否则就会出现最终版本不一致错误。所以 Spring 在代码中开了个口子,循环依赖发生时,提前代理,没有循环依赖,代理方式不变,依然是初始化以后代理。
其实也可以不分提前代理和正常代理,全部的 Bean 都直接提前生成代理,但那样的话 AbstractAutoProxyCreator.postProcessAfterInitialization 直接废了,相当于把原本的逻辑推翻重写,如此只是为了解决循环依赖的话就会变得得不偿失,没有完全必要的情况下对核心代码大改甚至推翻重写是一种大忌。
而三级缓存的实现提供了提前生成代理的口子,而不是直接生成代理,只有发生循环依赖执行 getObject 才会生成代理,达到上述循环依赖发生时,提前代理,没有循环依赖,代理方式不变,依然是初始化以后代理的目的。
原文链接:https://blog.csdn.net/u012098021/article/details/107352463/
4.2.1. 关闭默认支持循环依赖
SpringBoot2.6 之后关闭了默认支持循环依赖的开关
开启方法: spring.main.allow-circular-references=true
https://juejin.cn/post/7096798740593246222
4.3. 循环依赖流程⭐️🔴
https://www.processon.com/diagraming/639c04ed1efad465c9cd90da
https://www.processon.com/diagraming/639e8c7f1efad465c9cfdd1a
https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c
4.3.1. 各级缓存的加入时机
4.3.1.1. 一级缓存
Map<String, Object> singletonObjects
new ConcurrentHashMap<>(256)
解决完整 Bean 与不完整 Bean 混乱问题
4.3.1.2. 二级缓存
❕ ^kyatt6
Map<String, Object> earlySingletonObjects
new ConcurrentHashMap<>(16)
提供缓存功能,以便从中获取原始或者代理对象,同时能够解决动态代理多次创建问题
只要有循环依赖,不管有没有 AOP,earlySingletonObjects
里都会有数据,比如 ABA,AB 在 3 级缓存中都有数据,B 在后面填充属性 A 时将 A 的三级缓存用掉,生成半成品 A 放入 2 级缓存中,3 级缓存的 A 被删除。而 B 直接从 3 级来到 1 级。
只是二级缓存中可能是原始 Bean,也可能是代理 Bean
4.3.1.3. 三级缓存
Map<String, ObjectFactory<?>> singletonFactories
new HashMap<>(16)
实例对象创建之后,属性填充之前
检测循环依赖条件:单例&允许循环依赖&当前 bean 正在创建中
解决死循环问题
不管有没有循环依赖,三级缓存中都会有数据
4.4. 循环依赖问题汇总
4.4.1. 为什么一级二级是 ConcurrentHashMap
因为先手线程加锁加在查询一级、二级缓存之后,所以一级、二级缓存的查询需要支持并发而不影响性能
https://www.bilibili.com/video/BV1pe4y1h7wK?p=19&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
debug 了 AOP+ 循环依赖
最新 Spring 全家桶 -2021 面试 - 重灾区:Spring/SpringCloud/SpringBoot/SpringMVC,肝完就跳槽
https://www.bilibili.com/video/BV1x64y1x71b?p=56
4.5. AOP 提前代理逻辑
4.5.1. 正常代理
对象初始化之后创建动态代理,这是 SpringBean 的正常生命周期
BeanPostProcessor.postProcessAfterInitialization(),具体实现类是AbstractAutoProxyCreator
4.5.2. 提前代理
4.5.2.1. getEarlyBeanReference
❕ ^qideqv
getEarlyBeanReference 方法就是为了生成提前代理对象而设计的。
如果有代理❕ ,那么在 6. registerBeanPostProcessors() 中会将提供 AOP 功能的 Bean(AnnotationAwareAspectJAutoProxyCreator) 注册完成,然后在后面的 getEarlyBeanReference(beanName, mbd, bean) 方法中就会走 AbstractAutoProxyCreator 的实现,返回 proxy。否则,如果没有 AOP 代理,那么会走 SmartInstantiationAwareBeanPostProcessor 的默认实现,返回正常 bean
4.5.2.2. 细节
防止提前代理后,正常代理再生成一次代理
https://blog.csdn.net/u012098021/article/details/107352463/
代理对象示例
4.5.2.3. 产生的影响⭐️🔴
@Autowired 自动注入的对象有可能会是代理对象,取决于 Bean 是否使用了 AOP 注解
spring 很多功能都是通过 aop 来实现,比如事务,缓存注解,异步、还有一些自定义的 aop 等等,而 aop 是通过动态代理来实现的,spring 主要用到的动态代理有 jdk 的动态代理和 cglib。
- Spring 在没有使用 aop 的时候自动注入的时候是原始类型对象
- 在发生 aop 的时候,若代理对象有实现接口,则默认会使用 jdk 动态代理
- 在发生 aop 的时候,若代理对象没有实现接口,则默认会使用 cglib 动态代理
- jdk 动态代理必须有实现接口
- 可以强制使用 cglib 来做 spring 动态代理
示例代码: https://xie.infoq.cn/article/29ce37bd3f6f4c5b405549c06
4.5.3. 二级缓存的作用
如果调用 getEarlyBeanReference 生成动态代理对象立即返回,不使用二级缓存来缓存起来,就会出现多次生成同一个对象的动态代理的情况,比如 B、C 都与 A 循环依赖,那么不用缓存判断,直接返回的话,就会生成 2 次 A 的代理对象 ❕
如果提前代理,后面需要从二级缓存中取出返回出去。
4.5.4. 测试程序
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/spring-framework/springsource-test/src/main/java/com/atguigu/spring/config/MainConfig.java]]
4.6. @Async 导致循环依赖报错⭐️🔴
4.6.1. 原因分析
❕
@EnableAsync 开启时它会向容器内注入 AsyncAnnotationBeanPostProcessor
,它是一个 BeanPostProcessor,实现了postProcessAfterInitialization方法。此处我们看代码,创建代理的动作在抽象父类 AbstractAdvisingBeanPostProcessor 上:
1 |
|
根本原理是只要能被切面 AsyncAnnotationAdvisor
切入(即只需要类/方法有标注 @Async
注解即可)的 Bean,在初始化完成之后,经过 BeanPostProcessor.postProcessAfterInitialization()
最终都会生成一个代理对象(若已经是代理对象里,只需要加入该切面即可了)赋值给上面的 exposedObject
作为返回最终 add 进 Spring 容器内。
4.6.2. 流程分析
1 |
|
- A->B: context.getBean(A) 开始创建 A,A 实例化完成后给 A 的依赖属性 b 开始赋值
- B->A: context.getBean(B) 开始创建 B,B 实例化完成后给 B 的依赖属性 a 开始赋值
重点:此时因为 A 支持循环依赖,而且没有动态代理所以会执行 A 的 getEarlyBeanReference 方法得到它的早期引用。而执行 getEarlyBeanReference() 的时候因为@Async 根本还没执行,所以最终返回的仍旧是原始对象的地址
- B 完成初始化、完成属性的赋值,此时属性 field 持有的是 Bean A原始类型的引用
- 继续 A 的创建流程:完成了 A 的属性的赋值(此时已持有 B 的实例的引用),继续执行初始化方法 initializeBean(…),在此处会解析@Aysnc 注解,从而生成一个代理对象,所以最终 exposedObject 是一个代理对象(而非原始对象)最终加入到容器里
^vlhwms
上演尴尬的场面:B 引用的属性 A 是个原始对象,而此处准备 return 的实例 A 竟然是个代理对象,也就是说 B 引用的并非是最终对象(不是最终放进容器里的对象)
执行自检程序:由于 allowRawInjectionDespiteWrapping 默认值是 false,表示不允许上面不一致的情况发生,so 最终就抛错了
4.6.3. 解决方案
通过上面分析,知道了问题的根本原因,现总结出解决上述新问题的解决方案,可分为下面三种方案:
- 把 allowRawInjectionDespiteWrapping 设置为 true
- 使用@Lazy 或者@ComponentScan(lazyInit = true) 解决
- 不要让@Async 的 Bean 参与循环依赖
@Lazy 原理: Spring-9、@Lazy
4.6.4. 其他
本质上:@Async 的后置处理器,没有在 getEarlyRefrence 时,创建被@Async 修饰代理对象,而是在 initlizeBean 方法中创建了代理对象。作为对比,被@Aspect 修饰的 Aop 对象会在 getEarlyRefrence 阶段,提前创建代理对象,顾 aop 时不存在该问题
https://blog.csdn.net/f641385712/article/details/92797058
[[(210条消息) 使用@Async异步注解导致该Bean在循环依赖时启动报BeanCurrentlyInCreationException异常的根本原因分析,以及提供解决方案【享学Spring】_YourBatman的博客-CSDN博客]]
4.7. BFPP 导致自动配置失败
Bean 工厂后置处理器在容器刷新的第 5 步,远在第 11 步中的 9 大 Bean 后置处理器之上
https://www.bilibili.com/video/BV15b4y117RJ?p=169&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
- BFPP 尽量使用静态工厂方法
- 依赖注入使用局部变量
4.8. 多例的循环依赖
Spring 有没有解决多例 Bean 的循环依赖?
a. 多例不会使用缓存进行存储(多例 Bean 每次使用都需要重新创建)
b. 不缓存早期对象就无法解决循环
关键: 一定要有一个缓存保存它的早期对象作为死循环的出口
4.9. 构造方法的循环依赖
❕ ^7cjsyr
4.9.1. @Lazy
使用@Lazy 注解,会创建 cglib 动态代理
- 🚩 - todo:https://zhuanlan.zhihu.com/p/562691467 - 🏡 2023-01-29 15:53 Spring-9、@Lazy
https://www.yuanjava.cn/posts/spring-constructor-circular-dependencies/
https://blog.csdn.net/WX10301075WX/article/details/123904543
循环依赖
4.9.2. ObjectFactory
4.9.3. Provider
4.9.4. @Scope
4.10. 防止获取到不完整的 Bean⭐️🔴
❕ ^d4s7ab
https://www.bilibili.com/video/BV1t44y1C73F?p=29&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
上面这个图对应的版本为 5.0.X,后面版本 5.2.X 之后,Spring 只锁在三级缓存前面,也正因此一级、二级缓存用的是 ConcurrentHashMap ❕
- 两个地方加了同一把锁,即锁住了一级缓存
- 先在创建 bean 的过程中加了锁,具体位置在
doGetBean
中的第二个getSingleton
,即入参为beanName
和ObjectFactory<?>
- 于此同时,相当于也在 getSingleton(beanName, boolean) 中加了锁,因为是同一把对象锁 ❕
只能说太精妙了,2 个锁都是在单例仓库 DefaultSingletonBeanRegistry
中,getSingleton
的 2 个重载的方法里:
5. 扩展点
^ywsrjj
Spring-7、扩展点6. 设计模式
7. 实战经验
手动集成 Bean 到容器中
https://blog.csdn.net/yuan882696yan/article/details/99082252
1 |
|
8. 参考与感谢
8.1. 图灵徐庶
https://www.bilibili.com/video/BV1t44y1C73F/?p=24&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204 75p
[[Spring全家桶面试题—图灵徐庶.pdf]]
https://www.processon.com/view/link/5f5075c763768959e2d109df#map
https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c
Bean 创建过程: https://www.processon.com/view/link/5f15341b07912906d9ae8642
8.2. 尚硅谷雷丰阳
8.2.1. 配套视频
https://www.bilibili.com/video/BV1gW411W7wy/?spm_id_from=333.337.search-card.all.click
8.2.2. 配套代码
spring 注解驱动开发 _spring 源码版 - 雷丰阳 - 尚硅谷
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/Spring注解驱动开发-尚硅谷-雷丰阳/spring-annotation/src/main/resources/SpringSource.txt]]
8.2.3. 配套笔记
https://liayun.blog.csdn.net/article/details/111413608
8.3. 马士兵 -lianpengju
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/lianpengju/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java]] ❕
https://www.bilibili.com/video/BV11B4y1175J?p=6&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
8.4. 黑马
8.4.1. 视频
8.4.2. 资料
1 |
|
8.5. 测试 Demo
013-DemoCode/spring-framework/spring
[[AnnotationMainTest.java]]