看现象

maven依赖

我们只测试IOC容器,因此只需要引入spring-context即可

    <dependencies>
        <!--测试框架-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!--假数据声场-->
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>1.0.2</version>
        </dependency>

        <!--IOC-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>RELEASE</version>
        </dependency>
        
    </dependencies>
  

测试用例

public class TestSpringIOCCircularDependency {

    @Test
    public void test(){
        // create and configure beans
        ApplicationContext context = new ClassPathXmlApplicationContext("ioc/circularDependency.xml");

        // retrieve configured instance
        CircleA circleA = context.getBean("circleA", CircleA.class);
        circleA.test();

        CircleB circleB = context.getBean("circleB", CircleB.class);
        circleB.test();

    }
}

测试用例里面引入了配置文件:circularDependency.xml,看看里面写了啥

  • circularDependency.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--来一个普通bean,看看普通bean与循环引用的bean构建过程有什么不同-->
    <bean id="userDao" class="com.spring.dao.impl.UserDaoImpl"/>

    <!--A依赖于B,B依赖于A,形成循环依赖-->
    <bean id="circleA" class="com.spring.service.CircleA">
        <property name="circleB" ref="circleB"/>
    </bean>

    <bean id="circleB" class="com.spring.service.CircleB">
        <property name="circleA" ref="circleA"/>
    </bean>

</beans>
  • CircleA

/**
 * A依赖B
 */
public class CircleA {
    static {
        System.out.println("I AM CircleA , I am loaded now @" + System.currentTimeMillis());
    }

    /*注入circleB*/
    private CircleB circleB;
    public void setCircleB(CircleB circleB) {
        this.circleB = circleB;
    }

    public void test(){
        System.out.println("I am CircleA , I have an circleB@" + circleB.hashCode());
    }
}
  • CircleB
/**
 * B依赖A
 */
public class CircleB {
    static {
        System.out.println("I AM CircleB , I am loaded now @" + System.currentTimeMillis());
    }

    /*注入circleA*/
    private CircleA circleA;
    public void setCircleA(CircleA circleA) {
        this.circleA = circleA;
    }
    public void test(){
        System.out.println("I am CircleB , I have an circleA@" + circleA.hashCode());
    }
}

运行结果

I AM CircleA , I am loaded now @1615430548511
I AM CircleB , I am loaded now @1615430548512
I am CircleA , I have an circleB@428910174
I am CircleB , I have an circleA@1682463303

思考

相信你一眼就能看出来CircleA依赖CircleB,而CircleB创建的时候又要依赖于CircleA,这不就是典型的循环依赖么?但是Spring在创建的时候并不会发生死循环,它又是怎么处理的?

Spring IOC如何解决循环依赖的问题?

创建过程

普通对象的创建

一上来先调用addSingletonFactory扔到三级缓存里面,创建完成后调用addSingleton转移到一级缓存里面,普通无循环依赖的对象就算创建完毕了

循环依赖对象的创建

  • A 创建过程中需要 B,于是将A自己放到三级缓存里面 ,去实例化 B(在实例化B的时候,也会把B放到三级缓存里面)
  • B 实例化的时候发现需要 A,于是调用getSingleton方法,先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了!(也就是说A发现创建自己需要依赖自己)
    • 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A
    • B 顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
  • 然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成A的创建,清空三级缓存,并将自己放到一级缓存里面,如此一来便解决了循环依赖的问题

总而言之,通过getSingleton方法,会检测到创建一个bean对象是否在某种依赖深度上需要依赖自己,如果依赖(即三级缓存里面有自己)那就是循环引用

三级缓存

四哥们儿,三个map一个set,在类DefaultSingletonBeanRegistry中定义的:

  • singletonObjects:单例缓存,也叫第一级缓存
  • earlySingletonObjects:早期单例缓存(啥叫早期?就是说这个对象里面的属性还没有被填充的),也叫第二级缓存
  • singletonFactories:单例工厂缓存,也叫第三级缓存
  • singletonsCurrentlyInCreation:是个set,用于帮助判断当前bean是否在创建中,后文有讲
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

三个方法

这几个方法保证了一个bean在三级缓存中只能在一个缓存中存在,并且都加了锁是线程安全的,这几个缓存表示了bean创建的不同状态,也是在类DefaultSingletonBeanRegistry中定义的。

  • 添加单例(一级缓存):
    确保只有一级缓存才有!此时说明单列已经创建完毕,里面的属性也填充好了,往singletonObjects单列缓存里面加东西,其他的缓存全部清掉,registeredSingletons当然也要加上,注意是set,多次添加也无妨
protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}
  • 添加单例工厂缓存(三级缓存):
    确保只有三级缓存才有!在单列缓存singletonObjects没有的时候才能添加,如果bena都全部初始化好了,那没有必要再往单例工厂缓存里面添加了
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);
        }
    }
}

并且在下面这种条件下添加,即要创建的bena是单列,并且允许循环引用,并且当前对象正在创建中...,allowCircularReferences参照AbstractAutowireCapableBeanFactory.allowCircularReferences = true

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
  • 拿到单列
    三级缓存转移到二级缓存!重点来了,如果是循环引用,此处有一个三级缓存转移到二级缓存的过程
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    //在一级缓存没拿到,说明bean还没创建好并且当前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;
}

一个标志位

这个方法DefaultSingletonBeanRegistry.isSingletonCurrentlyInCreation返回当前传入对象是否正在创建,那么当在什么时候在创建中?什么时候创建完毕呢?

下面的方法都在DefaultSingletonBeanRegistry中定义的

public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
}

singletonsCurrentlyInCreation这玩意定义是下面这样子的,就是个set,往里面放的就是在创建中的,移出去的就是创建完成来的,那么我们只需要找在一个bean创建过程当中什么时候往里面放入了,什么时候又从里面移除掉了

为啥这个set要定义成这样,new个set不好?这里肯定是为了线程安全,是从ConcurrentHashMap转换成set的

/** Names of beans that are currently in creation. */ 
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

在这里放入的,在加入三级缓存之前就放入了

protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

在这里移除的

protected void afterSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}