乐趣区

关于spring:Spring如何解决循环依赖-springboot实战电商项目mall4j

Spring 如何解决循环依赖?

springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)

java 开源商城零碎

@component
class A {private B b;}
@component
class B {private A a;}

类 A 依赖了 B 作为属性,类 B 又应用类 A 作为属性,彼此循环依赖。

源码了解:

// 调用 AbstractBeanFactory.doGetBean(),向 IOC 容器获取 Bean,触发依赖注入的办法
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {
        ...
         // 第一次 getSingleton 获取对象实例
        // 先从缓存中取是否曾经有被创立过的单例类型的 Bean[没有的话就去获取半成品的,也就是 earlySingletonObjects,缓存二的货色]
        // 对于单例模式的 Bean 整个 IOC 容器中只创立一次,不须要反复创立
        Object sharedInstance = getSingleton(beanName);
        ...
        try {
            // 创立单例模式 Bean 的实例对象
            if (mbd.isSingleton()) {
                // 第二次 getSingleton 尝试创立指标对象,并且注入属性
                // 这里应用了一个匿名外部类,创立 Bean 实例对象,并且注册给所依赖的对象
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        // 创立一个指定 Bean 实例对象,如果有父级继承,则合并子类和父类的定义
                        return createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        // 显式地从容器单例模式 Bean 缓存中革除实例对象
                        destroySingleton(beanName);
                        throw ex;
                    }
                });
                // 如果传入的是 factoryBean,则会调用其 getObject 办法,失去指标对象
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                //IOC 容器创立原型模式 Bean 实例对象
            }
            ...
        } catch (BeansException ex) {cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
        ...
        return (T) bean;
    }

也就是实例化 A 的时候在缓存中没找到 [第一个 getSingleton],就去第二个getSingleton 实例化 A [实际上是调用了 doCreateBean()],因为 A 须要 B,又去 doGetBean 尝试获取 B,发现 B 也不在缓存中,持续调用第二个 getSingleton 去实例化,当要注入属性 A 的时候在二级缓存找到了半成品 A,胜利注入返回到 A 实例化的阶段,将 B 注入。

第一个 getSingleton 代码

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 从 spring 容器中获取 bean
        Object singletonObject = this.singletonObjects.get(beanName);// 缓存 1
        // 如果获取不到 判断要获取的对象是不是正在创立过程中 ---- 如果是,则去缓存 (三级缓存) 中取对象(不是 bean)
        //isSingletonCurrentlyInCreation() 寄存的对象 的机会是在 getBean 中第二次调用 getSingleton 时候 beforeSingletonCreation(beanName); 存进去的
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);// 缓存 2
                if (singletonObject == null && allowEarlyReference) {//allowEarlyReference-- 判断是否反对循环依赖,默认为 true
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 缓存 3
                    if (singletonFactory != null) {singletonObject = singletonFactory.getObject();
                        // 将三级缓存降级为二级缓存
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 从三级缓存中删除  为什么删除? 避免反复创立。设置三级缓存的目标是为了进步性能,因为每次创立都须要通过 factory,会破费很多工夫
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

在实例化 AB 的时候,三个缓存都是找不到这两个类的,因为两者均未创立;

三级缓存

    /** Cache of singleton objects: bean name --> bean instance */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 一级缓存,寄存残缺的 bean 信息

    /** Cache of singleton factories: bean name --> ObjectFactory */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);// 三级缓存,bean 创立完了就放进去,还有他的 bean 工厂

    /** Cache of early singleton objects: bean name --> bean instance */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 二级缓存,寄存还没进行属性赋值的 bean 对象,也即半成品 bean

第二个 getSingleton 代码

此处将 A 先创立好 <u> 放入三级缓存 </u> 中,实际上是委托给另一个 doGetBean() 实现的

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        ...
        synchronized (this.singletonObjects) {
            // 再次判断 ioc 容器中有无该 bean
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                ..before..
                try {
                    // 回调到 doCreateBean
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                ..after..
            }
            return singletonObject;
        }
    }

因为第一个获取单例的办法找不到 AB,故此将会进入第二个获取单例的办法试图找到,这个办法里 singletonFactory.getObject() 为外围,将会回调到 doCreateBean 办法持续创立 Bean。

doCreateBean 代码

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {

        // 封装被创立的 Bean 对象
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            // 单例的状况下尝试从 factoryBeanInstanceCache 获取 instanceWrapper, 并革除同名缓存
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            // 创立 Bean 实例
            // instanceWrapper 会包装好指标对象
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        ...
        // 向容器中缓存单例模式的 Bean 对象,以防循环援用,allowCircularReferences 是判断是否反对循环依赖,这个值能够改为 false
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        // 判断是否容许循环依赖
        if (earlySingletonExposure) {
            ...
            // 将这个对象的工厂放入缓存中(注册 bean 工厂 singletonFactories,第一个 getSingleton 应用的),此时这个 bean 还没做属性注入
            // 这里是一个匿名外部类,为了避免循环援用,尽早持有对象的援用
            // getEarlyBeanReference 很特地,这外面会做 aop 代理
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        //Bean 对象的初始化,依赖注入在此触发
        // 这个 exposedObject 在初始化实现之后返回作为依赖注入实现后的 Bean
        Object exposedObject = bean;
        try {
            // 填充属性 -- 主动注入
            // 后面是实例化,并没有设置值,这里是设置值. 将 Bean 实例对象封装,并且 Bean 定义中配置的属性值赋值给实例对象
                // 这里做注入的时候会判断依赖的属性在不在,不在就调用 doGetBean 持续创立
            populateBean(beanName, mbd, instanceWrapper);
            // 该办法次要是对 bean 做一些扩大
            // 初始化 Bean 对象。属性注入已实现,解决各种回调
            //(对实现 Aware 接口 (BeanNameAware、BeanClassLoaderAware、BeanFactoryAware) 的 bean 执行回调、// aop、init-method、destroy-method?、InitializingBean、DisposableBean 等)exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        ...
        return exposedObject;
    }

在这个办法里,实例化 A 的时候,曾经把 A 作为一个半成品通过调用 addSingletonFactory 办法将其退出了三级缓存 singletonFactories,不便在递归实例化 B 的时候能够获取到 A 的半成品实例,具体代码如下:

将创立的 bean 退出 <u> 三级缓存 </u>

产生在 addSingletonFactory 这个办法

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {
                // 放到三级缓存
                // 注册 bean 工厂
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

那么是在什么时候会产生一个递归的调用呢?

实际上是在 <u>populateBean(beanName, mbd, instanceWrapper);</u> 要做属性注入的时候,假如是依据名称主动注入的,调用 <u>autowireByName(),</u> 该法会去循环遍历在 getBean 之前曾经把 xml 文件的属性退出到注册表之类的属性,

populateBean 有一个 autowireByName 的办法,代码如下

protected void autowireByName(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {// 对 Bean 对象中非简略属性 (不是简略继承的对象,如 8 中原始类型,字符串,URL 等都是简略属性) 进行解决
        String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
        for (String propertyName : propertyNames) {
            // 如果 Spring IOC 容器中蕴含指定名称的 Bean,就算还没进行初始化,A 发现有 B 属性时,B 属性曾经被写入注册表之类的货色,所以这个判断返回 true
            if (containsBean(propertyName)) {
                // 调用 getBean 办法向 IOC 容器索取指定名称的 Bean 实例,迭代触发属性的初始化和依赖注入
                Object bean = getBean(propertyName); // 实际上就是委托 doGetBean()
                pvs.add(propertyName, bean);
                // 指定名称属性注册依赖 Bean 名称,进行属性依赖注入
                registerDependentBean(propertyName, beanName);
                if (logger.isDebugEnabled()) {
                    logger.debug("Added autowiring by name from bean name'" + beanName +
                            "'via property'" + propertyName + "'to bean named'" + propertyName + "'");
                }
            }
            ...
        }
    }

Object bean = getBean(propertyName); 这个办法实际上又去委托了 doGetBean(),又一次递归的走上了流程,也就是 A 在实例化到该步时发现,还有一个 B,就会又从 doGetBean()开始,一步步的寻找创立,不同的是,当 B 走到这个依据名称注入的办法时,此时的曾经能在二级缓存里找到 A 的身影了,无需再次创立 A 对象。

总结

spring 使用三级缓存解决了循环依赖的问题;

采纳递归的形式,逐渐的去实例化对象,并将上一步曾经退出缓存的半成品对象作为属性注入;

等到走到最初一个递归时,将会逐渐返回,把对应的实例一个个创立好。

springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)

java 开源商城零碎

退出移动版