Spring bean装配流程和三级缓存
马士兵
源码方法论
- 不要忽略源码中的注释
- 先梳理脉络,再深入细节
- 大胆猜测、小心求证
- 见名知意
- hold on
- 对源码有兴趣的都是变态
- 为了钱!
Spring
IoC
Spring容器帮助管理对象,不需要程序员去new。
<!-- applicationContext.xml -->
<bean id=? class=?>
<property name=? value=?/>
<property name=? ref=?/>
<bean/>
<bean id=? class=?>
<constructor-arg name=? value=?/>
<constructor-arg name=? ref=?/>
<bean/>
- 加载配置文件
- 解析属性值
- 创建实例对象
- 使用
- 销毁
Spring容器中使用 concurrentHashMap数据结构存放 bean。k-v结构存储。
Spring容器设计
常规创建对象的方式:
- new
- 反射
- 设计模式
Spring中 bean的作用域默认是单例的,其他还有 request的,session的。
// 常规反射创建对象
Class clazz = Class.forName();
Class clazz = 类名.class;
Class clazz = 对象名.getClass();
Constructor con = clazz.getDeclareConstructor();
Object obj = con.newInstance();
1)针对不同的配置方式 xml, 注释, json等有一个抽象定义规范接口 BeanDefinitonReader 读取Spring中 BeanDefinition接口中bean的定义信息。
2)
ApplicationContext ac = new ClassPathXmlApplicationContext();
Object o = ac.getBean("xxx");
实例化:在堆中开辟一块空间,属性都是默认值
初始化:给属性赋值、填充属性、执行初始化方法
<bean id="" class="" init-mmethod="" destroy-method="" />
完整对象
DefaultListableBeanFactory
从 BeanDefinition到 BeanFactory有多个 BeanFactoryPostProcessor实现过程增强(修改 bean的定义信息)。如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}" />
</bean>
其中,在 <property/>
标签中,将 value值从 db.properties
中提取出来并赋值给username
的过程便是一个过程增强的过程。具体调用的是 PlaceholderConfigurerSupport
,它是 BeanFactoryPostProcessor
的子类。
可以自定义类实现 BeanFactoryPostProcessor
接口,获取 BeanDefinition
,并对其进行修改。
应用场景:针对在 xml配置文件中定义了诸多的类并且发现部分同名属性都定义错了,就可以在容器读取配置文件后,通过 BeanFactoryPostProcessor
进行截断修改。
针对注解开发的 @ComponentScan
同样也需要有相应的增强器,ConfigurationClassPostProcessor
,用于解析被注解的类。
bean的生命周期
1)实例化
2)填充属性(populateBean)
3)执行诸多 aware接口需要实现对方法
aware接口存在的意义是:方便通过 spring中的 bean对象来获取对应容器中的相关属性。
4)BeanPostProcessor(before, init-method, after)
CglibAopProxy
JdkDynamicAopProxy
5)完整的对象
6)销毁流程
观察者模式
在 Spring生命周期的不同阶段做不同的处理工作。
- 监听器
- 监听事件
- 多播器
Debug
1)ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
2)Refresh()
3)BeanFactoryPostProcessor
4)BeanPostProcessor
5)多播器
6)监听器
7)实例化非懒加载的对象
doGetBean();
createBean();
doCreateBean();
createBeanInstance();
instabtiateBean();
getInstantiationStrategy().instantiate();
getDeclaredConstruction();
BeanUtils.instantiateClass();
newInstance();
populateBean();
invokeAwareMethods();
ignoreDependencyInterface(EnvironmentAware.class);
循环依赖
A类中定义了 b属性
B类中定义了 a属性
A调用B,B调用A
三级缓存
DefaultSingletonBeanRegistry
set注入:可以解决
构造方法注入:不能解决
原因:实例化和初始化分开处理(提前暴露对象)
实例化流程
1)先实例化 A对象,只是完成了堆空间开辟,并没有设置属性值,只是生成了半成品
2)初始化 A对象,此时要给 b属性赋值,而 B对象是一个完全独立的对象,所以此时要去 Spring容器中查找 B对象
如果此时已经有了 B对象,就直接赋值
如果此时没有 B对象,就先完成 B对象的实例化
3)实例化 B对象 。。。
4)初始化 B对象,给 B对象中的 a属性赋值 。。。
如果有了 A对象,就赋值
如果没有,就实例化 A对象
解决策略
- 使用半成品
- 放到容器内的一个 map中取用
ObjectFactory是一个函数式接口,仅有一个方法,可以传入 lambda表达式或者匿名内部类,可以通过调用 getObject()方法来执行具体的逻辑
1)创建 a:
getBean -> doGetBean -> getSingleton -> createBean -> doCreateBean
getObject()实际上调用的是 createBean();
instanceWrapper = nreateBeanInstance(beanName, mbd, args);
得到对象 A@1755
2)填充 b:
getEarlyBeanReference(); 判断一级缓存内是否已经包含目标实例
存入三级缓存:k-a, v-() ->getEarlyBeanReference(beanName, mbd, bean);
populateBean()
applyPropertyValus();开始填充属性值,填充 b
getName(); b
getValue(); RuntimeBeanReference@1983
resolveValueIfNecessary();
判断 value类型是否为 RuntimeBeanReference -> resolveReference()
3)创建 b:B@2152
4)填充 b:存入三级缓存:k-b, v-() ->getEarlyBeanReference(beanName, mbd, bean);
populateBean(),给 b对象内的 a属性赋值 RuntimeBeanReference@2375
5)给 b中的 a赋值:在三级缓存中获取,获取 a所对应的 lambda表达式
此时 getObject() -> getEarlyBeanReference() -> 获取 A@1755
存入 二级缓存,删除 三级缓存中的 a对象,此时 a仍旧是半成品
此时 b的实例化和初始化已经完成
6)继续初始化 a:在一级缓存中存入k-b, v-B@2152,清除二级缓存和三级缓存中的 b
resolveValueIfNecessory()
initializeBean(); 之后 a就成为了完全体
在一级缓存中存入 k-a, v-A@1755,清除二级缓存和三级缓存中的 a
7)之后再需要获取时,直接在一级缓存中获取即可
查找顺序:
- 一级缓存:存完全体
- 二级缓存:存半成品
- 三级缓存:存 lambda,来完成代理对象的覆盖过程
总结
三级缓存解决循环依赖问题的关键是什么?
为什么通过提前暴露对象能解决?
因为对象的 实例化 和 初始化 分离
在中间过程中给其他对象赋值的时候,并不是一个完整的对象,而是把半成品对象赋值给了其他对象。
如果只使用一级缓存能不能解决问题?
不能!在整个调用过程中,一、二、三级缓存中存放的是半成品和成品对象,如果只有一级缓存,则成品和半成品都会放到一级缓存中。则有可能获取到半成品对象,而它是无法正常使用的。因此需要把半成品和成品对象的存放空间分割开来。
只使用二级缓存行不行?为什么需要三级缓存?
三级缓存相比二级缓存,多了个 getEarlyBeanReference();
如果,我们能保证所有的 bean对象都不需要此方法,那么只用二级缓存也可以。
getEarlyBeanReference():获取早期暴露的对象引用,解决循环依赖的关键!
AbstractAutoProxy();
createProxy();
使用三级缓存的本质在于解决AOP代理的问题!!
在创建代理对象时,如果某个 bean需要代理对象,那么会不会创建普通的 bean对象?
肯定会!
当调用 getEarlyBeanReference()时,会先把 bean赋值给 exposedBean,如果在之后的一系列判断中为 false,则直接返回该对象。否则,进行处理再返回。此时的 exposedObject
为什么使用了三级缓存就可以解决代理的问题?
当一个对象需要被代理的时候,在整个创建流程中包含两个对象(代理前的普通对象 和 代理后的牛逼对象)
而 bean默认是单例的,那么在整个生命周期的处理中,一个 beanname也只能对应一个对象。
解决措施:保证我在使用的时候,判断是否需要进行代理的处理。
怎么确定什么时候使用 AOP代理?
无法确定,因此需要使用匿名内部类或者 lambda,在使用的时候直接对普通对象进行覆盖。
保证全局唯一!
Spring bean装配流程和三级缓存的更多相关文章
- Spring Bean装配详解(五)
装配 Bean 的概述 前面已经介绍了 Spring IoC 的理念和设计,这一篇文章将介绍的是如何将自己开发的 Bean 装配到 Spring IoC 容器中. 大部分场景下,我们都会使用 Appl ...
- Spring Bean装配(下)——注解
@Repository,@Service,@Controller这三个注解是基于component定义的注解 component-scan:组件扫描 base-package:扫描这个下的所有类 &l ...
- Spring Bean装配(上)
Bean:在spring的IOC里面,把配置到IOC容器里面的实体或者是对象都称为Bean Bean配置项 Bean的作用域 Bean的生命周期 Bean的自动装配 Resources&Res ...
- Spring入门篇——第4章 Spring Bean装配(下)
第4章 Spring Bean装配(下) 介绍Bean的注解实现,Autowired注解说明,基于java的容器注解说明,以及Spring对JSR支持的说明 4-1 Spring Bean装配之Bea ...
- Spring入门篇——第3章 Spring Bean装配(上)
第3章 Spring Bean装配(上) 介绍Bean的作用域.生命周期.Aware接口.自动装配和Resource等内容. 3-1 Spring Bean装配之Bean的配置项及作用域 从上至下依次 ...
- Spring Bean装配笔记
Spring Bean装配笔记 Spring中的Bean是一个很重要的概念.Spring作为一个Bean容器,它可以管理对象和对象之间的依赖关系,我们不需要自己建立对象,把这部分工作全部转交给容器完成 ...
- Spring Bean装配学习
解释:所谓装配就是把一个类需要的组件给它设置进去,英文就是wire,wiring:注解Autowire也叫自动装配. 目前Spring提供了三种配置方案: 在XML中进行显式的配置 在Java中进行显 ...
- spring Bean装配的几种方式简单介绍
Spring容器负责创建应用程序中的bean同时通过ID来协调这些对象之间的关系.作为开发人员,我们需要告诉Spring要创建哪些bean并且如何将其装配到一起. spring中bean装配有两种方式 ...
- Spring Bean装配方式
Spring装配机制 在xml中进行显示配置 在Java中进行显示配置 隐式bean发现机制和自动装配 自动化装配bean 组件扫描(component scanning),Spring会自动发现应用 ...
随机推荐
- java Suspicious regex expression "." in call to 'replaceAll()' 问题延展
因为要处理从身份证读取到的有效期时间,所以用到了replaceAll这个方法,类似如下代码: String s1 = s.replaceAll(".", "-" ...
- Map集合的遍历方式以及TreeMap集合保存自定义对象实现比较的Comparable和Comparator两种方式
Map集合的特点 1.Map集合中保存的都是键值对,键和值是一一对应的 2.一个映射不能包含重复的值 3.每个键最多只能映射到一个值上 Map接口和Collection接口的不同 Map是双列集合的根 ...
- 若依 | 点击顶部 tag 标签不自动刷新
需求场景 之前:只要点击若依顶部的标签,页面都会自动刷新. 问题:A 页面有查询结果,切换到 B 页面查看信息,再切回 A 页面,则 A 页面的查询结果不会保留. 需求:点击标签,页面不自动刷新,或者 ...
- 漏洞扫描工具awvs13破解
AWVS13_windows破解版:链接:https://pan.baidu.com/s/1qeGiNubhWgY6oeMx_IkMtg 提取码:2i2o AWVS13_linux破解版:链接:htt ...
- 圆形谷仓Circular Barn_Silver---(DP优化 / )队列 + 贪心(复杂度O(2n))---DD(XYX)的博客
目录 小数据 大数据 小数据 题目描述 农夫约翰有一个圆形的谷仓,谷仓分成了环形的n(3≤n≤1000)个房间,编号为1 , 2 , -- .每个房间有三个门,两个门通往两个相邻的房间,第三个门朝外. ...
- 「CCO 2017」专业网络
Kevin 正在一个社区中开发他的专业网络.不幸的是,他是个外地人,还不认识社区中的任何人.但是他可以与 N 个人建立朋友关系 . 然而,社区里没几个人想与一个外地人交朋友.Kevin 想交朋友的 N ...
- 持久化-Word库加载项劫持
持久化-Word库加载项劫持 利用wll.xll和dll的特性来利用的 重点利用office word的信任文件来进行加载恶意代码
- session,cookie,jwt的简单使用
cookie的使用 https://blog.csdn.net/qq_58168493/article/details/122492358 session的使用 https://blog.csdn.n ...
- 【Java】学习路径56-TCP协议 发送、接收
与UDP不同的是,TCP协议使用的是Socket,而不是DatagramSocket,这是要作区分的. 构造Socket对象的时候,可以直接指定ip地址与端口号.此时需要抛出异常. import ja ...
- Apache DolphinScheduler 简单任务定义及复杂的跨节点传参
点亮 ️ Star · 照亮开源之路 GitHub:https://github.com/apache/dolphinscheduler Apache DolphinScheduler是一款非常不 ...