文章目录
手写spring ioc(二)
本篇介绍
本篇主要解决属性之间的循环依赖的问题
循环依赖解决方案
在我们探讨解决方案之前先看一个基于ioc(一) 的循环依赖的例子,看看会发生什么
public class CBean {
private ABean aBean;
}
public class Test {
DefaultListableBeanFactory fac = new DefaultListableBeanFactory();
@org.junit.jupiter.api.Test
public void testIoc1() throws Exception {
BeanDefinition bd1 = new GenericBeanDefinition();
bd1.setBeanClass(CBean.class);
List<PropertyValue> pvs1 = new ArrayList<>();
pvs1.add(new PropertyValue("aBean",new BeanReference("aBean")));
bd1.setPropertyValues(pvs1);
fac.registerBeanDefinition("cBean",bd1);
BeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(ABean.class);
bd.setInitMethodName("init");
List<PropertyValue> pvs = new ArrayList<>();
pvs.add(new PropertyValue("name","lry"));
pvs.add(new PropertyValue("age",20));
pvs.add(new PropertyValue("cBean",new BeanReference("cBean")));
bd.setPropertyValues(pvs);
fac.registerBeanDefinition("aBean",bd);
fac.preInstantiateSingletons();
//上面都是系统做的,以后会支持注解扫描出类注册bd,提前实例化bean
ABean aBean = (ABean) fac.getBean("aBean");
aBean.doSth();
}
}
ABean依赖cBean,CBean 依赖aBean ,不出意外的stackOverflow了
要想解决问题,必须得发现问题,定位问题,循环依赖亦是如此,我们得先定位到什么时候出现了循环依赖,我们可以定义一个singletonsCurrentlyInCreation set集合存储正在创建的spring beanName,何谓正在创建,即getBean到放到singletonObjects里之前都是正在创建,一旦getBean(beanName)时发现singletonsCurrentlyInCreation 中已经有此beanName 就说明出现了循环依赖,那么如何解决呢?
我们可以定义一个earlySingletonObjects,在DI之前 就把实例化好的对象put到这个早期单例池中,到时候去这个里面拿即可
doGetBean代码改为如下
protected Object doGetBean(String beanName) throws Exception {
BeanDefinition bd = beanDefinitionMap.get(beanName);
if(bd==null||bd.getBeanClass()==null){
return null;
}
Object instance = null;
//单例直接从单例池拿
if(bd.isSingleton()){
instance = getSingleton(beanName);
if(instance!=null){
return instance;
}
if(singletonsCurrentlyInCreation.contains(beanName)){
throw new Exception(beanName + " 循环依赖!" + singletonsCurrentlyInCreation);
}
singletonsCurrentlyInCreation.add(beanName);
}
//决策构造器
instance = createBeanInstance(bd);
if(bd.isSingleton()) {
earlySingletonObjects.put(beanName, instance);
}
//DI
populateBean(bd,instance);
//AOP代理 生命周期回调
instance = initializeBean(bd,instance);
//加入单例池
if(bd.isSingleton()) {
singletonObjects.put(beanName, instance);
earlySingletonObjects.remove(beanName);
singletonsCurrentlyInCreation.remove(beanName);
}
return instance;
}
private Object getSingleton(String beanName){
//单例池中拿 第一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
//单例池没有&&当前bean正在创建中
if (singletonObject == null&&isBeanNameInUse(beanName)){
//从早期单例池拿
synchronized (this.singletonObjects){
//early中拿 第二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
}
}
return singletonObject;
}
总结
本篇较为简单,当然这是在没有aop的情况,有aop的情况会极其复杂,spring源码会在循环依赖中提前进行aop代理,这也是不得已为之,不然最后spring容器存储的ABean里的cBean属性就不是代理对象了,可以看这个例子
public static void main(String[] args) {
ABean a = new ABean();
CBean c = new CBean();
a.cBean = c; // cBean aop代理前
c = null; // aop代理
System.out.println(a);
//a的属性cBean 并不会为null,即 a的属性cBean是一个未经aop代理的对象,所以spring对于循环依赖的情况不得不提前aop
//个人理解,可能有偏差
}
关于循环引用还有一个问题 :
假如先getBean(“aBean”),然后getBean(“cBean”) ,按照我们解决循环依赖的逻辑,可以画出下图
图其中第八步的意思是从早期单例池拿出一个未DI的ABean赋给CBean的aBean属性,那么这一步会有问题吗,即最终的CBean的属性aBean有值吗,测试代码表明是有的,下面再给出一个例子说明这点
public static void main(String[] args) {
CBean cBean = new CBean();
ABean aBean = new ABean();
cBean.aBean = aBean;
aBean.age = 20;
aBean.name = "lry";
System.out.println(cBean);
//此时cBean的属性aBean是有属性age和name的
}