spring-ioc的循环依赖,环境如下我们测试3个对象循环依赖
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
default-autowire="byType">
<context:component-scan base-package="org.springframework.work2"></context:component-scan>
<bean id="eService" class="org.springframework.work2.service.EService">
</bean>
<bean id="tService" class="org.springframework.work2.service.TService">
</bean>
<bean id="uService" class="org.springframework.work2.service.UService">
</bean>
</beans>
package org.springframework.work2.service;
import org.springframework.stereotype.Component;
public class EService {
TService tService;
public EService(){
System.out.println(" eService object init");
}
public void settService(TService tService) {
this.tService = tService;
}
}
package org.springframework.work2.service;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
public class TService{
UService uService;
public TService(){
System.out.println(" tService object init");
}
public void setuService(UService uService) {
this.uService = uService;
}
}
package org.springframework.work2.service;
import org.springframework.stereotype.Component;
public class UService {
EService eService;
public UService(){
System.out.println(" uService object init");
}
public void seteService(EService eService) {
this.eService = eService;
}
}
package org.springframework.work2;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.work2.app.AppConfig;
import org.springframework.work2.service.EService;
import org.springframework.work2.service.TService;
import org.springframework.work2.service.UService;
public class Main {
public static void main(String[] args) {
//AnnotationConfigApplicationContext cxc = new AnnotationConfigApplicationContext(AppConfig.class);
ClassPathXmlApplicationContext cxc = new ClassPathXmlApplicationContext("classpath:spring2.xml");
System.out.println(cxc.getBean("eService"));
}
}
打印结果如下
打印结果应该属于意料之中了。
那么我们对spring创建一个bean的大致流程可以宏观的认为是class--初始化对象--填充属性(即依赖)--剩下的生命周期
下面我们就开始一起探讨spring是如何解决循环依赖的问题。
我们先看下一下调用链,可以跟着我的断点一起调试。注意第一轮断点走完记得取消设置或者重新设置断点条件,不然你就掉坑里起不来了
第一个if (isFactoryBean(beanName))不会进,因为这个对象不是一个FactoryBean,所以会进else 的 getBean 方法
BeanFactory 与 FactoryBean的区别
我们先重点看进去看getSingleton这个方法。设置好eService的断点进去,这个true就是默认支持循环依赖的
这里有3个map我们先看一看
第一个map里面有10个对象,这是spring内部自己root进去的。当前时刻是没有我们自己编写的对象在里面的。
所以第一次getSingleton返回为空,另外两个map也是空的。如果没有取到这个对象,那么毫无疑问spring就会去创建这个对象
第321行先断点进去
然后在225行直接点进入方法的方式可以回到323行的断点(不知道什么原因前三轮测试走这一行代码以下的逻辑都不会执行,而且直接返回了)
继续,开始创建对象(通过判断的构造函数创建)
开始包装bean,注入属性。
我们直接进去定位到最后一行代码
由spring判断后推断的构造方法初始化对象
开始注入属性,注入属性之前会先判断是否需要循环依赖
这个三个条件分别是判断该对象的是否是一个单例的,第二个默认为true ,第三个顾名思义是指这个对象是否正在被创建(bean的生命周期之中)
这个对象是否正在被创建在这个getSingleton方法中可以看到
紧接着再看下面的方法
可以看到EService 被放到了第二个map中
填充属性的入口就是从populateBean方法开始,我们点进去往里面看
我们在xml中配置了自动注入,注入模型是bytype,所以会进入到下面的方法自动装配属性,我们继续点进去
属性名称获取到的是tService,正是eService所依赖的对象
我想代码跟踪到这儿大家心里也明白了。EService的创建过程中 依赖了 TService 时 ,就会先去创建 TService对象,我们继续点进去看看,然后再看debugger的调用链 ,没错又回到了我们刚开始走的方法
当我们在创建TService的时候 发现又依赖了UService 那么肯定会继续回来走相同的getBean方法。那如果在创建UService的时候发现 又依赖了 EService 难道又要继续无限死循环下去吗。spring一向精明能干,看看spring是怎么做的吧,还记得前面提到过的3个map吗?还记得这个方法吗?每次都会往第二个map添加依赖的对象,我们来看第二次会怎样
初始化的时候:
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);
}
}
}
singletonObjects 10个root object
singletonFactories 0个
earlySingletonObjects 0个
我们第一次eService的结果是:
singletonObjects 10个root object
singletonFactories 1个--eService
earlySingletonObjects 0个
第二次tService的结果是:
singletonObjects 10个root object
singletonFactories 2个--eService,tService
earlySingletonObjects 0个
第三次uService的结果是:
singletonObjects 10个root object
singletonFactories 3个--eService,tService,uService
earlySingletonObjects 0个
当第三次uService查找依赖eService的时候会将singletonFactories的eService 设置到 earlySingletonObjects 中,然后在返回。
singletonObjects 10个root object
singletonFactories 2个,tService,uService
earlySingletonObjects 1个 eService
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;
}
等eService创建出来,同理另外两个对象也会用同样的方法创建出来。
简单一点说就是spring通过两个map来完成了bean的循环依赖
但如果使用构造方法如何来解决循环依赖呢,虽然spring4.0之后推荐使用构造方法注入,但是spring在解析构造方法注入的时候会抛出异常Requested bean is currently in creation: Is there an unresolvable circular reference?
那如何解决呢,可以在被依赖的对象使用set方法注入就可以了。
为什么spring 使用构造方法注入会出现这样的问题呢?
那我们先回到上文的第一个点:3个map,3个map下面还有一个set 集合 Set<String> singletonsCurrentlyInCreation
这个set集合是干什么用的了,所以第二个点我们回到上文提到过的,判断是否需要循环引用有三个判断条件,其中的第三个条件
会判断这个是否正在被创建,spring是怎么知道这个对象是否正在被创建呢。正是这个set集合做到的,但spring是怎么做到的呢?
不知道大家有没有注意到上文中打印了一个 eService object init呢。对象先初始化再来注入属性。从我们解释完属性的循环注入也应该注意到了循环应用的3个条件全部为true时,才会往第二个map中put数据
而我们刚开始的getSingleton方法中
此时取三级肯定为空,我们不妨回顾一下上面判断3个条件为true时是往二级缓存即第二个map中存放的数据,所以当返回的时候我们取二级是有值的,查找完最后一层循环依赖的时候
这个地方是有返回值的。
但是,结合上面的解释构造方法的循环依赖的流程中自始至终都没有往二级缓存中存放数据。但是构造方法的循环依赖在这一行就返回到上图的这个getSingleton了。为什么这一行就不往下面走了呢。不往下走就不能往map中存数据了。有兴趣可以点进去看看。
所以没有看到构造函数的循环依赖没有往二级缓存中放数据。但是那个set集合里面已经存在了当前这个对象即正在创建中的,当这个方法查找依赖的时候最后一次返回了空就又会走到第二个getSingleton方法中,并且将这个所依赖的对象方法这个set集合中,可是set集合自带去重特效(Java语法的支持),就很正常的报错了。
所以如果是构造方法的循环依赖是无法实现了,只能在被依赖的那个对象中使用set方法来注入了
下一篇spring是如何筛选合适的构造方法创建对象的