spring isPrototypeCurrentlyInCreation

这个方法是校验 prototype 类型的 bean 是否存在循环依赖。我们来看一下这个方法

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
	Object curVal = this.prototypesCurrentlyInCreation.get();
	return (curVal != null &&
			(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

大家可能会觉得就这两行代码就能解决原型模式下的循环依赖?大家带着这个疑问看我给大家分析下。
首先说明两个问题。

1、什么是原型模式(prototype)

原型模式其实是相对于单例模式(singleton)来讲的。原型模式的 bean 支持创建重复的对象,所以 spring 容器不会管理原型模式的 bean。

2、spring 为什么不支持原型模式的 bean 的循环依赖

这个问题就需要从 spring 是如何解决单例模式的 bean 的循环依赖说起呢,大家可以查看我之前讲单例模式的 bean 是如何解决循环依赖的。简单来讲就是 spring 支持通过 set 方式的循环依赖,其通过在 bean 完成实例化后提早暴露 bean 的工厂,来使得 bean 在属性设置阶段能够找到其锁依赖的 bean。但是由于 spring 并不管理原型模式的 bean,所以也就无法提早暴露 bean 工厂,也就无法支持其循环依赖。

了解了上面两个问题后,我们再来看下上面的代码。
这里的 prototypesCurrentlyInCreation 是一个 ThreadLocal 类型的引用,之所以是 object 类型是因为其中的类型可能是 String,也可能是 Set(后面会解释为什么是这两种类型)。

为什么使用 ThreadLocal 类型?
这个主要是因为对一个类及其依赖的类的加载是在同一个线程中的。这个怎么理解呢?比如有一个类 StudentA,其通过 set 方式依赖 StudentB,那么在加载 StudentA 时,当实例化完成后进行属性填充时,发现其需要依赖 StudentB,这时候就会去加载 StudentB 这个类,这两个类的加载是在一个线程中完成,所以使用了一个 ThreadLocal 类型。当然你也可以不用这个类型,使用一个 Set 类型完全可以。

那这个 prototypesCurrentlyInCreation 类型的值是在什么时候设置的呢?

doGetBean 这个方法中有这样一段方法

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

这里有一个方法 beforePrototypeCreation,这个方法如下:

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<>(2);
		beanNameSet.add((String) curVal);
		beanNameSet.add(beanName);
		this.prototypesCurrentlyInCreation.set(beanNameSet);
	}
	else {
		Set<String> beanNameSet = (Set<String>) curVal;
		beanNameSet.add(beanName);
	}
}

这段代码应该比较好理解呢。看完这个大家应该清楚为什么我上面说 ThreadLocal 里面的这个 Object 对象可能是 String,也可能是 Set 了吧。

我们通过一个例子来解释下最开始的 isPrototypeCurrentlyInCreation 方法

新建两个通过 set 方式循环依赖的类

package com.test.circuleDepend;

public class StudentA {

    private StudentB studentB;

    public StudentB getStudentB() {
        return studentB;
    }

    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }
}

package com.test.circuleDepend;

public class StudentB {

    private StudentA studentA;

    public StudentA getStudentA() {
        return studentA;
    }

    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
}

增加 xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-autowire="byName">

    <bean id="studentA" class="com.test.circuleDepend.StudentA" scope="prototype">
        <property name="studentB" ref="studentB"></property>
    </bean>
    <bean id="studentB" class="com.test.circuleDepend.StudentB" scope="prototype">
        <property name="studentA" ref="studentA"></property>
    </bean>
</beans>

测试类:

package com.test.springtest;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import java.io.Serializable;

public class App implements Serializable {

    public static void main(String[] args) {
        XmlBeanFactory parentBeanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
        parentBeanFactory.getBean("studentA");
    }
}

我们来分析下,当获取 studentA 对象时,当走到 isPrototypeCurrentlyInCreation 方法时,传入的参数是 studentA,此时 prototypesCurrentlyInCreation 中啥都没有,所以 curVal 为 null,所以这个方法会放回 false。此时在往下就会走到 beforePrototypeCreation,此时会将 studentA 这个字符串设置到 beforePrototypeCreation 中。当创建 studentA 时发现其依赖了 studentB,那么就会去加载 studentB,这时再次来到 isPrototypeCurrentlyInCreation 方法时,此时传入的参数是 studentB,从 prototypesCurrentlyInCreation 获取到的是 studentA 字符串,所以还是返回 false。然后在调用 beforePrototypeCreation 方法,这次就会将 studentAstudentB 放到一个 set 集合中,然后将 set 集合放到 prototypesCurrentlyInCreation 中。在创建 studentB 时发现其依赖了 studentA,那么就会去获取 studentA,因为 spring 是不保存原型模式的 bean 的,所以本次获取还会来到 isPrototypeCurrentlyInCreation 这个方法中,此时传入的就是 studentA。结果显而易见呢,会返回 true,此时程序就会报错。

大家可能会对这个方法中的 curVal.equals(beanName) 会存在疑问,按照上面的分析,这种情况是不存在的啊。大家可能忽略了一点,那就是 StudentA 这个类可以通过 set 方式依赖它自己,也就是如下:

package com.test.circuleDepend;

public class StudentA {
    private StudentA studentA;

    public StudentA getStudentA() {
        return studentA;
    }

    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
}

xml 如下:

<?xml version="1.0" encoding="UTF-8" ?>


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-autowire="byName">

    <bean id="studentA" class="com.test.circuleDepend.StudentA" scope="prototype">
        <property name="studentA" ref="studentA"></property>
    </bean>
</beans>

在这种情况下,就会用到 curVal.equals(beanName) 这个判断呢。

以上是个人的观点,如有问题,欢迎交流。

QQ
在这里插入图片描述

QQ 群
在这里插入图片描述
微信:TY_3268407924

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值