0

一张图彻底搞懂Spring循环依赖

 2 years ago
source link: https://my.oschina.net/gupaoedutom/blog/5291138
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1 什么是循环依赖?

如下图所示:

file

BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类。这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖。同理,再如下图的情况:

file

上图中,BeanA类依赖了BeanB类,BeanB类依赖了BeanC类,BeanC类依赖了BeanA类,如此,也形成了一个依赖闭环。再比如:

file

上图中,自己引用了自己,自己和自己形成了依赖关系。同样也是一个依赖闭环。那么,如果出现此类循环依赖的情况,会出现什么问题呢?

2 循环依赖问题复现

2.1 定义依赖关系

我们继续扩展前面的内容,给ModifyService增加一个属性,代码如下:


@GPService
public class ModifyService implements IModifyService {

	@GPAutowired private QueryService queryService;
    
    ...

}

给QueryService增加一个属性,代码如下:


@GPService
@Slf4j
public class QueryService implements IQueryService {

	@GPAutowired private ModifyService modifyService;

    ...
    
}

如此,ModifyService依赖了QueryService,同时QueryService也依赖了ModifyService,形成了依赖闭环。那么这种情况下会出现什么问题呢?

2.2 问题复现

我们来运行调试一下之前的代码,在GPApplicationContext初始化后打上断点,我们来跟踪一下IoC容器里面的情况,如下图:

file

启动项目,我们发现只要是有循环依赖关系的属性并没有自动赋值,而没有循环依赖关系的属性均有自动赋值,如下图所示:

file

这种情况是怎么造成的呢?我们分析原因之后发现,因为,IoC容器对Bean的初始化是根据BeanDefinition循环迭代,有一定的顺序。这样,在执行依赖注入时,需要自动赋值的属性对应的对象有可能还没初始化,没有初始化也就没有对应的实例可以注入。于是,就出现我们看到的情况。

3 使用缓存解决循环依赖问题

file

3.1 定义缓存

具体代码如下:


// 循环依赖的标识---当前正在创建的实例bean
    private Set<String> singletonsCurrectlyInCreation = new HashSet<String>();

    //一级缓存
    private Map<String, Object> singletonObjects = new HashMap<String, Object>();

    // 二级缓存: 为了将成熟的bean和纯净的bean分离. 避免读取到不完整的bean.
private Map<String, Object> earlySingletonObjects = new HashMap<String, Object>();

3.2 判断循环依赖

增加getSingleton()方法:


/**
     * 判断是否是循环引用的出口.
     * @param beanName
     * @return
     */
    private Object getSingleton(String beanName,GPBeanDefinition beanDefinition) {

        //先去一级缓存里拿,
        Object bean = singletonObjects.get(beanName);
        // 一级缓存中没有, 但是正在创建的bean标识中有, 说明是循环依赖
        if (bean == null && singletonsCurrentlyInCreation.contains(beanName)) {

            bean = earlySingletonObjects.get(beanName);
            // 如果二级缓存中没有, 就从三级缓存中拿
            if (bean == null) {
                // 从三级缓存中取
                Object object = instantiateBean(beanName,beanDefinition);

                // 然后将其放入到二级缓存中. 因为如果有多次依赖, 就去二级缓存中判断. 已经有了就不在再次创建了
                earlySingletonObjects.put(beanName, object);


            }
        }
        return bean;
    }
		

3.3 添加缓存

修改getBean()方法,在getBean()方法中添加如下代码:


		 //Bean的实例化,DI是从而这个方法开始的
    public Object getBean(String beanName){

        //1、先拿到BeanDefinition配置信息
        GPBeanDefinition beanDefinition = regitry.beanDefinitionMap.get(beanName);

        // 增加一个出口. 判断实体类是否已经被加载过了
        Object singleton = getSingleton(beanName,beanDefinition);
        if (singleton != null) { return singleton; }

        // 标记bean正在创建
        if (!singletonsCurrentlyInCreation.contains(beanName)) {
            singletonsCurrentlyInCreation.add(beanName);
        }

        //2、反射实例化newInstance();
        Object instance = instantiateBean(beanName,beanDefinition);

        //放入一级缓存
        this.singletonObjects.put(beanName, instance);

        //3、封装成一个叫做BeanWrapper
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        //4、执行依赖注入
        populateBean(beanName,beanDefinition,beanWrapper);
        //5、保存到IoC容器
        factoryBeanInstanceCache.put(beanName,beanWrapper);

        return beanWrapper.getWrapperInstance();
    
		}

3.4 添加依赖注入

修改populateBean()方法,代码如下:


    private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {

        ...

            try {

                //ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
                field.set(instance,getBean(autowiredBeanName));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        ...

    }

4 循环依赖对AOP创建代理对象的影响

4.1 循环依赖下的代理对象创建过程

我们都知道Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器来自动完成的。也就是说Spring最终给我们放进容器里面的是一个代理对象,而非原始对象。

这里我们结合循环依赖,再分析一下AOP代理对象的创建过程和最终放进容器内的动作,看如下代码:


@Service
public class MyServiceImpl implements MyService {
    @Autowired
    private MyService myService;
    
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

此Service类使用到了事务,所以最终会生成一个JDK动态代理对象Proxy。刚好它又存在自己引用自己的循环依赖的情况。跟进到Spring创建Bean的源码部分,来看doCreateBean()方法:


protected Object doCreateBean( ... ){
	
		...

		// 如果允许循环依赖,此处会添加一个ObjectFactory到三级缓存里面,以备创建对象并且提前暴露引用
		// 此处Tips:getEarlyBeanReference是后置处理器SmartInstantiationAwareBeanPostProcessor的一个方法,
		// 主要是保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象
		// AOP自动代理创建器此方法里会创建的代理对象

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		boolean earlySingletonExposure = (mbd.isSingleton() && 
													this.allowCircularReferences && 
													isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) { // 需要提前暴露(支持循环依赖),注册一个ObjectFactory到三级缓存
				addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// 如果发现自己被循环依赖,会执行上面的getEarlyBeanReference()方法,从而创建一个代理对象从三级缓存转移到二级缓存里
		// 注意此时候对象还在二级缓存里,并没有在一级缓存。并且此时可以知道exposedObject仍旧是原始对象	populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	
		// 经过这两大步后,exposedObject还是原始对象
		// 注意:此处是以事务的AOP为例
		// 因为事务的AOP自动代理创建器在getEarlyBeanReference()创建代理后,
	 // initializeBean() 就不会再重复创建了,二选一,下面会有详细描述)
	
		...
	
		// 循环依赖校验(非常重要)
		if (earlySingletonExposure) {
				// 前面讲到因为自己被循环依赖了,所以此时候代理对象还存放在二级缓存中
				// 因此,此处getSingleton(),就会把代理对象拿出来
				// 然后赋值给exposedObject对象并返回,最终被addSingleton()添加进一级缓存中
				// 这样就保证了我们容器里缓存的对象实际上是代理对象,而非原始对象

				Object earlySingletonReference = getSingleton(beanName, false);
				if (earlySingletonReference != null) {
	
						// 这个判断不可少(因为initializeBean()方法中给exposedObject对象重新赋过值,否则就是是两个不同的对象实例)
						if (exposedObject == bean) { 				
								exposedObject = earlySingletonReference;
						}
				}
				...
		}
	
}

以上代码分析的是代理对象有自己存在循环依赖的情况,Spring用三级缓存很巧妙的进行解决了这个问题。

4.2 非循环依赖下的代理对象创建过程

如果自己并不存在循环依赖的情况,Spring的处理过程就稍微不同,继续跟进源码:


protected Object doCreateBean( ... ) {
		...

		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

		...
	
		// 此处注意,因为没有循环引用,所以上面getEarlyBeanReference()方法不会执行
		// 也就是说此时二级缓存里并不会存在
		populateBean(beanName, mbd, instanceWrapper);

		// 重点在此
		//AnnotationAwareAspectJAutoProxyCreator自动代理创建器此处的postProcessAfterInitialization()方法里,会给创建一个代理对象返回
		// 所以此部分执行完成后,exposedObject() 容器中缓存的已经是代理对象,不再是原始对象
	 // 此时二级缓存里依旧无它,更别提一级缓存了
	 exposedObject = initializeBean(beanName, exposedObject, mbd);

		...
	
		// 循环依赖校验
		if (earlySingletonExposure) {
				// 前面讲到一级、二级缓存里都没有缓存,然后这里传参数是false,表示不从三级缓存中取值
				// 因此,此时earlySingletonReference = null ,并直接返回

				// 然后执行addSingleton()方法,由此可知,容器里最终存在的也还是代理对象

				Object earlySingletonReference = getSingleton(beanName, false);
				if (earlySingletonReference != null) {
						if (exposedObject == bean) { 
								exposedObject = earlySingletonReference;
						}
				}
			 ...
}

根据以上代码分析可知,只要用到代理,没有被循环引用的,最终存在Spring容器里缓存的仍旧是代理对象。如果我们关闭Spring容器的循环依赖,也就是把allowCircularReferences设值为false,那么会不会出现问题呢?先关闭循环依赖开关。


// 它用于关闭循环引用(关闭后只要有循环引用现象将报错)
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false);

    }
}

关闭循环依赖后,上面代码中存在A、B循环依赖的情况,运行程序会出现如下异常:


Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
	

此处异常类型也是BeanCurrentlyInCreationException异常,但报错位置在DefaultSingletonBeanRegistry.beforeSingletonCreation

我们来分析一下,在实例化A后给其属性赋值时,Spring会去实例化B。B实例化完成后会继续给B属性赋值,由于我们关闭了循环依赖,所以不存在提前暴露引用。因此B无法直接拿到A的引用地址,只能又去创建A的实例。而此时我们知道A其实已经正在创建中了,不能再创建了。所有就出现了异常。对照演示代码,来分析一下程序运行过程:


@Service
public class MyServiceImpl implements MyService {

	// 因为关闭了循环依赖,所以此处不能再依赖自己
	// 但是MyService需要创建AOP代理对象
    //@Autowired
    //private MyService myService;
    
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

其大致运行步骤如下:


protected Object doCreateBean( ... ) {

		// earlySingletonExposure = false  也就是Bean都不会提前暴露引用,因此不能被循环依赖

		boolean earlySingletonExposure = (mbd.isSingleton() && 
													this.allowCircularReferences && 
													isSingletonCurrentlyInCreation(beanName));
		...

		populateBean(beanName, mbd, instanceWrapper);

		// 若是开启事务,此处会为原生Bean创建代理对象
		exposedObject = initializeBean(beanName, exposedObject, mbd);

		if (earlySingletonExposure) {
				... 

				// 因为上面没有提前暴露代理对象,所以上面的代理对象exposedObject直接返回。

		}
}

由上面代码可知,即使关闭循环依赖开关,最终缓存到容器中的对象仍旧是代理对象,显然@Autowired给属性赋值的也一定是代理对象。

最后,以AbstractAutoProxyCreator为例看看自动代理创建器实现循环依赖代理对象的细节。

AbstractAutoProxyCreator是抽象类,它的三大实现子类InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator小伙伴们应该比较熟悉,该抽象类实现了创建代理的动作:


// 该类实现了SmartInstantiationAwareBeanPostProcessor接口 ,通过getEarlyBeanReference()方法解决循环引用问题

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

	...

	// 下面两个方法是自动代理创建器创建代理对象的唯二的两个节点:

	// 提前暴露代理对象的引用,在postProcessAfterInitialization之前执行
	// 创建好后放进缓存earlyProxyReferences中,注意此处value是原始Bean

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {

			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			this.earlyProxyReferences.put(cacheKey, bean);
			return wrapIfNecessary(bean, beanName, cacheKey);

	}

	// 因为它会在getEarlyBeanReference之后执行,这个方法最重要的是下面的逻辑判断
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {

		if (bean != null) {

			Object cacheKey = getCacheKey(bean.getClass(), beanName);

			// 下面的remove()方法返回被移除的value,也就是原始Bean
			// 判断如果存在循环引用,也就是执行了上面的getEarlyBeanReference()方法,
		    // 此时remove() 返回值肯定是原始对象
			
		    // 若没有被循环引用,getEarlyBeanReference()不执行
			// 所以remove() 方法返回null,此时进入if执行逻辑,调用创建代理对象方法
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
					return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}

		return bean;

	}
	...
}

根据以上分析可得知,自动代理创建器它保证了代理对象只会被创建一次,而且支持循环依赖的自动注入的依旧是代理对象。由上面分析得出结论,在Spring容器中,不论是否存在循环依赖的情况,甚至关闭Spring容器的循环依赖功能,它对Spring AOP代理的创建流程有影响,但对结果是无影响的。也就是说Spring很好地屏蔽了容器中对象的创建细节,让使用者完全无感知。

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK