spring默认支持单实例的循环引用,简单而言,就是A类中注入了B类,B类中注入了A类,循环依赖的根本问题是就是属性注入;本文主要记录spring支持循环引用的源码学习;即:从源码层面解读,spring是如何支持循环引用
先文本描述一下,spring是如何支持循环依赖的
@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在循环依赖的过程中,提前生成了代理对象,在调用完初始化方法回调之后,就不会再次调用生成代理对象的代码
总结
所以我们可以得知: