天天看點

spring依賴注入——循環依賴

上一篇部落格簡單地分析了下依賴注入。但是對于依賴注入的很多細節,都沒有深入的分析。這一篇部落格會繼續分析spring的依賴注入。這篇部落格會解決分析getBean緩存時候遺留下來的循環依賴問題。

循環依賴分析

首先明确下,隻有單例情況下,spring才會試着去解決循環依賴問題,多例是不會去解決循環依賴的。這個也好了解,如果是多例的話,比如a -> b 并且 b -> a 那麼,當A a=new A(); 之後要注入b,b卻是多例的,那麼究竟該注入哪個B是不确定的。如下圖:

spring依賴注入——循環依賴

接下來我們分析,為啥會有循環依賴的問題。

先來分析沒有循環依賴的問題

public static class A{
    
    private B b;

    //省略get和set方法
}

public static class B{
}
           

這個時候,如果spring先初始化A,然後會發現A依賴于B,然後就會初始化B,最後注入到A裡。

整個流程用代碼表示大概如下所示:

A a = 建立A
B b = 建立B
        -----> 建立A子流程  a.setB(b);
           

但是假設B也依賴了A呢?即

public static class B{
    
    private A a;

}
           

那樣依賴注入過程就會變成(簡單示例)

A a = 建立A
B b = 建立B
       ---->  建立B子流程 b.setA(????);  //  這個時候A還沒建立完成呢
a.setB(b);
           

那麼如何解決呢?很簡單,把那個還沒建立完的A(隻是new了,但是沒有進行依賴注入的A)set到B裡就好了。

spring依賴注入——循環依賴

弄清了這個流程之後,我們再來分析spring是如何進行依賴注入的。

spring對引用類型的注入

這裡我先從spring對引用屬性的注入開始。

<bean id="person" class="com.hdj.learn.spring.demo.Person">
    <property name="car" ref="car"></property>
</bean>
           

即ref的注入

private Object resolveReference(Object argName, RuntimeBeanReference ref) {
    try {
        String refName = ref.getBeanName();
        refName = String.valueOf(doEvaluate(refName));
        if (ref.isToParent()) {
            if (this.beanFactory.getParentBeanFactory() == null) {
                throw new BeanCreationException(
                        this.beanDefinition.getResourceDescription(), this.beanName,
                        "Can't resolve reference to bean '" + refName +
                        "' in parent factory: no parent factory available");
            }
            return this.beanFactory.getParentBeanFactory().getBean(refName);
        }
        else {
            Object bean = this.beanFactory.getBean(refName);
            this.beanFactory.registerDependentBean(refName, this.beanName);
            return bean;
        }
    }
    catch (BeansException ex) {
        throw new BeanCreationException(
                this.beanDefinition.getResourceDescription(), this.beanName,
                "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
    }
}
           

實作很簡單,就是從beanFactory裡擷取要依賴的對象

我們再來回顧下流程

spring依賴注入——循環依賴

問題是,當走到6時候,似乎又會回到1,這樣不就死循環了麼?

重點就是,第六步的擷取A,我們回到doGetBean方法中。

getSingleton 方法

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) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
           

我們在分析初始化bean的

緩存部分

時,曾分析過這幾個緩存。當時其實隻知道了singletonObjects是存儲了已經建立了的對象。

現在讓我們回頭再看看這些緩存。

首先看看singletonFactories指派的地方。

doCreateBean

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isDebugEnabled()) {
        logger.debug("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            //預設實作傳回bean
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
}
           

在建立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)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}
           

這裡就碰見了我們要分析的singletonFactories,它存儲了beanName -> 此處的匿名内部類singletonFactory。

singletonFactory

我們再回到getSingleton方法,以及之前繪制的圖

ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}
           

邏輯很簡單

spring依賴注入——循環依賴

三級緩存

我們經常聽說spring通過三級緩存解決了循環依賴,其實三級緩存非常簡單。就是指我們分析過的

Map<String, Object> singletonObjects

Map<String, ObjectFactory<?>> singletonFactories

Map<String, Object> earlySingletonObjects

這裡分别舉這樣三個例子。

A 依賴 B(B不依賴A)

spring依賴注入——循環依賴

一級緩存

A 依賴 B && B依賴A

spring依賴注入——循環依賴

A依賴B && B依賴A + B依賴C && C 依賴 A

spring依賴注入——循環依賴

二級緩存

至此所謂的三級緩存及其使用,應該就非常的清楚了。

總結下

這篇部落格分析了spring依賴注入中,循環依賴的問題,分析了在spring初始化bean時,各級緩存的作用。應該算是挺清晰的了。