SpringBean依赖和 * 缓存的案例讲解
作者:酒剑随马@ 发布时间:2023-06-25 09:33:22
spring中的bean依赖有大体上可以分为两类,共3中形式,下面简单介绍一下。
第一类是构造方法中的循环依赖,这种会报错
@Service
public class ServiceA {
private ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
public void methodA(){
System.out.println("a");
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
public void methodB(){
System.out.println("b");
}
}
//错误提示
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| serviceA defined in file [C:\demo\target\classes\com\example\demo\ServiceA.class]
↑ ↓
| serviceB defined in file [C:\demo\target\classes\com\example\demo\ServiceB.class]
└─────┘
第二类是field循环依赖,它分为两种,第一类循环依赖的作用域scope默认是singleton,启动不会报错
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void methodA(){
System.out.println("a");
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void methodB(){
System.out.println("b");
}
}
第二种作用域scope为prototype,在这种情况下bean是多例的,按理说这种启动也会报错,但是它成功了。。我也不知道为啥
@Service
@Scope("prototype")
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void methodA(){
System.out.println("a");
}
}
@Service
@Scope("prototype")
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void methodB(){
System.out.println("b");
}
}
据我在网上查找的资料,spring可以帮我们处理bean的scope为singleton的field循环依赖,个人感觉应该也是这样,下面说一下它的处理过程。
简单说一下bean的加载过程,当spring启动的时候,首先加载进beanFactory的是beanDefinition,之后会根据beanDefinition判断其是否为sington并且为非抽象类非懒加载,那么之后会去创建bean,
bean的创建分为三步:
1.调用构造方法创建对象实例(这一步完成之后其它对象实例就可以引用它)
2.填充实例内部属性(会依次从 * 缓存中获取依赖的bean,如果没有找到,则会先去创建依赖的bean,之后再返回继续填充属性)
3.执行initializeBean方法,进行初始化
当bean进行创建时,会先调用getbean方法->执行doGetBean方法,在doGetBean方法中会调用getSingleton方法,这一步就是从 * 缓存中获取对象缓存,因为是刚开始创建bean所以缓存中肯定没有,之后会调用createBean方法,在createBean方法中会调用doCreateBean执行bean的创建过程就是上面的那三步,当bean创建成功之后会将其放入一级缓存之中,此时会将它从 * 和二级缓存中删除。
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//从缓存中获取实例
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//省略代码
} else {
//省略代码
createBean(beanName, mbd, args);
}
//将创建完成的bean放入一级缓存
addSingleton(beanname,object)
}
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
//省略代码
doCreateBean()
}
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
//根据构造方法创建对象实例
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
//将对象实例放入第 * 缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
//实例内部属性填充
populateBean(beanName, mbd, instanceWrapper);
//初始化bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
其中我们可以看到当第一步执行完毕后会将刚刚创建的实例放入singletonFactories(第 * 缓存)中,那么我们下面了解下到底什么是spring的 * 缓存。处于最上层的缓存是singletonObjects,它其中存储的对象是完成创建好,可以正常使用的bean,二级缓存叫做earlySingletonObjects,它其中存储的bean是仅执行了第一步通过构造方法实例化,并没有填充属性和初始化,第 * 缓存singletonFactories是一个工场。
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
其实在getSingleton方法中会首先从一级缓存中获取bean,一级缓存中没有再从二级缓存中获取,二级也没有就会从 * 中获取factory当factory不为null时,则会调用getObject方法获取bean,并将bean放入二级缓存,之后再从 * 缓存中删除该key-value对,代码如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
那么到此处我们就可以看出为什么不能在构造方法中存在循环依赖了,假如现在有a、b两个service,它们两个互相在构造方法中循环依赖,当项目启动,创建a的bean时执行第一步通过构造方法创建实例,但是发现依赖b的bean,所以就从 * 缓存中获取,但是没发现,那么就先挂起a的创建过程,先去创建b,在b创建过程中,又依赖于a,但是 * 缓存中也没有a的bean,这样就进入了一个循环创建的过程,自然是不可取的。
而内部field scope为prototype为何也会报错呢,当scope为prototype每次引用它时都会创建一个新的对象,所以也会存在循环创建的过程。
而默认情况下bean的scope为singleton,整个容器中仅有整个service的一个bean,还是假如a、b两service存在field循环依赖,当创建a的bean时,执行完构造方法后,a的实例已生成,将其factory对象存入第 * 缓存singletonFactories中,在填充属性时,发现依赖b的bean,但是在缓存中没有b的bean;因此转而去创建b,在此过程中执行完b的构造方法后将其factory也放入 * 缓存,此时执行b的属性填充,发现依赖a,从 * 缓存中获取a的对象,并将a放入二级缓存中,之后执行intialize初始化,最后将b的bean转入一级缓存;再继续回来创建a,这个时候发现在一级缓存中已经有了b,那么属性填充成功,进行初始化,最后a也放入一级缓存,至此执行完毕。
那么大家可能会感到疑惑,为什么要使用 * 缓存呢,感觉没有singletonFactories使用二级缓存也可以呀?
从前面的代码里可以看到向第 * 缓存中放置的是一个objectFactory的匿名实现类,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)),当我们从singletonFactories中获取objectFctory然后调用getObject方法获取bean的时候,实际就是通过getEarlyBeanReference获取的object,那么进入这个方法看一下。
它在这里会获取所有的beanPostProcessor实现类,然后从中找出实现了SmartInstantiationAwareBeanPostProcessor的beanPostProcessor,然后调用它的getEarlyBeanReference(obgect,beanName)方法,对bean进行处理,然后进行返回,这些实现类中就有aop的核心AbstractAutoProxyCreator,从这里我们就可以看出来,从第 * 缓存objectFactory中获取的obejct是经过了处理的一个代理对象,个人理解 * 缓存就是为了获取在创建对象的过程中提前对其进行一些扩展操作。
* 缓存实现bean的扩展,将代理对象放入二级缓存中,供其他依赖该bean的对象的使用,如果没有了 * 缓存,将bean扩展放在二级缓存中实现,那么如果有bean a被其他多个bean依赖,那么在其他bean填充属性的过程中会多次获取bean a,这个过程中就会多次执行获取bean a代理,就有些多余,而 * 缓存结构就是在第 * 缓存完成bean的扩展,生成代理对象,放入二级缓存之中,供其他bean获取。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。如有错误或未考虑完全的地方,望不吝赐教。
来源:https://blog.csdn.net/swl1993831/article/details/106678932


猜你喜欢
- 本文实例为大家分享了Android实现外卖购物车功能的具体代码,供大家参考,具体内容如下先看看效果图:知识点分析效果图来看不复杂内容并没多少
- Activity是最基本的模块,一般称之为"活动",在应用程序中,一个Activity通常就是一个单独的屏幕。简单理解,
- 一、在java中遍历一个文件夹里边的所有文件,可以有两种方式:1.递归遍历,通常也是开发者第一时间能想到的方法,递归遍历的优点是:实现起来相
- 主要功能设计:用户、区域、物质类型、物质详情、物质申请和审核以及我的申请和通知公告以及灵活控制菜单权限主要技术实现:spring、 spri
- 实现的功能比较简单,就是随机产生了四个字符然后输出。效果图如下,下面我会详细说一下实现这个功能用到了那些知识点,并且会把 这些知识点详细的介
- 对于Android View的测量,我们一句话总结为:
- springboot整合vue实现上传下载文件,供大家参考,具体内容如下环境springboot 1.5.x完整代码下载:springboo
- //activity的xml<?xml version="1.0" encoding="utf-8&qu
- 本文实例讲述了C#迷你猜数。分享给大家供大家参考。具体如下:using System; using System.Collections.G
- 通俗的来说,Jackson是一个 Java 用来处理 JSON 格式数据的类库,其性能非常好。本文就来针对Jackson的用法做一个较为详细
- 前言C++中修饰数据可变的关键字有三个:const、volatile和mutable。const比较好理解,表示其修饰的内容不可改变(至少编
- 分页插件  MP中自带了分页插件的功能,只需要在配置类中进行简单的配置即可使用分页的相关功能。分页插件常
- String:字符串类型1、构造函数。String() :构造一个空字符串对象。String(byte[] bytes) :通过byte数组
- 用java压缩/解压文件: import java.io.*; import java.awt.*; import java.aw
- 本文实例讲述了C#检测DataSet是否为空的方法。分享给大家供大家参考。具体如下:下面的代码片段通过判断DataSet的Table数量来判
- 前言在java生态圈谈到Rpc,很多人可能就会想到Dubbo、Motan、Grpc等框架。但是你知道吗?作为Java编程全家桶的Spring
- 本文为大家分享了Unity实现粒子光效导出成png序列帧的具体代码,供大家参考,具体内容如下这个功能并不是很实用,不过美术同学有这样的需求,
- 本文实例讲述了Java文本文件操作方法。分享给大家供大家参考。具体分析如下:最初Java是不支持对文本文件的处理的,为了弥补这个缺憾而引入了
- 本文实例讲述了Android应用启动另外一个apk应用的方法。分享给大家供大家参考,具体如下:在开发的过程中,经常会遇到在一个应用中启动另外
- 前面讲解了MediaPlayer播放网络音频,主要介绍了MediaPlayer关于网络音频的缓冲和进度条控制的方法,本文再来讲解一下Medi