通过此文你可以了解Spring:
- refreshContext里的getBean做了什么事情
- BeanPostProcessor中多个拓展点的调用时机
- 动态代理的入口
- 解决循环依赖的机制
- 同时面对循环依赖和多次动态代理的做法
切入问题的代码
为了模拟循环依赖和多次动态代理,使用以下代码,可以看到DevController和TestController是互相依赖的,并且通过@Transactional和@Async来模拟多次动态代理
@Transactional
@RestController
public class DevController {
@Autowired
private TestController testController;
@GetMapping("dev")
@Async
public String dev() {
return "dev";
}
}
复制代码
@Transactional
@RestController
public class TestController {
@Autowired
private DevController devController;
@GetMapping("test")
@Async
public String test() {
return "test";
}
}
复制代码
1.refreshContext里的getBean做了什么事情
Spring作为IOC容器,一眼看上去十分简单,无非就是初始化对象然后放到map里,需要的时候直接从map取。Spring的总体思路确实如此,但细节上面对Factory类、Advisor类、循环依赖、动态代理等各种特殊情况做出处理,引出三级缓存用来处理这些特殊情况。
- singletonObjects:一级缓存,保存完成初始化的单例bean实例
- earlySingletonObjects:保存早期暴露的单例bean实例
- singletonFactories:保存单例bean的工厂函数对象
getBean里面涉及到各级缓存,直接上图
整个getBean可以分为4大部分,分别是getBean、doGetBean、createBean和doCreateBean,如果单例实例已经创建好,就会直接在doGetBean里将实例返回,如果尚未创建,则会调用createBean和doCreateBean的一系列创建过程,可以简单概括为三部分:
- createBeanInstance:利用构造方法等途径new出实例
- populateBean:往实例中注入各个属性
- initializeBean:对实例进行一些后续初始化工作
一些细节的逻辑在上图中已经标示,这里不再赘述。
2.BeanPostProcessor中多个拓展点的调用时机
Spring中提供了若干个BeanPostProcessor接口(下称BPP),BPP提供了在不同的时间点让用户对bean进行自定义调整的机会,大多数都在上图用黄色泡泡特别标示了,有以下几种BPP接口比较常用:
- postProcessMergedBeanDefinition:可对BeanDefinition添加额外的自定义配置
- getEarlyBeanReference:返回早期暴露的bean引用,一个典型的例子是循环依赖时如果有动态代理,需要在此先返回代理实例
- postProcessAfterInstantiation:在populateBean前用户可以手动注入一些属性
- postProcessProperties:对属性进行注入,例如配置文件加密信息在此解密后注入
- postProcessBeforeInitialization:属性注入后的一些额外操作
- postProcessAfterInitialization:实例完成创建的最后一步,这里也是一些BPP进行代理的时机
3.动态代理的入口
Spring的动态代理是通过BPP实现的,其中AbstractAutoProxyCreator是十分典型的自动代理类,它实现了getEarlyBeanReference和postProcessAfterInitialization两个接口都是代理的逻辑,通过earlyProxyReferences缓存避免对同一实例代理两次。
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 将实例放进缓存里,说明已经代理过了
this.earlyProxyReferences.put(cacheKey, bean);
// wrapIfNecessary是根据Advisor的情况对bean实例进行代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 如果实例尚未被代理过,则进行代理
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
// 实例已经被代理过,直接返回
return bean;
}
复制代码
4.解决循环依赖的机制
结合getBean的工作流程,我们来看一下问题中的DevController和TestController是如何解决循环依赖的(假设DevController先于TestController构造)
其中关键步骤就是如下代码,描述了TestController如何从三级缓存中寻找DevController并进行实例化
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 首先从一级缓存中查找
Object singletonObject = this.singletonObjects.get(beanName);
// 一级缓存查不到而且bean实例正在创建,则从二级缓存中查找
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;
}
复制代码
5.同时面对循环依赖和多次动态代理的做法
网上很多文章提到这三级缓存是为了解决循环依赖,但细想一下,面对循环依赖的情况,只需把依赖的bean放进二级缓存就足够了,为何还需要三级缓存呢,我觉得三级缓存是用来解决循环依赖和动态代理同时存在的问题。
可以看到4.解决循环依赖的机制解释了三级缓存singletonFactories面对循环依赖的用法,那么二级缓存earlySingletonObjects则是加上了多次动态代理的时候使用
具体代码在doCreateBean的尾段,个人认为理解了这段代码,就基本上把Spring IOC最晦涩的部分理解清楚了
if (earlySingletonExposure) {
// 从一级缓存和二级缓存中查找bean实例,由于最后一个参数是false,因此不会查找三级缓存
Object earlySingletonReference = getSingleton(beanName, false);
// 在上述的循环依赖下,DevController被@Transactional动态代理,通过AbstractAutoProxyCreator被暴露到二级缓存中
// 此时会进入该分支
if (earlySingletonReference != null) {
// 如果在initializeBean中DevController没有被代理,符合此条件,最终返回被代理后的DevController
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 如果在initializeBean中DevController被代理了(例如@Async的AbstractBeanFactoryAwareAdvisingPostProcessor)
// 那么exposedObject != bean,会尝试下面的判断
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 如果DevController有依赖其他bean,本例中是依赖TestController,此时有两个DevController
// 分别是@Async和@Transactional代理过的,返回哪一个都不对,因此抛出异常
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
复制代码
总结
- Spring提供了多样的BeanPostProcessor供用户介入实例化过程,Spring自身也使用了这些BPP进行动态代理等工作,如果你在开发一些框架功能,熟悉使用BPP能让你事半功倍
- 一个普通孤立bean的实例化十分简单,经过createBeanInstance,populateBean,initializeBean等步骤即完成
- 在populateBean阶段会注入其他依赖的bean,如果该bean尚未创建,会首先执行其实例化
- 如果存在循环依赖,则动用到三级缓存将其提前暴露出来,如果没有循环依赖,则不会提前暴露
- 如果同时存在循环依赖和多次动态代理,可使用@Lazy等方法延迟实例化解决
作者:sabersword
链接:https://juejin.im/post/5dec9fe76fb9a01608236cd3