什么是循环依赖?
目前,在我司是大力推行微服务之间消除循环依赖。即应用A依赖B,B也依赖A。目前消除应用间的循环依赖,除了从业务和功能上去梳理合理性之外,此外还倡导应用下沉,即梳理出应用最核心的一部分职责或者功能,下沉到单独的一个应用。例如:A应用通过梳理后,下沉能力到一个新应用C,那么这个时候依赖结构就变成了:A应用依赖C,B应用也依赖C,C应用是独立的。即通过引入一个新的应用,来解决循环依赖的局面。
Spring内的循环依赖,其实和微服务是相似的,比如:有个Bean A,Bean B,A依赖B,B也依赖A,这种循环依赖在Spring内如果是通过构造方法初始化Bean,是不支持的,会抛出异常。如果是通过setter方法注入Bean,Spring是支持这种循环依赖的。
Spring循环依赖实例
<bean id="testCycleB" class="com.yangt.risk.ao.ioc.TestCycleB">
<property name="testCycleA" ref="testCycleA"/>
</bean>
<bean id="testCycleA" class="com.yangt.risk.ao.ioc.TestCycleA">
<property name="testCycleB" ref="testCycleB"/>
</bean>
public class TestCycleA {
private TestCycleB testCycleB;
public TestCycleB getTestCycleB() {
return testCycleB;
}
public void setTestCycleB(TestCycleB testCycleB) {
this.testCycleB = testCycleB;
}
}
public class TestCycleB {
private TestCycleA testCycleA;
public TestCycleA getTestCycleA() {
return testCycleA;
}
public void setTestCycleA(TestCycleA testCycleA) {
this.testCycleA = testCycleA;
}
}
public class ApplicationContextBeanCycleTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext pathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-ioc-test1.xml");
Object testCycleA = pathXmlApplicationContext.getBean("testCycleA");
System.out.println("==========="+testCycleA+"============");
Object testCycleB = pathXmlApplicationContext.getBean("testCycleB");
System.out.println("==========="+testCycleB+"============");
}
}
可见,通过setter方式注入是完全可行的,spring支持setter方式的这种循环依赖。
Spring是如何解决循环依赖
概述
主要通过两种方式:
- 细分生成单例Bean的生命周期。
- 三级缓存。
Spring内对单例Bean的生成,分为了三步:
1)实例化,createBeanInstance(通过反射执行无参构造方法,得到对象实例,属性都为null)
2)填充属性,populateBean (填充依赖的属性,如果是引用,则填充引用)
3)初始化,initlializeBean (执行init初始化方法,afterProperties会被回调)
Spring内有三级缓存,分别是:
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
singletonObjects :单例bean的缓存,key是beanName,value是已经初始化后的Bean。
earlySingletonObjects :提前曝光的单例Bean的缓存,key是beanName,value是实例化后的Bean。
singletonFactories:单例工厂缓存,key是beanName, ObjectFactory是一个接口,内部有一个方法用于获取真正的对象。
总体流程
这里重点要描述下递归的部分,因为在流程图上说不清楚,具体如下:
依旧是,A依赖B,B依赖A。按照如上的流程图,假如是A执行到了填充属性环节,发现依赖了B,那么就会调用beanFactory.getBean,去寻找B这个Bean,因为beanFactory.getBean在单例场景下的底层就是getSingleton。因此直接触发了此流程图开始的环节。B这个时候肯定是没有的,所以从三个缓存内都拿不到,于是会执行B的实例化环节,然后执行B的填充属性环节,发现B的属性依赖了A,于是继续调用beanFactory.getBean,即还是调用getSingleton,这个时候从getSingleton内的调用逻辑是如下:
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 != NULL_OBJECT ? singletonObject : null);
}
因为如上的流程图中,A已经在singletonFactories(单例工厂缓存)内了,这个时候,会执行上图的
singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject); 这两行代码,这是通过ObjectFactory.getObject获取对象,获取后被放到了earlySingletonObjects这个缓存内。此外说一下,这里使用ObjectFactory这个接口的目的,主要是处理代理的bean的情况。这时A这个Bean(实例化阶段)就会返回了。这个时候B继续执行初始化环节,到最后B这个Bean就初始化完成,放入到了singletonObjects这个缓存中。
这个时候继续说A,因为A依赖的B已经整体完成了初始化环节,虽然依赖的A是实例化阶段的A。A还是在填充属性阶段,发现依赖的B已经完全初始化了,A继续执行初始化环节。A执行完初始化环节后,A就真正的被初始化了。最重要的一点是:因为B持有A,所以这个时候的A也是初始化后的Bean了。
总结
通过以上流程也说明了,为何构造注入的循环依赖Spring会直接抛异常,因为在实例化阶段,就发生了冲突,无法执行。Spring之所以能完全支持setter的循环依赖,主要原因还是,将整个过程,拆成了三个环节以及缓存的使用。看了很多博客,很多人会问,其实解决循环依赖,两层缓存就够了,为何需要三个缓存,看了源码后,发现ObjectFactory这个接口对应的缓存,主要是用来处理代理Bean。因为需要调用objectFactory.getObject才能获取到真正的Bean。这或许是Spring使用三个缓存的原因吧。