目录
什么是循环依赖
多个bean之间相互依赖,形成了一个闭环。比如:A依赖B、B依赖C、C依赖A。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class T1 { class A { B b; } class B { C c; } class C { A a; } } |
通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,属性互相引用的场景。
循环依赖官网说明:beans-dependency-resolution
结论
我们AB循环依赖问题只要A的注入方式是setter且singleton,就不会有循环依赖问题。也就说,这种注入方式,spring底层会解决循环依赖。
循环依赖报错
循环依赖现象在spring容器中注入依赖的对象,有2种情况:构造器方式注入依赖和以set方式注入依赖。
构造器方式注入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class ServiceA { private ServiceB serviceB; // 构造器注入 public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; } } public class ServiceB { private ServiceA serviceA; // 构造器注入 public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; } } public class ClientConstructor { new ServiceA(new ServiceB(new ServiceA(..无限new下去..))); } |
结论:构造器循环依赖是无法解决的。你想让构造器注入支持循环依赖,是不存在的。
以set方式注入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
@Component public class ServiceA { private ServiceB serviceB; public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; System.out.println("A里面设置了B"); } } @Component public class ServiceB { private ServiceA serviceA; public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; System.out.println("B里面设置了A"); } } public class ClientSet { public static void main(String[] args) { // 创建 ServiceA ServiceA a = new ServiceA(); // 创建 ServiceB ServiceB b = new ServiceB(); // 将 ServiceA 注入 ServiceB 中 b.setServiceA(a); // 将 ServiceB 注入 ServiceA 中 a.setServiceB(b); } } |
结论:以set方式注入依赖,可以解决循环依赖。
在普通的java下,并不会出现循环依赖问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class A { public A() { System.out.println("A created success"); } private B b; public B getB() { return b; } public void setB(B b) { this.b = b; } } public class B { public B() { System.out.println("B created success"); } private A a; public A getA() { return a; } public void setA(A a) { this.a = a; } } public class ClientCode { public static void main(String[] args) { A a = new A(); B b = new B(); b.setA(a); a.setB(b); } } |
在Spring容器下,默认的单例(singleton)的场景是支持循环依赖的,不报错。原型(Prototype)的场景是不支持循环依赖的,会报错。
applicationContext.xml,scope改为prototype则会产生循环依赖错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--scope默认是singleton,如果改为prototype,则会产生循环依赖错误--> <bean id="a" class="com.binz.study.spring.circulardepend.A" scope="prototype"> <!--bean a有个属性b,需要引用id="b"的bean--> <property name="b" ref="b"/> </bean> <bean id="b" class="com.binz.study.spring.circulardepend.B" scope="singleton"> <!--bean b有个属性a,需要引用id="a"的bean--> <property name="a" ref="a"/> </bean> </beans> |
运行
1 2 3 4 5 6 7 |
public class ClientSpringContainer { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); A a = context.getBean("a",A.class); B b = context.getBean("b",B.class); } } |
报循环依赖异常
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?
Spring 内部通过三级缓存解决循环依赖
第一级缓存:(也叫单例池)singletoneObjects。存放已经经历了完整生命周期的Bean对象。
第二级缓存:earlySingletonObjects 。存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存:Map<String, ObjectFactory<?>> singletonFactories。存放可以生成Bean的工厂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100; /** * 单例对象所得缓存:bean名称-bean实例,即:所谓的单例池。 * 表示已经经历了完整生命周期的Bean对象。 * <b> 第一级缓存 </b> */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); /** * 早期的单例对象的高速缓存:bean名称-bean实例。 * 表示Bean的生命周期还没走完(Bean的属性还未填充)就把这个Bean存入该缓存中 * 也就是实例化但未初始化的Bean放入该缓存里。 * <b> 第二级缓存 </b> */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16); /** * 单例工厂的高速缓存:bean名称-ObjectFactory。 * 表示存放生成bean的工厂。 * <b> 第三级缓存 </b> */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16); private final Set<String> registeredSingletons = new LinkedHashSet(256); private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16)); private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap(16)); @Nullable private Set<Exception> suppressedExceptions; private boolean singletonsCurrentlyInDestruction = false; private final Map<String, Object> disposableBeans = new LinkedHashMap(); private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap(16); private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap(64); private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap(64); public DefaultSingletonBeanRegistry() { } // --- 以下省略 --- } |
只有单例的Bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的Bean(scope=”prototype”),每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的Bean(scope=”prototype”)是没有缓存的,不会将其放到三级缓存中。
Spring 三级缓存操作
1、A 创建过程中需要B,于是A 将自己放到三级缓存里面,去实例化B。
2、B实例化的时候发现需要A ,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A。然后把三级缓存里面的这个A 放到二级缓存里面,并删除三级缓存里面的A 。
3、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建状态)。然后回来接着创建A ,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
Spring解决循环依赖过程
Spring 创建 bean 主要分为两个步骤,创建原始 bean 对象,接着去填充对象属性和初始化。
每次创建 bean 之前,我们都会从缓存中查下有没有该 bean ,因为是单例,只能有一个。
当我们创建 beanA 的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了 beanB ,接着就又去创建 beanB ,同样的流程,创建完 beanB 填充属性时又发现它依赖了 beanA 又是同样的流程。
不同的是:
这时候可以在三级缓存中查到刚放进去的原始对象 beanA ,所以不需要继续创建,用它注入 beanB ,完成 beanB 的创建。
既然 beanB 创建好了,所以 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 从 singletonObjects 获取实例,singletonObjects 中的实例都是准备好的 bean 实例,可以直接使用 Object singletonObject = this.singletonObjects.get(beanName); // isSingletonCurrentlyInCreation() 判断当前单例bean是否正在创建中 if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized(this.singletonObjects) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 一级缓存没有,就去二级缓存找 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 二级缓存也没有,就去三级缓存找 ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { // 三级缓存有的话,就把它移动到二级缓存 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; } |
Spring解决循环依赖依靠的是 Bean 的“中间态”这个概念,而这个中间态指的是己经实例化但还没初始化的状态——>半成品。
实例化的过程又是通过构造器创建的,如果 A 还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
Spring为了解决单例的循环依赖问题,使用了三级缓存。
其中一级缓存为单例池( singletonObjects )
二级缓存为提前曝光对象( earlySingletonObjects )
三级缓存为提前曝光对象工厂( singletonFactories )。
假设 A 、 B 循环引用,实例化 A 的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖 A ,这时候从缓存中查找到早期暴露的 A ,没有 AOP 代理的话,直接将 A 的原始对象注入B,完成 B 的初始化后,进行属性填充和初始化,这时候 B 完成后,就去完成剩下的 A 的步骤,如果有 AOP 代理,就进行 AOP 处理获取代理后的对象 A ,注入 B ,走剩下的流程。
1、调用doGetBean()方法,想要获取beanA,于是调用getSingletone()方法从缓存中查找beanA。
2、在getSingletone()方法中,从一级缓存中查找,没有,返回null。
3、doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingletone()的重载方法(参数为ObjectFactory的)。
4、在getSingletone()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法。
5、进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第4步的集合中)。判断为true,则将beanA添加到【三级缓存】中。
6、对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB。
7、调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性。
8、此时beanB依赖于beanA,调用getSingletone()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletoneObject,此时这个singletoneObject指向的就是上面在doCreateBean()方法中实例化的beanA。
9、这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中。
10、随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getSingletone()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中。