目录
jvm类加载循环依赖
一个Java对象创建分为实例化和初始化两步,例如Person person = new Person();可以拆解为两步:
第一步:Person person;//类Person的引用,存储在栈中,用来指向Person在堆中创建的对象,可以理解为Person对象在堆中描述符,会触发Person类加载和类变量的初始化
第二步:person = new Person();//类Person的一个对象,储存在堆中,会触发类实例变量的默认值的初始化。
jvm类加载循环依赖两种情况:
情况1:
public class ClassAA {
private ClassBB classBB;
public ClassAA(ClassBB classBB ){
this.classBB = classBB;
}
}
public class ClassBB {
private ClassAA classAA;
public ClassBB(ClassAA classAA){
this.classAA = classAA;
}
}
测试代码:
ClassAA classAA = new ClassAA(new ClassBB());//new ClassBB();也需要有参
ClassBB classBB = new ClassBB(new classAA());//new ClassAA();也需要有参
运行结果:编译就不通过,这种写法本身语法逻辑就行不通
情况2:
public class ClassBB {
private ClassAA classAA;
public ClassAA getClassAA() {
return classAA;
}
public void setClassAA(ClassAA classAA) {
this.classAA = classAA;
}
}
public class ClassAA {
private ClassBB classBB;
public ClassBB getClassBB() {
return classBB;
}
public void setClassBB(ClassBB classBB) {
this.classBB = classBB;
}
}
测试代码:
ClassAA classAA = new ClassAA();
ClassBB classBB = new ClassBB();
System.out.println(classAA+ ","+ classBB.getClassAA());
System.out.println(classBB +","+ classAA.getClassBB());
运行结果:
类在循环加载如何解决,todo…
Spring的IoC容器如何解决循环依赖
问题引出
Spring的IoC容器在按照饿汉方式(单例模式)初始化容器的时候,也会同时创建bean,将bean属性初始化,如果beanA其中一个属性是BeanB,beanB的一种属性是BeanA,就形成了Bean对象初始化时的循环依赖。
public class ClassA {
private ClassB classB;
public void setClassB(ClassB classB) {
this.classB = classB;
}
public ClassB getClassB() {
return classB;
}
}
public class ClassB {
private ClassA classA;
public void setClassA(ClassA classA) {
this.classA = classA;
}
public ClassA getClassA() {
return classA;
}
}
xml配置文件
<bean id="classA" class="com.noodles.domain.ClassA">
<property name="classB" ref="classB"></property>
</bean>
<bean id ="classB" class="com.noodles.domain.ClassB">
<property name="classA" ref="classA"></property>
</bean>
测试代码
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:beans.xml");
ClassA classA = (ClassA) applicationContext.getBean("classA");
ClassB classB = (ClassB) applicationContext.getBean("classB");
System.out.println(classA==classB.getClassA());
System.out.println(classB==classA.getClassB());
程序执行结果:
程序正常执行,说明spring在向IOC中注册bean的时候,解决了循环依赖问题。
Spring循环依赖解决办法
bean初始化过程:首先根据解析bean配置文件,从上到下创建bean对象,当bean中的属性是IOC中其他Bean对象是,会先去创建依赖的bean对象,才能完成当前bean的属性初始化。
bean创建的核心逻辑在DefaultSinggletonBeanRegistry和AbstranctBeanFactory类中,两个类属于继承关系。
在从IoC容器中获取bean时,依次从一级缓存singletonObjects-》二级缓存earlySingletonObjects-》三级缓存中singletonFactories根据bean id判断bean对象是否存在。
三级缓存作用:
一级缓存:Map<String,String> singletonObjects
,key时bean的id,value时初始化的bean对象,也是最终可以直接拿来用的bean对象。
二级缓存:Map<String,Obejct> earlySingletonObjects
,key时bean的id,value时实例化好的对象,还没有给对象属性初始化。
三级缓存:Map<String,ObjectFactory<?>> singletonFactories
,key时bean的id,value时类型时ObjectFactory,ObjectFactoy时一个函数式接口,其中里面的getObject方法用来被重写生成bean对象,value可以理解为一段生成bean对象的逻辑。
解决循环依赖是否需要三级缓存,首先尝试使用两级缓存看能不能解决循环依赖问题,此时一个bean对象创建过程:
getBean-》实例化-》填充属性-》初始化
Spring通过AOP,有时需要对一些对象进行动态代理,然后将动态代理对象放在IoC容器中,要同时解决循环依赖,需要用到三级缓存,此时创建一个bean的过程变成:
getBean-》实例化-》填充属性-》初始化-》生成代理对象
总结
当没有循环依赖式,使用一级缓存就可以。当有循环依赖,不需要对对象进行动态代理时,两层缓存就可以解决;当对象需要动态代理,并且代理对象需要存在循环依赖,才需要三级缓存。