上图展示了循环依赖是什么,类A存在B类的成员变量,所以类A依赖于类B,类B同样存在类A的成员变量,所以类B也依赖于类A,就形成了循环依赖问题。
Spring是如何创建Bean的
Spring 中Bean初始化的精简流程如下:
简要描述一下Spring Bean的创建流程:
(1)首先Spring容器启动之后,会根据使用不同类型的ApplicationContext,通过不同的方式去加载Bean配置,如xml方式、注解方式,将这些Bean配置加载到容器中,Bean定义包装成BeanDefinition对象保存起来,为下一步创建Bean做准备。
(2)根据加载的Bean定义信息,通过反射来创建Bean实例,如果是普通Bean,则直接创建Bean,如果是FactoryBean,说明真正要创建的对象为getObject()的返回值,调用getObject()将返回值作为Bean。
(3)目前已经完成了Bean实例的创建,还需要对依赖的属性进行装配,例如,平时开发中Controller中,往往需要将Service Bean注入进来,循环依赖也是在这一步解决的,后面会详细说明,如果通过@Autowired注解注入的成员变量,则会通过AutowiredAnnotationBeanPostProcessor后置处理器进行注入,如果xml自动注入,则会根据按名字自动装配和按类型自动装配分别进行处理。
(4)自动装配完成后,将完整的Bean对象保存到Spring 缓存中,接下来进入Bean的初始化流程,执行Bean的后置处理器的前置处理方法,如果Bean本身实现了InitializingBean接口,就去执行对应的afterPropertiesSet()方法,最后再执行Bean的后置处理器的后置处理方法。
Spring是如何进行依赖注入的
既然要了解循环依赖是如何解决,首先要了解,Spring是如何进行依赖注入的,在Spring中,创建Bean的方式基本是通过调用BeanFacotry的getBean()方法来进行创建的,在常用的实现了ApplicationContext接口的高级容器的内部都创建了一个BeanFacoty的实例。
这个实例一般是DefaultListableBeanFactory,如果ApplicationContext比喻成汽车,DefaultListableBeanFactory就是汽车的发动机,整个Bean的创建装配过程都是在DefaultListableBeanFactory类的preInstantiateSingletons()方法中完成的。
xml方式依赖注入
如下,手工配置好的依赖关系,Spring读取Bean信息时,已经保存在BeanDefinition内部了。
<bean id="a" class="com.wzy.circle.A">
<property name="b" ref="b"/>
</bean>
获取xml中的依赖项
对依赖进行注入
如果是引用类型的,则要去容器中去解析对应的bean,如果不存在则去通过getBean()方法去获取。
最终通过反射将依赖进行注入。
如果使用xml进行自动注入,autowire属性设置为byName,则使用autowireByName()方法获取注入的属性。
<bean id="a" class="com.wzy.circle.A" autowire="byName">
</bean>
通过getBean()获取依赖信息。
如果autowire属性设置为byType,则使用autowireByType()方法获取注入的属性。
<bean id="a" class="com.wzy.circle.A" autowire="byType">
</bean>
在autowireByType()方法内部解析对应依赖。
注解方式依赖注入
如果使用@Autowired注解方式注入。
@Service
public class A {
@Autowired
private B b;
public A() {
System.out.println("create A instance");
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
在装配Bean属性时,判断容器是否有InstantiationAwareBeanPostProcessor类型的Bean处理器,这是一个特殊的Bean后置处理器,循环这些后置处理器调用postProcessProperties()方法。
使用@Autowired注解,主要是使用InstantiationAwareBeanPostProcessor接口的实现类AutowiredAnnotationBeanPostProcessor类。
这个方法为一个默认的钩子方法,有默认的实现。直接返回null。
调用AutowiredAnnotationBeanPostProcessor的postProcessProperties()方法,在方法中,首先获取类中@Autowired注解的元信息,再通过inject()方法从容器中获取对象利用反射进行注入,如果容器中不包含该Bean,则同样使用getBean()方法进行获取。
Spring是如何解决循环依赖的
类A
@Service
public class A {
@Autowired
private B b;
public A() {
System.out.println("create A instance");
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
类B
@Service
public class B {
@Autowired
private A a;
public B() {
System.out.println("create B instance");
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
根据上面的介绍的依赖注入流程,A和B对象的装配流程如下:
类A创建Bean对象之后去解析依赖类B对象,调用getBean()方法去获取类B对象,之后创建B类对象,去装配类B的属性,此时类B又依赖于类A,在容器中获取类A,此时容器中还不存在类A,那么Spring是如何解决这个问题的呢?
三级缓存解决循环依赖
Spring通过引入三级缓存的机制来解决这个问题,如下图所示:
所谓的三级缓存,代表的是三个保存单例bean的集合。
一级缓存:即单例对象缓存池,beanName->Bean,其中存储的就是实例化,属性赋值成功之后的单例对象。
二级缓存:早期的单例对象,beanName->Bean,其中存储的是实例化之后,属性未赋值的单例对象,执行了工厂方法生产出来的Bean,bean被放进去之后, 当bean在创建过程中,就可以通过getBean方法获取到早期单例对象。
三级缓存:单例工厂的缓存,beanName->ObjectFactory,添加进去的时候实例还未具备属性,用于保存beanName和创建bean的工厂之间的关系map。
在获取单例bean时,调用DefaultSingletonBeanRegistry的getSingleton()方法获取单实例bean,DefaultListableBeanFactory为DefaultSingletonBeanRegistry的父类,getSingleton()方法实现如下:
getSingleton()方法的逻辑如下:
1)首先尝试从一级缓存中获取,创建并填充好属性的bean,如果单例bean不存在,并且要获取的bean正在创建中。
2)接下来,给一级缓存对象加锁,从早期实例map也就是二级缓存中获取,如果获取不到,并且是允许早期实例化的,那么就从三级缓存中获取,如果不为空,将bean放入二级缓存中,从三级缓存中将bean移除,最终将单例bean进行返回。
其实Spring解决循环依赖的方案就在上述的代码中,如果bean是允许早期实例化的,说明bean是允许被循环依赖的。
Spring解决循环依赖的过程如下:
(1)如果两个bean循环依赖,在调用A类的getBean()方法创建单例bean的过程中,A类的Bean创建完成后,如果是允许循环依赖,将创建Bean的单例工厂对象放入三级缓存。
(2)A类bean对象创建完成后,要去解析依赖的B,要获取B类的bean,由于容器中不存在B类实例,则创建B类实例,同样将创建Bean的工厂对象放入三级缓存,之后再去解析依赖的A对象。
(3)这时候,调用上面介绍的getSingleton()方法,获取单例对象,由于A类和B类都允许循环依赖,所以,在三级缓存里面能找到对应的Bean,可以获取到对应的单例Bean,并且将单例Bean放入二级缓存中,将三级缓存中的类A移除。
(4)此时类B的Bean对象已经是完整的了,将类B的Bean对象放入一级缓存中,移除二级缓存和三级缓存中的类B。
(5)类B对象创建完成之后,并且保存到一级缓存中,之后再回到类A的populateBean()方法,通过getBean()方法已经获取到了完整的B实例。
(6)将B的实例Bean注入到类A中,此时A也是完整的对象了,将A的bean对象放入一级缓存,移除类A在二级缓存和三级缓存中的信息,至此就完成了类A和B类Bean的创建。
具体原理图如下:
Spring采用了三级缓存的机制,在创建完对象之后就把不完整的实例放到三级缓存中,让循环依赖的Bean可以获取到对应Bean实例,从而解决了循环依赖的问题,值得注意的是,Spring只能解决单例Bean的循环依赖,其他作用域的Bean循环依赖,Spring就无能为力了。
三种循环依赖的情况
① 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException异常。
② 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖,能处理。
③ 非单例循环依赖:无法处理。原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。
if (isPrototypeCurrentlyInCreation(beanName))
{ throw new BeanCurrentlyInCreationException(beanName);}
原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A…这就套娃了, 你猜是先StackOverflow还是OutOfMemory?
Spring怕你不好猜,就先抛出了BeanCurrentlyInCreationException。
标签:缓存,Spring,bean,Bean,依赖,单例,循环 From: https://www.cnblogs.com/xfeiyun/p/17278349.html