面試官:詳細介紹一下Spring的循環依賴

架構的小事 2024-05-16 03:02:55
一、什麽是循環依賴

Spring 循環依賴是指:兩個不同的 Bean 對象,相互成爲各自的字段,當這兩個 Bean 中的其中一個 Bean 進行依賴注入時,會陷入死循環,即循環依賴現象。

代碼例子:

@Componentpublic UserServiceA {    @Autowire    private UserServiceB userServiceB;}@Componentpublic UserServiceB {    @Autowire    private UserServiceA userServiceA;}UserServiceA 與 UserServiceB 之間相互依賴二、三級緩存解決循環依賴2.1 三級緩存

針對循環依賴的現象,Spring 中使用提供了三級緩存解決循環的問題,我們先看 Spring 中使用到三級緩存的代碼:

public DefaultSingletonBeanRegistry implements SingletonBeanRegistry {​    /**     * 一級緩存     */    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();​    /**     * 二級緩存     */    private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();​    /**     * 三級緩存     */    private Map<String, ObjectFactory<?>> singletonFactory = new HashMap<>();​ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }}SingletonObjects:一級緩存,存儲完整的 Bean;EarlySingletonObjects:二級緩存,存儲從第三級緩存中創建出代理對象的 Bean,即半成品的 Bean;SingletonFactory:三級緩存,存儲實例化完後,包裝在 FactoryBean 中的工廠 Bean;

在上面的 getSingleton 方法中,先從 SingletonObjects 中獲取完整的 Bean,如果獲取失敗,就從 EarlySingletonObjects 中獲取半成品的 Bean,如果 EarlySingletonObjects 中也沒有獲取到,那麽就從 SingletonFactory 中,通過 FactoryBean 的 getBean 方法,獲取提前創建 Bean。如果 SingletonFactory 中也沒有獲取到,就去執行創建 Bean 的方法。

2.2 解決循環依賴

Spring 産生一個完整的 Bean 可以看作三個階段:

createBean:實例化 Bean;populateBean:對 Bean 進行依賴注入;initializeBean:執行 Bean 的初始化方法;

産生循環依賴的根本原因是:對于一個實例化後的 Bean,當它進行依賴注入時,會去創建它所依賴的 Bean,但此時它本身沒有緩存起來,如果其他的 Bean 也依賴于它自己,那麽就會創建新的 Bean,陷入了循環依賴的問題。

所以,三級緩存解決循環依賴的根本途徑是:當 Bean 實例化後,先將自己存起來,如果其他 Bean 用到自己,就先從緩存中拿,不用去創建新的 Bean 了,也就不會産生循環依賴的問題了。過程如下圖所示:

在 Spring 源碼中,調用完 createInstance 方法後,然後就把當前 Bean 加入到 SingletonFactory 中,也就是在實例化完畢後,就加入到三級緩存中;2.3 爲什麽需要三級緩存?一層和兩層可以嗎?2.3.1 一級緩存失效的原因

對于只使用一級緩存的情況,是不能夠解決循環依賴的,有下面兩個原因:

如果只使用一級緩存,在創建 Bean 的過程中,我們會在初始化完畢(注意是初始化,如果只有一級緩存,該緩存需要存儲完整的 Bean)後把 Bean 放入到緩存中。此時依然會産生循環依賴問題,因爲依賴注入的過程是在初始化之前的,依賴注入時是從緩存中獲取不了對應的 Bean,從而再次引起循環依賴問題。那如果我們提前在 Bean 實例化後就放入到緩存呢?答案也是不行的。因爲我們沒有考慮到代理對象(Spring AOP) ,如果我們創建的 Bean 是代理對象,就要在實例化後就要創建出來,那麽就會帶來新的問題:JDK Proxy 代理對象實現的是目標類的接口,在進行依賴注入時會找不到對應的屬性和方法而報錯(也就是說,提前創建出來的代理對象是沒有原來對象的屬性和方法的)。

Spring AOP 是依賴于 AnnotationAwareAspectJAutoProxyCreator 的,這是一個後置處理器,在 Bean 實例化完畢後在初始化方法中才執行的。

2.3.2 不使用二級緩存的原因

先說結論,使用二級緩存是可以的。

對于普通對象來說,使用二級緩存是可以解決循環依賴的。在實例化後把對象存入第一級緩存中,如果其他對象依賴注入該對象,就從第一級緩存中拿就行了。對象初始化完畢後,再寫入到第二級緩存中即可。

但是對于代理對象來說,就顯得十分麻煩了。如果循環依賴注入的對象是代理對象,我們就需要在對象實例化後提前把代理對象創建出來,即提前創建出所有的代理對象。但是在目前 Spring AOP 的設計來說,代理對象的創建是在初始化方法中的 AnnotationAwareAspectJAutoProxyCreator 後置處理器創建的。這與 Spring AOP 的代理設計原則是相違背的。

所以,Spring 就再引用了一層緩存 SingletonFactory,存儲著 FactoryBean,我們來看看代碼:

if (beanDefinition.isSingleton()) {    Object finalBean = bean;    //加入FactoryBean    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));}

當我們調用這個 FactoryBean 的 getBean 方法時:

protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) {​    Object exposedObject = bean;    List<BeanPostProcessor> beanPostProcessors = getBeanPostProcessors();    for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {        if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {            exposedObject = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).getEarlyBeanReference(bean, beanName);            if (exposedObject == null) {                return exposedObject;           }       }   }    return exposedObject;}​// AnnotationAwareAspectJAutoProxyCreator@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {    earlyProxyReferences.add(bean);    return wrapIfNecessary(bean,beanName);}

發現它其實通過 InstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 來創建代理對象。所以對于代理對象來說,Spring 沒有直接提前創建,而是在它産生循環依賴時,再通過 getEarlyBeanReference 方法來創建代理對象的。

三、循環依賴被完全解決了嗎3.1 循環依賴只支持單例對象

對于 scope 爲 property 的 Bean,三級緩存是沒有解決循環依賴的。因爲它們的作用域是原型,每次使用到時都會創建一個新對象,不進緩存!

   @Override    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object... args) throws BeansException {​    Object bean = null;    try {        // 加入一級緩存        if (beanDefinition.isSingleton()) {            Object finalBean = bean;            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));       }​   } catch (Exception e) {        throw new BeansException("Instantiation of bean failed", e);   }​    Object exposeObject = bean;    if (beanDefinition.isSingleton()) {​        // 從二級緩存中獲取對象        exposeObject = getSingleton(beanName);        registerSingleton(beanName, exposeObject);   }    return exposeObject;}可見,在加入緩存時,都會判斷當前的 Bean 是不是 Singleton 的,如果不是就不加入到緩存中。3.2 通過構造器注入的類無法解決循環依賴@Componentpublic BeanB {    private BeanA a;    public BeanB(BeanA a) {        this.a = a;   }}​@Componentpublic BeanA {    private BeanB b;    public BeanA(BeanB b) {        this.b = b;   }}

上述代碼中,我們通過構造器來注入 BeanA 和 Bean B,Spring 是無法解決它們循環依賴的問題的。

因爲在調用 BeanA 的構造器方法時 BeanA 是沒有實例化完成的,緩存中不存在 BeanA 對象,此時就要注入 BeanB 對象,毫無疑問的會産生循環依賴的問題。

但是,對于這種情況來說,我們可以通過 @Lazy 注解來延遲 BeanB 的加載。即調用構造器方法時先不創建 BeanB 對象,當使用到BeanB 對象時才繼續創建,此時 BeanB 也已經創建完成了,就不會産生循環依賴的問題了。

@Componentpublic BeanA {    private BeanB b;    public BeanA(@Lazy BeanB b) {        this.b = b;   }}
0 阅读:125

架構的小事

簡介:感謝大家的關注