@
前言
关于Spring 循环引用 网上的分析文章很多,写的水平良莠不齐,虽然看完了 知道怎么个回事 但是过段时间还是忘记了,主要本人没过目不忘的本领哈,但是只要记住主要的点就好了
但是如果你自己想更深入的了解,还是要自己去看源码分析一波,因为别人分析的时候,有些知识点你是get不到的,只有当自己走进源码去看的时候,才有get到更多的!比如网上很多文章都分析Springs是怎么解决循环依赖的 但是为什么只有单类的才可以,Prototype的就不行呢,在哪里不行,或者说构造器的注入为什么也不可以,最后如果解决循环依赖,或者说 怎么去换中写法去解决问题。
这句话献给正在读文章的你,看完记得点赞,还有就是自己去下载Spring 源码 去看看
正文
OK,进入正文,当然上面也不是废话啦,Spring 的循环引用 我想读者们应该知道,不知道的话,算了 来个code把!
@Component
public class CycleTestServiceA {
private CycleTestServiceB b;
public void setB(CycleTestServiceB b) {
this.b = b;
}
}
@Component
public class CycleTestServiceB {
private CycleTestServiceA a;
public void setA(CycleTestServiceA a) {
this.a = a;
}
}
上面的 代码 就是一个普通的set注入的方式,A里面依赖B,B里面依赖A,这样就导致了循环依赖,Component默认是Singleton的
分析
我们从Spring Beanc创建开始作为入口,在Spring IoC 容器中一个完整的Bean 要进过实例化 和初始化的阶段
Spring Bean 实例化就getBean的过程
那我们接进入源码去看下getBean的过程
doGetBean
getBean方法时 BeanFactory 接口的方法 他的实现类有很多,我们跟进去他的抽象实现类org/springframework/beans/factory/support/AbstractBeanFactory.java 类,其实都是调用了doGetBean方法
下面是我截取的核心代码
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
/*
* 检测是否 有缓存对象 这个方法时处理循环依赖的关键入口
* 记住这个的代码 我还会回来的
* */
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
/*
*Prototype bean 是否在创建当中 如果存在 说明产生了循环依赖 处理Bean 循环依赖的地方
*这个地方就是为什么Scope 是Prototype的时候 会报循环依赖的错误,慢慢看 后面会解释
* */
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
...
if (!typeCheckOnly) {
markBeanAsCreated(beanName);//这个方法就是把当前的bean 加入到alreadyCreated的set集合中 后面有些判断需要
}
try {
...
/*
* 获取Bean 的依赖项 这边的依赖 是我们在xml 有时候可以配置的depends-on的依赖 和我们本次讲的循环依赖不是同一个
* 我特别说明下
* */
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
//注册依赖 创建Bean 等
}
}
/*
* 如果是单列 创建createBean 记住这个的代码 我还会回来的
* */
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
...
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
/*
* Prototype对象
* */
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
/*
* 不是Singleton也不是Prototype,可能是自定义scope的对象
* */
else {
...
}
}
}
...
return (T) bean;
}
上面是dogetBean()的核心方法
为什么Prototype不可以
带着这个问题 我们可以从上面的代码中 看下 Spring在处理么Prototype的时候 有2个方法beforePrototypeCreation(),afterPrototypeCreation(),
上下代码
/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<Object>("Prototype beans currently in creation");
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<String>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
this.prototypesCurrentlyInCreation.remove();
}
else if (curVal instanceof Set) {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}
上面的代码 我相信小伙伴都能看的懂,就是用一个set集合存储当前正在创建的Bean的BeanName,而且是用ThreadLocal去存储Set集合的 ThreadLocal是每个线程私有的。看到这个 我们再把目光往代码上面看一看 isPrototypeCurrentlyInCreation这个方法的判断
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
看到了么 这边就是用这个ThreadLocal里面的set集合去判断的,为什么用ThreadLocal想下,你想呀,A依赖B,而B依赖A,AB都是Prototype的,A创建的时候 A会加入到这个set集合中,然后A去填充实例的时候,因为要依赖B,所以去getB,发现B又依赖A,这个时候有要getA,你看 当执行到 最上面的判断isPrototypeCurrentlyInCreation的时候,是不报了循环引用的错,因为A已经在prototypesCurrentlyInCreation的Set集合中了,因为整个流程一定是一个线程走下去的,所以存入ThreadLocal中,一点问题没有,而且还不受其他线程影响~
createBean
不管是哪种Scope 都是要调用createBean方法的,我们跟进去代码 发现唯一重写的实现在org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java 中
我们进入代码看下
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
...
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 该函数的作用是给 BeanPostProcessors 后置处理器返回一个代理对象的机会
// 这里是实现AOP处理的重要地方
// AOP是通过BeanPostProcessor机制实现的,而接口InstantiationAwareBeanPostProcessor是实现代理的重点
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
...
/*
* 后置处理器 没有返回有效的bean 就创建
* */
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
这边 我看到一句英文注释,都没舍得替换中文,Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. 哈哈 给后置处理器一个返回代理bean的机会,这边就是Spring 中实现AOP的重点,动态代理 其实就是使用后置处理器 替换了target Bean 的实例,从而达到代理的作用,这个以后聊到AOP 的时候在慢慢聊吧!这个最核心的代码还在再doCreateBean中,继续跟进
doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;//BeanWrapper 是Bean 的包装类 方便对Bean 实例的操作
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
mbd.resolvedTargetType = beanType;
....
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));//满足三个条件 单列 运行循环引用 bean 是否正在创建中
if (earlySingletonExposure) {
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);//提前暴露引用 获取早期的引用
}
});
}
// Initialize the bean instance. 初始化Bean 实例
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);//填充Bean
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);//执行初始化Bean里面的方法
}
}
...
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
/*
*这边其实还是做了一个判断,exposedObject是经过了 initializeBean方法方法的
*而bean还是那个提前暴露的Bean,
*为什么要做这个判断你,是因为exposedObject经过了initializeBean里面的后置处理器的修改 可能Object 已经改变了
**/
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
/*
*有兴趣的可以根据到上面的每一个方法看下 ,这边就是判断如果提前暴露的bean已经和在后置处理器里面修改了并且不一样了,就抛出异常,因为提前暴露的Bean 可能作为了另外的bean的依赖 这样就会导致单类的bean在容器中有2个实例的出现,这是非法的!
*/
if (!actualDependentBeans.isEmpty()) {
//抛出一个异常 由于很多文字我就删掉了
}
}
}
}
...
return exposedObject;
}
earlySingletonExposure这个主要关注的是earlySingletonExposure 这边的代码,这个就是在Bean 实例化完成后,开始填充属性之间发的代码
earlySingletonExposure为true 要满足三个条件
- 单类
- 允许循环引用
- 当时单类正在创建中
前面2个可以理解 那最后一个又是什么呢?话不多说 进入方法看下
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
这个方法 一看很简答 就是判断当前的bean是否在singletonsCurrentlyInCreation的set集合中 那这个集合又是什么时候加入的呢?带着这个想法 我又重头扫描了一篇代码 还记的org/springframework/beans/factory/support/AbstractBeanFactory.java代码中的doGetBean()方法里面Singleton的Bean 在创建Instance的时候是调用了getSingleton方法么,不清楚的话 可以往上看下
getEarlyBeanReference
这个方法 是 addSingletonFactory 方法 构建ObjectFactory的参数的时候 里面返回使用方法
看下代码:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
里面最主要的就是看下getEarlyBeanReference方法 这个方法时SmartInstantiationAwareBeanPostProcessor里面的方法,他的实现有2个 一个是org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java 还有一个是动态代理使用的 我就不列举了,
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
看了下 其实InstantiationAwareBeanPostProcessorAdapter的重写就是 返回了当前的bean 没有做任何操作。这边其实就是做了一个引用的保存。
getSingleton
代码位于org/springframework/beans/factory/support/DefaultListableBeanFactory.java中
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
...
beforeSingletonCreation(beanName);
boolean newSingleton = false;
...
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
这个方法其所就是SingletonBean的核心创建流程
beforeSingletonCreation
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
当我看到这个singletonsCurrentlyInCreation.add的时候 我很欣慰 因为我之前的问题解决了 就是这个方法把Bean 放入到之前的singletonsCurrentlyInCreation的集合中的
singletonFactory.getObject
这个应该都很清楚了 就是我们方法传入的匿名的ObjectFactory对象,当之前getObject的时候 才会执行我们刚才的看的createBean方法
afterSingletonCreation
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
看下afterSingletonCreation方法里面的东西也很简单,就是从singletonsCurrentlyInCreation集合中移除
addSingleton
先看下代码
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
看到 这边 就不得表介绍下三级缓存了
- singletonObjects 实例化 初始化都完成的bean 缓存
- earlySingletonObjects 可提前引用的 Bean 缓存,这里面的Bean 是一个非完整的bean,属性填充 后置处理器都未执行的bean
- singletonFactories 单类bean的创建工厂函数对象
说道这里 我们就清楚了 这个方法其所就是在二级缓存和三级缓存中删除当前的Bean,把当前的Bean 放入到一级缓存中,因为到了这一步 bean 的实例化,属性填充,后置处理器执行,初始化等方法都已经执行了。
addSingletonFactory
这个方法 哪里用的呢 那我们有要回到上面的代码doCreateBean中 当earlySingletonExposure为true的时候 会调用这个方法addSingletonFactory
这个方法就是 当前的Bean可以提前引用的话执行的方法
看下代码也很简答
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
看下这个方法 说白了就是往三级缓存里面存放bean的ObjectFactory对象 这个地方也是处理循环引用的关键,这个时候Bean 刚刚进行了实例化 还没有进行bean的属性填充和初始化等一些列方法
那怎么去解决提前引用的呢?可以看下ObjectFactory返回的是getEarlyBeanReference对象
getSingleton(beanName)
这个方法是在doGetBean方法中 从缓存中获取Bean 对象的方法 这个方法很关键 是处理循环依赖的入口,那我们跟进去看下方法
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);
}
最终调用的方法如上,allowEarlyReference是为true的,我们还是用最上面的ServiceA和ServiceB 为例,第一次ServiceA 进入的时候 是没法进入下面的判断的 应为当前ServiceA不在SingletonCurrentlyInCreation中,但是当第二次进来,第二次是什么时候呢,就是在填充ServiceB的时候 需要依赖 ServiceB,这个时候ServiceB也要执行getBean的流程,发现又依赖ServiceA,这个时候 ServiceA就是在SingletonCurrentlyInCreation的集合中了,而且在三级缓存中,这个时候会进行判断条件里面的方法,先找一级缓存,找不到就找二级缓存,最后找三级缓存,然后将取出三级缓存里面的ObjectFactory执行getObject方法 就是获取我们上面提到的提前引用的bean,最后将bean 放入到二级缓存,从三级缓存中移除~
核心说明
看完了 上面的一推 也许很懵逼,可能也是我文字组织能力差,只能以后慢慢改变
缓存的说明
上面涉及到几个缓存 我在边在重写描述一下
执行流程图
最终我还是用一个方法执行的流程图 来描述下 循环依赖的处理
构造器的注入解决
那么为什么构造器的注入方式不行呢?原因是因为 Bean在实例化阶段的时候createBeanInstance的时候就会去创建依赖的B,这样的话A根本就走不到提前暴露的代码块,所以会报一个循环引用的错误,报错的地方就是构造函数参数bean 创建的地方,自己可以写个demo,调试下 在哪一步报错,博主可是看了半天 才找到,哈哈!
解决方法
关于如果解决构造器的循环注入
https://www.baeldung.com/circular-dependencies-in-spring
这是一篇外国博文,小伙伴们可以看下
- 使用懒加载
- 修改使用setter注入的方式
- 使用PostConstruct注解
- InitializingBean 后置处理器的方式
总结
Spring 处理循环依赖的核心就是 三级缓存,让Bean 提前暴露出来,可以提前引用,让互相依赖的Bean 可以流程上执行下去,从而解决了循环依赖的问题