参考资料:

《Spring如何解决循环依赖问题》

《Spring 系列教程之 bean 的加载》

前文:

《Spring IOC:finishBeanFactoryInitialization调用链》

《Spring IOC:getBean调用链》

《Spring IoC源码学习:createBean调用链》

目录

一、循环依赖的介绍

二、三层缓存详解

        1、singletonObjects

        2、earlySingletonObjects

        3、singletonFactories

补充

1、Spring不能解决构造器的循环依赖

2、Spring不能解决多例的循环依赖

3、其它循环依赖如何解决

        (1)生成代理对象产生的循环依赖

        (2)构造器循环依赖


写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。

一、循环依赖的介绍

        循环依赖就是循环引用,即两个或多个 bean 相互之间的持有对方,比如 TestA引用 TestB,TestB引用 TestC, TestC引用 TestA,则它们最终反映为一个环。(这里讲的循环引用只的是属性引用,其他类型的循环引用会在后续介绍)

spring中一个单例类中调用了一个scope为request的属性会报错吗 spring怎么解决单例问题_sed

public class TestA {
    private TestB testB;
    public void a() {
        testB.b();
    }
    public TestB getTestB() {
        return testB;
    }
    public void setTestB(TestB testB) {
        this.testB = testB;
    }
}

public class TestB {
    private TestC testC;
    public void b() {
        testC.c();
    }
    public TestC getTestC() {
        return testC;
    }
    public void setTestC(TestC testC) {
        this.testC = testC;
    }
}

public class TestC {
    private TestA testA;
    public void c() {
        testA.a();
    }
    public TestA getTestA() {
        return testA;
    }
    public void setTestA(TestA testA) {
        this.testA = testA;
    }
}

        对于 setter 注入造成的依赖是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的 bean来完成的,而且只能解决单例作用域的 bean循环依依赖。通过提前暴露一个单例工厂方法,从而使其他 bean 能引用到该 bean 。

   

二、三层缓存详解

         三层缓存分别如下

// 第一层缓存(singletonObjects):单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象;
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
 
// 第二层缓存(earlySingletonObjects):单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象;
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

// 第三层缓存(singletonFactories): 单例工厂的缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

        1、singletonObjects

        我们从bean实例化的核心方法doGetBean方法开始看起。源码分析可以看我之前写的《Spring IOC:getBean调用链》

spring中一个单例类中调用了一个scope为request的属性会报错吗 spring怎么解决单例问题_缓存_02

        1、在doCreate方法中,调用getSingleton(String beanName, ObjectFactory<?> singletonFactory)

        2、而在该getSingleton方法内部,通过重写的singletonFactory.getObject方法创建的bean实例化及属性注入等操作。

在getObject方法将bean创建出来后,调用addSingleton将这个完整的bean放入第一级缓存中,并从二、三层缓存中删除。

我们来看addSingleton的源码,这里将创建好的对象添加到各级缓存中,单例对象缓存singletonObjects、已注册的单例对象缓存registeredSingletons,并从已过期缓存中清除,单例工厂缓存singletonFactories、早期单例对象缓存earlySingletonObjects。

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 1.添加到单例对象缓存
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
        // 2.将单例工厂缓存移除(已经不需要)
        this.singletonFactories.remove(beanName);
        // 3.将早期单例对象缓存移除(已经不需要)
        this.earlySingletonObjects.remove(beanName);
        // 4.添加到已经注册的单例对象缓存
        this.registeredSingletons.add(beanName);
    }
}

        

        2、earlySingletonObjects

            earlySingletonObjects是在getSingleton方法中产生的,该方法非常重要,是三层缓存解决循环依赖的核心代码之一,我们首先看下这段代码在哪里有用到。

        首先是通过beanname判断该bean是否为工厂类的方法isFactoryBean。

// AbstractBeanFactory.java
@Override
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
    // 拿到真正的beanName(去掉&前缀、解析别名)
    String beanName = transformedBeanName(name);
 
    // 尝试从缓存获取Bean实例对象
    Object beanInstance = getSingleton(beanName, false);
    // 省略
}

         然后是bean实例的核心方法doGetBean。

protected <T> T doGetBean(
        final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
        throws BeansException {
    // 1.解析beanName,主要是解析别名、去掉FactoryBean的前缀“&”
    final String beanName = transformedBeanName(name);
    Object bean;
 
    // Eagerly check singleton cache for manually registered singletons.
    // 2.尝试从缓存中获取beanName对应的实例
    Object sharedInstance = getSingleton(beanName);
    // 省略
}

        我们发现,getSingleton的作用就在于读取缓存,接下来我们就通过分析源码来看看它到底是怎么读取缓存的。

        (1)首先会尝试从singletonObjects缓存中获取bean实例,singletonObjects缓存我们可以从上中已经知道了这是用来存储已经创建好了的实例的,这里的创建完成是指实例化+属性注入全部完成的对象。

        (2)如果没有,表明该实例还没有创建完,那么则尝试从第二级缓存earlySingletonObjects中获取,在上文中我们介绍了earlySingletonObjects是指已经实例化,但还没有属性注入完成的对象。

        (3)如果还没有,那我们转而去第三级缓存singletonFactory 中查找该bean所对应的ObjectFactory,前两个缓存中存储的都是bean的对象(只是阶段不同,一个是完全体,一个是半成品),但这里存储的则是bean实例所对应的工厂类ObjectFactory(该类为Spring为解决循环依赖所创建的,不是我们预先设置的工厂类)。

在获取到该bean所对应的ObjectFactory后,调用其getObject()方法,获取到半成品的bean实例,并置入第二级缓存earlySingletonObjects中,然后再将该ObjectFactory从singletonFactory中移除(这是因为整个IOC所控制的都只有单例bean,当唯一的bean被创建出来后,创建该bean的工厂自然也不需要了,后续都可以从第二级缓存中获取该bean了)。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从单例对象缓存中获取beanName对应的单例对象
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果单例对象缓存中没有,并且该beanName对应的单例bean正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加锁进行操作
        synchronized (this.singletonObjects) {
            // 从早期单例对象缓存中获取单例对象(之所称成为早期单例对象,是因为earlySingletonObjects里
            // 的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作)
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用
            if (singletonObject == null && allowEarlyReference) {
                // 从单例工厂缓存中获取beanName的单例工厂
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 如果存在单例对象工厂,则通过工厂创建一个单例对象
                    singletonObject = singletonFactory.getObject();
                    // 将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存了,
                    // 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects缓存拿到,不需要在用到该单例工厂
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    // 返回单例对象
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
 
public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
}

经过上面的分析,我们了解到,在IOC尝试获取bean缓存时,会优先从第一、二层缓存中获取,如果都没有,会判断有无第三层缓存,有的话则利用第三层缓存的singletonFactory来创建一个bean,并放入第二层缓存中。接下来,我们着重看下第三层缓存的产生。

        3、singletonFactories

        让我们再回到bean的创建过程,在doCreateBean方法中,创建完bean实例后,会调用addSingletonFactory方法,并重写getObject方法。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
    
    // 创建bean实例
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    // 判断是否需要提早曝光实例:单例 && 允许循环依赖 && 当前bean正在创建中
    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");
        }
 
        // 提前曝光beanName的ObjectFactory,用于解决循环引用
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                // 应用后置处理器SmartInstantiationAwareBeanPostProcessor,允许返回指定bean的早期引用,若没有则直接返回bean
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    } 
    // 省略

}

         addSingletonFactory并不复杂,就是将该bean及其重写好的getObject方法置入第三层缓存中。我们再回到上面重写了的getObject方法。

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);
			}
		}
	}

         getObject实际上是由getEarlyBeanReference完成的,其内部会对传入的bean进行后置处理并返回。

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 exposedObject;
                }
            }
        }
    }
    // 回传经过了后置处理的bean
    return exposedObject;
}

        到这里,我们确认了,第三层缓存中getObject获取的就上就是将刚刚实例化,还未注入属性的bean。

        综合以上三层缓存,我们可以整理吃完整的循环依赖解决过程。

        (1)在没有创建好的TestA(即第一层缓存)时,正常创建这个bean,在实例化后将其加入到第三层缓存中(因为此时bean还未创建完成,需要与第一层缓存区分开),供其他bean属性注入时使用。

        (2)TestA在属性注入时发现要注入的TestB同样未生成,Spring容器便去进行创建。经过同样的过程后。发现需要获取TestA。在获取缓存时,发现存在第三层缓存,便通过TestA的getObject方法获取到第二层缓存(即第1点中只完成了实例化过程的TestA)。

        (3)TestB在拿到了TestA(虽然只是半成品)后顺利完成了属性注入,成功加入到第一层缓存中。再返回头交付给第一次生成的TestA,于是TestA也创建完成,加入第一层缓存,同时删除第二、三层缓存,整个过程结束。

spring中一个单例类中调用了一个scope为request的属性会报错吗 spring怎么解决单例问题_实例化_03

补充

1、Spring不能解决构造器的循环依赖

        构造器注入形成的循环依赖: 也就是TestA需要在TestB的构造函数中完成初始化,TestB也需要在TestA的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖无法解决。

        Spring解决循环依赖主要是依赖三级缓存,但是的在调用构造方法之前还未将其放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的Bean,因此不能解决。

2、Spring不能解决多例的循环依赖

        多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。

3、其它循环依赖如何解决

        三级缓存无法解决以上类型的循环依赖,但不代表我们就没有别的办法。

        (1)生成代理对象产生的循环依赖

        解决方法主要有

  • 使用@Lazy注解,延迟加载
  • 使用@DependsOn注解,指定加载先后关系
  • 修改文件名称,改变循环依赖类的加载顺序

        (2)构造器循环依赖

        通过使用@Lazy注解解决。

spring中一个单例类中调用了一个scope为request的属性会报错吗 spring怎么解决单例问题_spring_04

        虽然Spring为我们提供了一些方法来应对循环依赖,但也不是所有的依赖都有解决方法,例如@DependsOn产生的循环依赖和多例依赖,只能通过修改bean的定义来解决。最后,需要强调的时,在工作中,能避免循环依赖就不要去产生他,这才是最好的解决方案。