关于java:Spring是如何解决循环依赖的

3次阅读

共计 5864 个字符,预计需要花费 15 分钟才能阅读完成。

三级缓存思维

Spring 解决循环依赖的外围就是提前裸露对象,而提前裸露的对象就是搁置于第二级缓存中。下表是三级缓存的阐明:

所有被 Spring 治理的 Bean,最终都会寄存在 singletonObjects 中,这外面寄存的 Bean 是经验了所有生命周期的(除了销毁的生命周期),残缺的,能够给用户应用的。

earlySingletonObjects 寄存的是曾经被实例化,然而还没有注入属性和执行 init 办法的 Bean。

singletonFactories 寄存的是生产 Bean 的工厂。

解决循环依赖

Spring 是如何通过下面介绍的三级缓存来解决循环依赖的呢?这里只用 A,B 造成的循环依赖来举例:

  1. 实例化 A,此时 A 还未实现属性填充和初始化办法(@PostConstruct)的执行,A 只是一个半成品。
  2. 为 A 创立一个 Bean 工厂,并放入到 singletonFactories 中。
  3. 发现 A 须要注入 B 对象,然而一级、二级、三级缓存均为发现对象 B。
  4. 实例化 B,此时 B 还未实现属性填充和初始化办法(@PostConstruct)的执行,B 只是一个半成品。
  5. 为 B 创立一个 Bean 工厂,并放入到 singletonFactories 中。
  6. 发现 B 须要注入 A 对象,此时在一级、二级未发现对象 A,然而在三级缓存中发现了对象 A,从三级缓存中失去对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(留神,此时的 A 还是一个半成品,并没有实现属性填充和执行初始化办法)
  7. 将对象 A 注入到对象 B 中。
  8. 对象 B 实现属性填充,执行初始化办法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 曾经是一个成品)
  9. 对象 A 失去对象 B,将对象 B 注入到对象 A 中。(对象 A 失去的是一个残缺的对象 B)
  10. 对象 A 实现属性填充,执行初始化办法,并放入到一级缓存中,同时删除二级缓存中的对象 A。

咱们从源码中来剖析整个过程:

创立 Bean 的办法在 AbstractAutowireCapableBeanFactory::doCreateBean()

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    
    if (instanceWrapper == null) {
        // ① 实例化对象
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }

    final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
    Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
   
    // ② 判断是否容许提前裸露对象,如果容许,则间接增加一个 ObjectFactory 到三级缓存
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 增加三级缓存的办法详情在下方
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // ③ 填充属性
    this.populateBean(beanName, mbd, instanceWrapper);
    // ④ 执行初始化办法,并创立代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);
   
    return exposedObject;
}

增加三级缓存的办法如下:

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);
        }
    }
}

@FunctionalInterface
public interface ObjectFactory<T> {T getObject() throws BeansException;
}

通过这段代码,咱们能够晓得 Spring 在实例化对象之后,就会为其创立一个 Bean 工厂,并将此工厂退出到三级缓存中。

因而,Spring 一开始提前裸露的并不是实例化的 Bean,而是将 Bean 包装起来的 ObjectFactory。为什么要这么做呢?

这实际上波及到 AOP,如果创立的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。然而 Spring 一开始并不知道 Bean 是否会有循环依赖,通常状况下(没有循环依赖的状况下),Spring 都会在实现填充属性,并且执行完初始化办法之后再为其创立代理。然而,如果呈现了循环依赖的话,Spring 就不得不为其提前创立代理对象,否则注入的就是一个原始对象,而不是代理对象。因而,这里就波及到应该在哪里提前创立代理对象?

Spring 的做法就是在 ObjectFactory 中去提前创立代理对象。它会执行 getObject() 办法来获取到 Bean。实际上,它真正执行的办法如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 如果须要代理,这里会返回代理对象;否则返回原始对象
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

因为提前进行了代理,防止对前面反复创立代理对象,会在 earlyProxyReferences 中记录已被代理的对象。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 记录已被代理的对象
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
}

通过下面的解析,咱们能够晓得 Spring 须要三级缓存的目标是为了在没有循环依赖的状况下,提早代理对象的创立,使 Bean 的创立合乎 Spring 的设计准则。

如何获取依赖

通过一个 getSingleton() 办法去获取所须要的 Bean 的。

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) {
                    // Bean 工厂中获取 Bean
                    singletonObject = singletonFactory.getObject();
                    // 放入到二级缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

当 Spring 为某个 Bean 填充属性的时候,它首先会寻找须要注入对象的名称,而后顺次执行 getSingleton() 办法失去所需注入的对象,而获取对象的过程就是先从一级缓存中获取,一级缓存中没有就从二级缓存中获取,二级缓存中没有就从三级缓存中获取,如果三级缓存中也没有,那么就会去执行 doCreateBean() 办法创立这个 Bean。

二级缓存可能解决循环依赖吗

第三级缓存的目标是为了提早代理对象的创立,因为如果没有依赖循环的话,那么就不须要为其提前创立代理,能够将它提早到初始化实现之后再创立。

既然目标只是提早的话,那么咱们是不是能够不提早创立,而是在实例化实现之后,就为其创立代理对象,这样咱们就不须要第三级缓存了。因而,咱们能够将 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)) { // 判断一级缓存中不存在此对象
            object o = singletonFactory.getObject(); // 间接从工厂中获取 Bean
            this.earlySingletonObjects.put(beanName, o); // 增加至二级缓存中
            this.registeredSingletons.add(beanName);
        }
    }
}

这样的话,每次实例化完 Bean 之后就间接去创立代理对象,并增加到二级缓存中。测试后果是齐全失常的,Spring 的初始化工夫应该也是不会有太大的影响,因为如果 Bean 自身不须要代理的话,是间接返回原始 Bean 的,并不需要走简单的创立代理 Bean 的流程。

测试证实,二级缓存也是能够解决循环依赖的。为什么 Spring 不抉择二级缓存,而要额定多增加一层缓存呢?

如果 Spring 抉择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都须要在实例化实现之后就立马为其创立代理,而 Spring 的设计准则是在 Bean 初始化实现之后才为其创立代理。所以,Spring 抉择了三级缓存。然而因为循环依赖的呈现,导致了 Spring 不得不提前去创立代理,因为如果不提前创立代理对象,那么注入的就是原始对象,这样就会产生谬误。

解决循环依赖总结

Spring 设计了三级缓存来解决循环依赖问题。
第一级缓存外面存储残缺的 bean 实例,这些实例是能够间接被应用的;
第二级缓存外面存储的实例化当前然而还没有设置属性值的 bean 实例,也就是 bean 外面的依赖注入还没有做;
第三级缓存用来寄存 bean 工厂,它次要用来生成原始 bean 对象,并且放到第二个缓存外面。

三级缓存的核心思想就是把 bean 的实例化和 bean 外面的依赖注入进行拆散,采纳一级缓存存储残缺的 bean 实例,采纳二级缓存来贮存不残缺的 bean 实例。通过不残缺的 bean 实例作为突破口,解决循环依赖问题。至于第三级缓存,次要是解决代理对象的循环依赖问题。

spring 无奈解决的循环依赖场景

  1. 多实例的 Setter 注入导致的循环依赖,须要把 bean 改成单例。
  2. 结构器注入导致的循环依赖,能够通过 @Lazy 注解。
  3. DependsOn 导致的循环依赖,找到注解循环依赖的中央,使它不循环依赖。
  4. 单例代理对象 Setter 注入导致的循环依赖,能够应用 @Lazy 注解;或者应用 @DependsOn 注解指定加载先后关系。

多例、结构器注入为什么不能解决循环依赖?

因为循环依赖的原理是实例化后提前裸露的援用,这两种状况还没实例化

正文完
 0