3.3 Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案
3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖3.2spring源码系列----循环依赖源码分析
根据之前解析的循环依赖的源码,分析了一级缓存,二级缓存,三级缓存的作用以及如何解决循环依赖的. 然而在多线程的情况下,Spring在创建bean的过程中,可能会读取到不完整的bean. 下面,我们就来研究两点:
1. 为什么会读取到不完整的bean.
2. 如何解决读取到不完整bean的问题.
和本文相关的spring循环依赖的前两篇博文如下:
3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
3.2spring源码系列----循环依赖源码分析
一. 为什么会读取到不完整的bean.
我们知道,如果spring容器已经加载完了,那么肯定所有bean都是完整的了,但如果,spring没有加载完,在加载的过程中,构建bean就有可能出现不完整bean的情况
如下所示:

首先,有一个线程要去创建A类,调用getBean(A),他会怎么做呢?
第一步: 调用getSingleton()方法,去缓存中取数据,我们发现缓存中啥都没有,肯定返回null.
第二步: 将其放入到正在创建集合中,标记当前bean A正在创建
第三步: 实例化bean
第四步: 将bean放到三级缓存中. 定义一个函数接口,方便后面调用
@H_
502_40@
addSingletonFactory(beanName,() -> getEarlyBeanReference(beanName,mbd,bean));
第四步: 属性赋值. 在属性赋值的时候,返现要加载类B,就在这个时候,另一个线程也进来了,要创建Bean A.
第五步: 线程2 创建bean,也是先去调用getSinglton()从缓存中取,一二级换粗中都没有,但是三级缓存中却是有的. 于是就调用动态代理,去创建bean,很显然这时候创建的bean是不完整的. 然后将其放入到二级缓存中,二级缓存里的bean也是不完整的. 这就导致了后面是用的bean可能都是不完整的. 详细的分析上图
二. 如何解决读取到不完整bean的问题.
其实,之所以出现这样的问题,原因就在于,第一个bean还没有被创建完,第二个bean就开始了. 这是典型的并发问题.
针对这个问题,其实,我们加锁就可以了.
用自己手写的代码为例
第一: 将整个创建过程加一把锁
@H_
502_40@
/**
* 获取bean,根据beanName获取
*/
public static Object getBean(String beanName) throws Exception {
// 增加一个出口. 判断实体类是否已经被加载过了
Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
Object instanceBean;
synchronized (singletonObjects) {
标记bean正在创建
if (!singletonsCurrectlyInCreation.contains(beanName)) {
singletonsCurrectlyInCreation.add(beanName);
}
*
* 第一步: 实例化
* 我们这里是模拟,采用反射的方式进行实例化. 调用的也是最简单的无参构造函数
*/
RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
Class<?> beanClass = beanDefinition.getBeanClass();
调用无参的构造函数进行实例化
instanceBean = beanClass.newInstance();
*
* 第二步: 放入到三级缓存
* 每一次createBean都会将其放入到三级缓存中. getObject是一个钩子方法. 在这里不会被调用.
* 什么时候被调用呢?
* 在getSingleton()从三级缓存中取数据,调用创建动态代理的时候
singletonFactories.put(beanName,new ObjectFactory() {
@Override
public Object getObject() throws BeansException {
return new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.(beanName),beanName);
}
});
earlySingletonObjects.put(beanName,instanceBean);
*
* 第三步: 属性赋值
* instanceA这类类里面有一个属性,InstanceB. 所以,先拿到 instanceB,然后在判断属性头上有没有Autowired注解.
* 注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式,一种是name,一种是type
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
判断每一个属性是否有@Autowired注解
Autowired annotation = declaredField.getAnnotation(Autowired.class);
if (annotation != ) {
设置这个属性是可访问的
declaredField.setAccessible(true);
那么这个时候还要构建这个属性的bean.
* 获取属性的名字
* 真实情况,spring这里会判断,是根据名字,还是类型,还是构造函数来获取类.
* 我们这里模拟,所以简单一些,直接根据名字获取.
String name = declaredField.getName();
*
* 这样,在这里我们就拿到了 instanceB 的 bean
Object fileObject = getBean(name);
为属性设置类型
declaredField.set(instanceBean,fileObject);
}
}
*
* 第四步: 初始化
* 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了
*/
*
* 第五步: 放入到一级缓存
*
* 在这里二级缓存存的是动态代理,那么一级缓存肯定也要存动态代理的实例.
* 从二级缓存中取出实例,放入到一级缓存中
*/
if (earlySingletonObjects.containsKey(beanName)) {
instanceBean = earlySingletonObjects.(beanName);
}
singletonObjects.put(beanName,instanceBean);
删除二级缓存
删除三级缓存
}
instanceBean;
}