zoukankan      html  css  js  c++  java
  • spring学习总结010 --- 循环依赖

    先说一下循环依赖常见问题的个人理解:

    1、spring只能解决属性或者setter注入场景下的循环依赖?? ------  我理解不是,在构造器注入和属性注入混合场景下也是能够解决的

    2、spring解决循环依赖采用了三级缓存,之所以用三级缓存是为了提升效率??  -------   我理解三级缓存和二级缓存效率相差无几,只不过为了解决AOP场景下生命周期问题

    什么是循环依赖

    A依赖B的同时,B也依赖A,这就是循环依赖,用代码表示如下:

    public class BeanB {
    
        @Autowired
        private BeanA beanA;
    }
    public class BeanA {
    
        @Autowired
        private BeanB beanB;
    }

    spring能解决哪种场景下的循环依赖

    1、A和B均通过构造器依赖注入                  -----  spring无法解决

    2、A和B均通过属性注入                             -----  spring可以解决

    3、A通过属性注入B,B通过构造器注入A   -----   spring可以解决

    4、A通过构造器注入B,B通过属性注入A   -----   spring无法解决

    spring如何解决循环依赖 

    spring中解决循环依赖bean的主要创建过程如下:

    三级缓存源码中的位置及缓存的内容如下:

    以A和B均属性注入产生循环依赖,说明spring如何解决循环依赖:

    创建bean都是从AbstractBeanFactory的getBean方法入口,然后调用getSingleton获取bean,当然创建BeanA的时候,getSingleton返回空;getSingleton方法源码:

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 从一级缓存中获取bean
        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) {
                        // 命中三级缓存, 将bean实例从三级缓存移动到二级缓存
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

    然后进入另一个getSingleton方法,该方法的第二个参数是lamba表达式,里面调用了createBean方法,返回单例工厂实例:

    然后在doCreteBean方法中调用前面提到的三个方法,调用createBeanInstance创建实例,然后将该实例转换成实例工厂对象,存放到三级缓存

    接下来在populateBean方法中填充属性,发现bean定义中BeanA依赖BeanB,然后调用getBean方法获取BeanB获取BeanB实例方法和获取BeanA一致,直到BeanB处理属性填充;

    BeanB填充属性BeanA时,先从缓存中获取,这里命中的是三级缓存;然后完成了BeanB的属性填充、初始化等操作,得到了完整的BeanB实例;

    接下来继续执行BeanA的初始化,直到创建完成。

    前面说到命中三级缓存返回的对象是不完整的,那么是否意味着BeanB持有的BeanA实例是不完整的??当然不是,因为BeanB持有的是引用,BeanA的初始化完成也意味着BeanB持有的引用初始化完成

    为什么spring只能解决特定场景下的循环依赖

    1、A和B均通过构造器依赖注入                  -----  spring无法解决

    A创建实例的时候发现需要注入B对象,然后调用B的创建流程,当创建B的时候,检测到需要注入A,但是此时缓存中没有只能去创建,因此循环依赖无法解决

    2、A和B均通过属性注入                             -----  spring可以解决

    3、A通过属性注入B,B通过构造器注入A   -----   spring可以解决

    4、A通过构造器注入B,B通过属性注入A   -----   spring无法解决

    和前面类似,A创建实例的时候发现需要注入B对象,然后调用B的创建流程;B是通过属性注入A,此时能够创建成功B的实例;接下来会执行填充B的属性,发现要注入A,执行getBean(A),因为此前A被标记在创建中,因此抛出异常

    示例:

    @Component
    @Slf4j
    public class BeanA {
    
        @Autowired
        public BeanA(BeanB beanB) {
        }
    }
    @Component
    @Slf4j
    public class BeanB {
    
        @Autowired
        private BeanA beanA;
    }

    执行结果:

    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:219)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1304)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1224)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
        ... 56 more

    spring为什么解决循环依赖时用了三级缓存,用二级行不行?

    我的理解,没有使用AOP的bean二级缓存完全够用,但是有了AOP,需要用三级缓存,也并不是说三级缓存是必须的,而是引入三级缓存为了使AOP场景下的bean生命周期标准化;至于三级缓存效率更高,我没觉得;

    先看看三级缓存里面存储了什么:三級缓存中存储的是工厂对象,在AOP场景下该工厂返回代理对象,非AOP场景下返回的是源对象

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        // 存在AOP代理, 为原来的bean创建代理对象; 否则返回原对象
        // AnnotationAwareAspectJAutoProxyCreator为AOP后置处理器
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
                exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
        return exposedObject;
    }
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        // 如果需要返回代理对象
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

    在初始化完成后,又调用了一次getSingleton方法,这里allowEarlyReference参数为false,也就是禁用三级缓存的含义;

    exposedObject == bean是恒等于的,除非没事闲的蛋疼,自定义了一个BeanPostProcessor,修改了bean;

    PS:虽然AnnotationAwareAspectJAutoProxyCreator也是一种后置处理器,并且返回了代理对象,会造成bean的修改,不过这个后置处理器在执行postProcessAfterInitialization方法时,会判断代理对象是否存在,不存在则返回代理对象;

            而在将bean存入三级缓存时,实际上就已经创建了代理对象;因此AnnotationAwareAspectJAutoProxyCreator不会影响exposedObject == bean

    前面这一坨做个总结:B在注入A时,从三级缓存拿到A,并将A存入二级缓存;A在注入了B之后,从二级缓存将A实例取出(此时的实例是完整的),然后将A存入单例池singletonObjects

    回到最开始的问题,为啥非得用三级缓存这个工厂对象,直接用二级缓存将对象暴露出去不可以么???三级缓存是为了延迟实例化期间生成代理对象;注:这里的三级缓存指的是一个lamba表达式

     

    1、无论是否存在循环依赖,只要有AOP,那么注入的对象一定是代理对象;

    2、不存在循环依赖时,如果只有二级缓存,那么先创建代理对象,然后存入二级缓存;这么早创建代理对象完全没必要并且违背AOP和spring结合的生命周期;

    3、spring结合AOP的处理类是AnnotationAwareAspectJAutoProxyCreator,是一种后置处理器,并且要求在初始化后创建代理对象

    4、因此引入三级缓存,提前暴露一个工厂对象,只有在循环依赖的时候才提前创建代理对象

    三级缓存真的提升了效率??

    还是以A、B循环依赖为例,A被定义为切面,并开启aop代理

    1、三级缓存场景

    2、假设只有二级缓存场景

    能够看到有无三级缓存只是创建A代理对象的时机不一样,并没有带来什么效率提升

  • 相关阅读:
    线程池参数设置技巧
    线程池的七个参数
    bug篇——Windows启动redis报错Creating Server TCP listening socket 127.0.0.1:6379: bind: No error
    总结篇——git本地仓库上传更新到github
    实例篇——springboot自定义拦截器
    总结篇——从零搭建maven多模块springboot+mybatis项目
    bug篇——MySQL的时区问题
    工具类篇——时间处理Calendar类
    安装篇——nginx安装ssl模块转发https请求
    总结篇——nginx代理服务器
  • 原文地址:https://www.cnblogs.com/sniffs/p/13295558.html
Copyright © 2011-2022 走看看