这个方法是校验 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
方法,这次就会将 studentA
和 studentB
放到一个 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