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
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
Android实现打开各种文件的intent方法小结
C#实现加密的几种方法介绍
SpringBoot返回Json对象报错(返回对象为空{})
![](https://img.aspxhome.com/file/2023/2/61642_0s.png)
Java动态获取实现某个接口下所有的实现类对象集合
Java操作FTP实现上传下载功能
![](https://img.aspxhome.com/file/2023/1/82311_0s.png)
C#实现文字转语音功能
![](https://img.aspxhome.com/file/2023/6/91246_0s.jpg)
Java队列篇之实现数组模拟队列及可复用环形队列详解
![](https://img.aspxhome.com/file/2023/1/118821_0s.png)
c# 配置文件App.config操作类库的方法
详解如何在SpringBoot项目中使用统一返回结果
![](https://img.aspxhome.com/file/2023/4/58714_0s.png)
java中如何执行xshell命令
C#动态执行批处理命令的方法
浅析Android App的相对布局RelativeLayout
![](https://img.aspxhome.com/file/2023/6/110676_0s.jpg)
SpringBoot教程_创建第一个SpringBoot项目
![](https://img.aspxhome.com/file/2023/8/125258_0s.png)
android效果TapBarMenu绘制底部导航栏的使用方式示例
![](https://img.aspxhome.com/file/2023/2/84722_0s.gif)
C语言植物大战数据结构二叉树递归
![](https://img.aspxhome.com/file/2023/2/124042_0s.png)
使用SpringBoot+OkHttp+fastjson实现Github的OAuth第三方登录
![](https://img.aspxhome.com/file/2023/9/126339_0s.png)
Mybatis plus中的like查询问题
java property配置文件管理工具框架过程详解
MyBatis动态SQL标签用法实例详解
Android Fragment实现底部通知栏
![](https://img.aspxhome.com/file/2023/1/139311_0s.jpg)