循环依赖就是循环引用,指两个或多个bean互相持有对方,比如说TestA引用TestB、TestB引用TestA,最终形成一个闭环。
1 循环依赖分类
类别
是否能解决
构造器循环依赖
无法解决
setter循环依赖
可以解决
prototype范围的依赖
无法解决
构造器循环依赖 表示通过构造器注入构成的循环依赖,此依赖是无解的,强行依赖只能抛出异常(BeanCreationException);
Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因此在创建bean的过程中如果发现自己已经在池中,则抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的bean将从“当前创建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 @ComponentScan("com.dependency") public class AppConfig {} @Component public class X { public X (Y y) { } } @Component public class Y { public Y (X x) { } } public class TestDemo { public static void main (String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(AppConfig.class); context.refresh(); System.out.println(context.getBean(X.class)); } } Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'x' defined in file [/Users/admin/IdeaProjects/springmvc-demo/target/classes/com/dependency/service/X.class]: Unsatisfied dependency expressed through constructor parameter 0 ; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'y' defined in file [/Users/admin/IdeaProjects/springmvc-demo/target/classes/com/dependency/service/Y.class]: Unsatisfied dependency expressed through constructor parameter 0 ; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'x' : Requested bean is currently in creation: Is there an unresolvable circular reference?
setter循环依赖 指通过setter注入方式构成的循环依赖。
解决方式 :Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成的。而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该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 38 39 40 41 @ComponentScan("com.dependency") public class AppConfig {} @Component public class X { @Autowired Y y; public X () { System.out.println("X create ***************" ); } } @Component public class Y { @Autowired X x; public Y () { System.out.println("Y create ***************" ); } } public class TestDemo { public static void main (String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(AppConfig.class); context.refresh(); System.out.println(context.getBean(X.class)); } } X create *************** Y create ***************
prototype范围的依赖 对于prototype作用域bean,spring容器无法完成依赖注入,因为spring容器不进行缓存prototype作用域的bean,因此无法提前暴露一个正在创建中的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 38 39 40 41 42 @ComponentScan("com.dependency") public class AppConfig {} @Component @Scope("prototype") public class X { @Autowired Y y; public X () { System.out.println("X create ***************" ); } } @Component @Scope("prototype") public class Y { @Autowired X x; public Y () { System.out.println("Y create ***************" ); } } public class TestDemo { public static void main (String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(AppConfig.class); context.refresh(); System.out.println(context.getBean(X.class)); } } Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'x' : Unsatisfied dependency expressed through field 'y' ; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'y' : Unsatisfied dependency expressed through field 'x' ; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'x' : Requested bean is currently in creation: Is there an unresolvable circular reference?
总结 1、构造器注入和prototype类型的field注入发生循环依赖时都无法初始化
2、field注入单例的bean时,尽管有循环依赖,但bean仍然可以被成功初始化
2 spring是如何解决循环依赖的 Spring创建Bean的过程
instantiate(): 利用反射选择合适的构造方法实例化
populateBean: 填充属性,设置bean之间的依赖关系
Spring解决循环依赖图解
提问
单例的设值注入bean是如何解决循环依赖问题呢?如果A中注入了B,那么他们初始化的顺序是什么样子的?
为什么prototype类型的和构造器类型的Spring无法解决循环依赖呢?
问题分析 对于问题1,单例的设值注入,如果A中注入了B,B应该是A中的一个属性,那么猜想应该是A已经被instantiate(实例化)之后,在populateBean(填充A中的属性)时,对B进行初始化。
对于问题2,instantiate(实例化)其实就是理解成new一个对象的过程,而new的时候肯定要执行构造方法,所以猜想应该是A在instantiate(实例化)时,进行B的初始化。
3 单例初始化三级缓存 简介 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { ... private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256 ); private final Map<String, Object> earlySingletonObjects = new HashMap<>(16 ); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16 ); ... private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16 )); private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256 )); }
这三级缓存分别指:
singletonFactories : 单例对象工厂的cache earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】 singletonObjects:单例对象的cache
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。
DefaultSingletonBeanRegistry#getSingleton
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 != NULL_OBJECT ? singletonObject : null ); }
上面的代码需要解释两个参数:
isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
分析:
spring首先从一级缓存 singletonObjects 中获取Bean,(如何获取到则直接返回)
如果一级缓存没有命中Bean,并且要获取的Bean是正在创建中的Bean,则尝试从二级缓存中获取(如果获取到则直接return)
如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动到了二级缓存)
从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:
1 2 3 public interface ObjectFactory <T > { T getObject () throws BeansException ; }
这个接口在下面被引用
DefaultSingletonBeanRegistry#addSingletonFactory
1 2 3 4 5 6 7 8 9 10 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); } } }
这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
注:三级缓存的添加是在设置属性之前的后置处理器中添加
二级缓存添加/移除 添加:向里面添加数据只有一个地方,就是上面说的getSingleton()里从三级缓存里挪过来
移除:addSingleton、addSingletonFactory、removeSingleton从语义中可以看出添加单例、添加单例工厂ObjectFactory的时候都会删除二级缓存里面对应的缓存值,是互斥的
循环依赖问题解答 对于问题1:
假设循环注入是A-B-A:A依赖B(A中autowire了B),B又依赖A(B中又autowire了A):
A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,
此时进行初始化的第二步,发现自己依赖对象B,
此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),
尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,
由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。
此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
对于当时问题2:
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
总结 Spring在InstantiateBean时执行构造器方法,构造出实例,如果是单例的话,会将它放入一个singletonBeanFactory的缓存中,再进行populateBean方法,设置属性。通过一个singletonBeanFactory的缓存解决了循环依赖的问题。
参考链接
参考链接