Spring循环依赖那些事儿(含Spring详细流程图)

打印 上一主题 下一主题

主题 885|帖子 885|积分 2655

  1. 本篇不仅仅是介绍Spring循环依赖的原理,而且给出Spring不能支持的循环依赖场景与案例,对其进行详细解析,同时给出解决建议与方案,以后出现此问题可以少走弯路。
复制代码
背景
1、循环依赖异常信息

  • 应用时间时间久
  • 应用多人同时并行开发
  • 应用保证迭代进度
经常出现启动时出现循环依赖异常
  1. Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'taskPunchEvent': Injection of resource dependencies failed; nested exception is org.
  2. springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'playContentService': Bean with name 'playContentService' has been injected into other be
  3. 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
  4. s is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
  5.   at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:325)
  6.   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1404)
  7.   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
  8.   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
  9.   at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
  10.   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
  11.   at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
  12.   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
  13.   at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
  14.   at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1255)
  15.   at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175)
  16.   at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:595)
  17.   ... 40 more
  18. Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'playContentService': Bean with name 'playContentService' has been injecte
  19. 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
  20. f the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
  21.   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)
  22.   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
  23.   at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
  24.   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
  25.   at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
  26.   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
  27.   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeanByName(AbstractAutowireCapableBeanFactory.java:452)
  28.   at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:527)
  29.   at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
  30.   at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:637)
  31.   at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:180)
  32.   at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
  33.   at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:322)
  34.   ... 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才会加载并初始化,而是框架启动时进行加载
  1. Spring创建Bean - #DefaultListableBeanFactory#preInstantiateSingletons
  2. @Override
  3. public void preInstantiateSingletons() throws BeansException {
  4.    
  5.     //......
  6.    
  7.     List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
  8.     // Trigger initialization of all non-lazy singleton beans...
  9.     for (String beanName : beanNames) {
  10.         RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
  11.         if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
  12.             if (isFactoryBean(beanName)) {
  13.                 //FactoryBean接口处理
  14.                 ......
  15.             }
  16.             else {
  17.                 //正常Bean的加载入口
  18.                 getBean(beanName);
  19.             }
  20.         }
  21.     }
  22.    
  23.     //......
  24. }
复制代码
 
4、循环依赖场景

  • 构造器内的循环依赖

    • 注入的好处很明显,如果容器中不存在或者存在多个实现时,可以从容处理。
    • 强依赖,先有鸡还是先有蛋问题暂无解,此依赖方式Spring不支持,除非自身实现代理加延迟注入,这种方式很难解决,除非实现类似于lazy生成代理方式进行解耦来实现注入,Spring没有支持可能因为此种注入场景都可以用其他方式代替且场景极少。
    • 弱依赖,spring 4.3之后增加 ObjectProvider 来处理

  1. //构造器循环依赖示例
  2. public class StudentA {
  3.     private StudentB studentB ;
  4.     public StudentA(StudentB studentB) {
  5.         this.studentB = studentB;
  6.     }
  7. }
  8. public class StudentB {
  9.     private StudentA studentA ;
  10.    
  11.     public StudentB(StudentA studentA) {
  12.         this.studentA = studentA;
  13.     }
  14. }
复制代码

  • setter方式单例,默认方式
  • setter方式原型,prototype
    对于“prototype”作用域Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
  • field属性循环依赖
    最常用,此场景是通过反射注入,以下为@Autowire 注入代码,@Resource省略
    AutowiredAnnotationBeanPostProcessor#postProcessProperties
  1. @Override
  2. public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
  3.     InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
  4.     try {
  5.         //属性注入
  6.         metadata.inject(bean, beanName, pvs);
  7.     }
  8.     catch (BeanCreationException ex) {
  9.         throw ex;
  10.     }
  11.     catch (Throwable ex) {
  12.         throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
  13.     }
  14.     return pvs;
  15. }
复制代码
 
5、三级缓存解决循环依赖(1)、一级缓存

DefaultSingletonBeanRegistry
  1. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
复制代码

  • 最基础的单例缓存
  • 限制 bean 在 beanFactory 中只存一份,即实现 singleton scope
(2)、二级缓存

二级缓存(未初始化未填充属性提前暴露的Bean)
  1. 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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表