spring源码:循环依赖源码学习

本文目的

spring默认支持单实例的循环引用,简单而言,就是A类中注入了B类,B类中注入了A类,循环依赖的根本问题是就是属性注入;本文主要记录spring支持循环引用的源码学习;即:从源码层面解读,spring是如何支持循环引用

结论

先文本描述一下,spring是如何支持循环依赖的

  1. 我们以A.class和B.class为例
  2. 在spring实例化A的时候,会经过一些列的方法处理,在其中一步:是对A类进行属性注入,在属性注入的时候,发现A类中注入了B类,所以会调用doGetBean()方法,获取B;
  3. 此时,B由于还没有实例化,所以会对B进行实例化,在执行到B的属性注入的时候,会发现,B类中,需要注入A类,所以会从单实例池中尝试获取A;此时,A还没有注入到单实例池中
  4. 但是,在第二步实例化A的时候,会把A类对应的一个半成品(这里说的半成品我们姑且认为就是一个对象,后面解释),放入到二级缓存中;所以,B类在注入A类的时候,实际而言,注入的是这里说的半成品(未执行初始化方法回调);
  5. 此时,B类会完成注入,并完成后续的AOP代理,并注入到单实例池中
  6. 然后A类会接着第二步,注入B,然后再执行后面的初始化方法和AOP动态代理的处理;最后注入到单实例池中,完成了循环依赖

代码

@Component
public class AddressVO {

    @Autowired
    private UserVO userVO;
}


@Component
public class UserVO {

    @Autowired
    private AddressVO addressVO;

    public void test(){
        System.out.println(addressVO.getClass());
    }
}

@Configuration
@ComponentScan("com.springsource.study.reference")
public class AppConfig {
}

public class TestReference {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        ac.getBean(BBean.class).test();
    }
}

源码学习

1、首先说,循环依赖的处理是在refresh()方法中的finishBeanFactoryInitialization(beanFactory);这个方法中完成的;这个方法的作用是:实例化所有的bean,可以这样理解:在spring执行到这一行代码的时候,所有需要注入到spring中的class类,都已经转换成了beanDefinition,并存入到了beanDefinitionMap中;spring在对要注入的类实例化的时候,直接从beanDefinitionMap中根据beanDefinitionName获取数据;具体是在哪一步将所有的class转换为beanDefinition呢?在invokeBeanFactoryPostProcessors(beanFactory);方法中

2、我们先提前列出,spring在实例化的时候,需要用到的几个关键集合和标志位

//在实例化bean的时候,将当前正在被创建的bean加入到singletonsCurrentlyInCreation,这个操作是在beforeSingletonCreation()方法中完成的
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
  
//这是spring单实例池(也就是我们常说的spring容器)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

//我们姑且称为spring的二级缓存,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));在该方法中,会将bean包装为ObjectFactory,存放到map中;这是在spring的一个后置处理器中完成的
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

//暂时称为三级缓存 三级缓存在循坏依赖中,用来防止重复创建
private final Map<String, Object> earlySingletonObjects = new HashMap(16);

allowCircularReferences:默认值为true,表示允许循环引用

关于上面这四个集合,我会在下面说到的时候,一一标明;
这里有一点要说明的是:
我们常说的spring容器:从狭义上来理解,就是指signletonObjects这个map;但是,详细点来讲,应该是由beanDefinition+beanDefinitionMaps+beanFactory+beanFactoryPostprocessor+singletonObjects…等一些列组件的集合组成的,当然,这里仁者见仁智者见智,我是这么理解的

3、我们下面要说的源码都在finishBeanFactoryInitialization()这个方法中;
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
这是spring在实例化bean的时候,首先进入的方法(前面的校验以及mergeBean,就跳过了,不是本篇文章的重点)
在doGetBean()方法中,我们要说的第一个方法是:
Object sharedInstance = getSingleton(beanName);

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			////这里只需要从二级缓存中拿一次就行,如果没有二级缓存,每次进来都需要从二级缓存get一次,影响效率
			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;
}
在第一次初始化AddressVO,走到这个方法的时候,这里返回的肯定是null,因为在第一次创建的时候,单实例池、二级缓存、三级缓存中都是null;这里先这样理解,在后面还会介绍这个方法;在这里返回null之后,spring就会继续往下进行创建初始化的流程;

4、在初始化bean的时候,没有从单实例池、二级缓存、三级缓存中获取到bean,就会继续执行后面的初始化逻辑,在doGetBean方法中,另一个要说的代码是:

if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, () -> {
		try {
			return createBean(beanName, mbd, args);
		}
		catch (BeansException ex) {
			destroySingleton(beanName);
			throw ex;
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

这个方法是开始执行spring生命周期中后置处理器的入口,其中,在getSingleton方法中,有一个beforeSingletonCreation(beanName);的调用,在这里,将当前bean添加到了singletonsCurrentlyInCreation;然后spring开始实例化bean对象;

5、在实例化bean对象的过程中,会调用到spring中第四个后置处理器

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			/**
			 * mpy 第四次调用后置处理器 获取一个提前暴露的对象 objectFactory 用来解决循环依赖
			 * 这里还有一个关键的作用,可以追进去看一下,这里可能会完成动态代理对象的生成(AOP)
			 * 正常情况下,AOP的动态代理是在调用org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)的时候,才会生成
			 * 但是,如果是循环依赖的话,会在 org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(java.lang.Object, java.lang.String) 中完成
			 * 需要注意:getEarlyBeanReference()是在doGetBean中,从二级缓存中获取半成品对象的时候,才会执行:singletonFactory.getObject();
			 */
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

这里则是:将我们上面所说的半成品的AddressVO添加到二级缓存中的代码逻辑;这里的getEarlyBeanReference()实际是调用了后置处理器,如果当前bean需要被生成动态代理(AOP),就会在这一步提前生成代理对象(这里需要说明的是:正常的逻辑中,执行完这行代码之后,会进行属性注入、再执行bean的初始化方法回调、然后再执行aop动态代理的生成;但是如果是允许循环依赖的话,这里会提前调用AOP的动态代理,将生成的代理对象添加到二级缓存)

6、在添加到二级缓存之后,就会执行下一步,属性注入;在属性注入的时候,会发现,需要注入UserVO;会再次调用到第三步中的doGetBean()方法,这时,由于UserVO是第一次初始化,所以,会执行一般上面所说的第四步、第五步的代码;

7、关键步骤就是在UserVO属性注入的时候,发现需要注入AddresVO,这时会调用doGetBean();方法尝试获取AddressVO对象;这时,和第一次调用doGetBean()获取addressVO对象不同的一点是:现在的二级缓存中,有addressVO的半成品对象;所以,这时候,UserVO会注入从二级缓存中取得的AddressVO对象,userVO继续完成后面的初始化代码;初始化完毕之后,AddressVO再注入UserVO

半成品的bean:
我们上面说到的半成品的bean:我的理解是这样的;在执行完addSingletonFactory代码,到最后完成bean的实例化,中间还有三个步骤,分别是:属性注入、初始化方法回调和aop动态代理的生成;其中循环依赖,就是在属性注入这个操作中完成的,所以,就只有两步,初始化方法回调和aop的动态代理
所以在循环依赖,提前注入bean的时候,需要确保bean是一个有效的,这里的意思是:如果A依赖B,B依赖了A;在往B中注入A的时候,必须确保A是一个接近完整的bean;举例:A在被注入的时候,A还阻塞在属性注入的这行代码;没有执行初始化方法回调,其实影响不大,因为在初始化方法回调中,基本是做一些业务代码的预处理;但是如果B在被注入到A类中的时候,还没有生成代理对象,那这样注入有什么意义呢?所以:spring的操作是:将动态代理的生成这一步,提前到了添加到二级缓存这一步,也就是说:如果是循环依赖的话,是先生成AOP动态代理,再进行属性注入,再进行初始化方法回调;初始化方法回调之后,AOP动态代理的处理方法中,有一个判断:如果当前需要生成代理对象的bean,已经生成了代理对象,就不会再处理
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		/**
		 * 下面这行代码的意思是:earlyProxyReferences这个set集合中,如果当前key是在循环依赖处理的后置处理器中完成了aop的动态代理,就无需再次代理了
		 */
		if (!this.earlyProxyReferences.contains(cacheKey)) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

这个代码,是生成aop动态代理的入口;earlyProxyReferences这个set集合,就是在调用二级缓存的getObject()方法时,会提前生成代理对象,然后,将当前bean添加到了set集合中;所以,如果bean在循环依赖的过程中,提前生成了代理对象,在调用完初始化方法回调之后,就不会再次调用生成代理对象的代码

总结
所以我们可以得知:

  1. 在依赖注入的时候,如果A注入了B,B注入了A;在A实例化的过程中,会将提前生成动态代理的代码,放到二级缓存中
  2. 在A属性注入的时候,会去实例化B,同理:将B提前生成动态代理的代码,放到了二级缓存中;B在属性注入的时候,会依赖A
  3. 此时A还未完成实例化过程,实例化A的代码还在属性注入这里,等待B的注入;那这时,B在注入A的时候,会从二级缓存中,获取到A提前生成动态代理的代码,将A生成一个代理对象,注入到B中;B接着完成初始化方法回调和动态代理的生成
  4. 在B放到单实例池之后,A接着属性注入的代码往后执行,在执行到生成动态代理这步时,由于已经生成了代理对象,会跳过;完成A的实例化

你可能感兴趣的:(spring源码)