Spring IOC-Spring循环依赖解决方案-第五篇

什么是循环依赖?

目前,在我司是大力推行微服务之间消除循环依赖。即应用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是如何解决循环依赖

概述

主要通过两种方式:

  1. 细分生成单例Bean的生命周期。
  2. 三级缓存。

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使用三个缓存的原因吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值