 - 本篇不仅仅是介绍Spring循环依赖的原理,而且给出Spring不能支持的循环依赖场景与案例,对其进行详细解析,同时给出解决建议与方案,以后出现此问题可以少走弯路。
复制代码 背景
1、循环依赖异常信息
- 应用时间时间久
- 应用多人同时并行开发
- 应用保证迭代进度
经常出现启动时出现循环依赖异常- Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'taskPunchEvent': Injection of resource dependencies failed; nested exception is org.
- springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'playContentService': Bean with name 'playContentService' has been injected into other be
- ans [toVoConvertor] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. Thi
- s is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
- at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:325)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1404)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
- at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
- at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
- at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
- at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
- at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
- at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1255)
- at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175)
- at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:595)
- ... 40 more
- Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'playContentService': Bean with name 'playContentService' has been injecte
- d into other beans [toVoConvertor] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version o
- f the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
- at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
- at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
- at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
- at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
- at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeanByName(AbstractAutowireCapableBeanFactory.java:452)
- at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:527)
- at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
- at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:637)
- at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:180)
- at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
- at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:322)
- ... 51 more
复制代码
2、依赖关系
先不关注其他不规范问题,看现象

3、涉及基础知识
- Spring bean 创建流程
- Dynamic Proxy 动态代理
- Spring-AOP 原理
问题
1、什么是循环依赖?
2、为什么会产生循环依赖?
3、循环依赖有哪些场景?
4、Spring如何解决循环依赖的?
5、Spring为什么使用三级缓存?
6、Spring支持AOP循环依赖,为何还存在循环依赖异常?
7、Spring不支持的循环依赖场景及如何解决?
注:Spring启动流程与Bean创建初始化流程如不熟悉,自行补习,篇幅原因此处不做介绍
Spring循环依赖
1、什么是循环依赖
2、核心概念
- BeanDefinition:spring核心bean的配置信息
- Spring Bean:spring管理的已经初始化好以后的可使用的实例
- 首先,通过spring通过扫描各种注解 @Compoent、@Service、@Configuration等等把需要交给spring管理的bean初始化成 BeanDefinition 的列表
- 然后,根据 BeanDefinition 创建spring bean的实例
- Java Bean:Java简单通过构造函数创建的对象
- Spring通过推断构造方法后,通过反射调用构造函数创建的对象
1、什么情况下出现循环依赖
并非使用者手动去getBean才会加载并初始化,而是框架启动时进行加载- Spring创建Bean - #DefaultListableBeanFactory#preInstantiateSingletons
- @Override
- public void preInstantiateSingletons() throws BeansException {
-
- //......
-
- List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
- // Trigger initialization of all non-lazy singleton beans...
- for (String beanName : beanNames) {
- RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
- if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
- if (isFactoryBean(beanName)) {
- //FactoryBean接口处理
- ......
- }
- else {
- //正常Bean的加载入口
- getBean(beanName);
- }
- }
- }
-
- //......
- }
复制代码
4、循环依赖场景
- 构造器内的循环依赖
- 注入的好处很明显,如果容器中不存在或者存在多个实现时,可以从容处理。
- 强依赖,先有鸡还是先有蛋问题暂无解,此依赖方式Spring不支持,除非自身实现代理加延迟注入,这种方式很难解决,除非实现类似于lazy生成代理方式进行解耦来实现注入,Spring没有支持可能因为此种注入场景都可以用其他方式代替且场景极少。
- 弱依赖,spring 4.3之后增加 ObjectProvider 来处理
- //构造器循环依赖示例
- public class StudentA {
-
- private StudentB studentB ;
- public StudentA(StudentB studentB) {
- this.studentB = studentB;
- }
- }
- public class StudentB {
-
- private StudentA studentA ;
-
- public StudentB(StudentA studentA) {
- this.studentA = studentA;
- }
- }
复制代码
- setter方式单例,默认方式
- setter方式原型,prototype
对于“prototype”作用域Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
- field属性循环依赖
最常用,此场景是通过反射注入,以下为@Autowire 注入代码,@Resource省略
AutowiredAnnotationBeanPostProcessor#postProcessProperties
- @Override
- public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
- InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
- try {
- //属性注入
- metadata.inject(bean, beanName, pvs);
- }
- catch (BeanCreationException ex) {
- throw ex;
- }
- catch (Throwable ex) {
- throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
- }
- return pvs;
- }
复制代码
5、三级缓存解决循环依赖(1)、一级缓存
DefaultSingletonBeanRegistry- private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
复制代码
- 最基础的单例缓存
- 限制 bean 在 beanFactory 中只存一份,即实现 singleton scope
(2)、二级缓存
二级缓存(未初始化未填充属性提前暴露的Bean)- private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
复制代码
- 看名字应该就能猜到,缓存earlySingletonBean,与三级缓存配合使用的
- 需要注意:
- 在没有AOP场景时是可以的,每次earlySingletonObjects.get()换成去三级缓存取就可以,存在问题
- 存在AOP场景时
- 因此,让使用者去做重复性判断是不可控的,很容易出现问题,于是引入了第二级缓存,当调用三级缓存里的对象工厂的getObject方法之后,getEarlyBeanReference 就会把返回值放入二级缓存,删除三级缓存,后续其他依赖该对象的Bean获取的都是同一个earlyBean,保证singleton原则。
- 每次都调用 getEarlyBeanReference,即使返回对象都一致,也浪费不必要时间
- 如果使用者在 getEarlyBeanReference 时直接 new XXX(),则对象又不一致,无法保证 singleton,所以需要使用者熟悉这块原理,并且自身维护,并且暴露内部实现细节
- 每次都调用 getEarlyBeanReference 返回代理对象都不一致,无法保证 singleton
- 如果没有此缓存,可不可以解决循环依赖问题?
(3)、三级缓存
三级缓存(Bean创建时提供代理机会的Bean工厂缓存)
[code]private final Map |