4. bean的加載

spring源碼學(xué)習(xí)筆記,要點(diǎn)歸納和代碼理解

前言

經(jīng)過了三節(jié)的的鋪墊,終于到了對象創(chuàng)建的核心部分了,bean的整個加載過程代碼量非常大,邏輯錯綜復(fù)雜,因此這部分我的原則是以理解和把控關(guān)鍵過程和邏輯為基本,不局限于細(xì)節(jié)實(shí)現(xiàn),以AbstractBeanFactorydoGetBean方法為骨架閱讀抽象出的方法代碼,對于重點(diǎn)邏輯詳細(xì)解析.

bean加載的整體流程

首先貼出精簡了異常處理邏輯的doGetBean方法代碼

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        // 轉(zhuǎn)換beanName,將alias或id或className統(tǒng)一轉(zhuǎn)換為beanName
        final String beanName = transformedBeanName(name);
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        // 從緩存中嘗試獲取實(shí)例
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            // 緩存中取出的不一定是創(chuàng)建好的實(shí)例,有可能是提早曝光加入到緩存的factoryBean
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        } else {
            // Fail if we're already creating this bean instance:
            // We're assumably within a circular reference.
            // 當(dāng)前bean如果在創(chuàng)建過程中,則發(fā)生循環(huán)依賴,拋出異常
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }

            // Check if bean definition exists in this factory.
            // 檢測父類工廠,此處為了保證bean的父類能夠被先于子實(shí)例加載
            BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                String nameToLookup = originalBeanName(name);
                if (parentBeanFactory instanceof AbstractBeanFactory) {
                    return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                            nameToLookup, requiredType, args, typeCheckOnly);
                }
                else if (args != null) {
                    // Delegation to parent with explicit args.
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else if (requiredType != null) {
                    // No args -> delegate to standard getBean method.
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
                else {
                    return (T) parentBeanFactory.getBean(nameToLookup);
                }
            }
            // 標(biāo)記已創(chuàng)建
            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);
            }

                final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        registerDependentBean(dep, beanName);
                        getBean(dep);
                        
                    }
                }

                // Create bean instance.
                if (mbd.isSingleton()) { // 創(chuàng)建單例bean
                    sharedInstance = getSingleton(beanName, () -> {
                        return createBean(beanName, mbd, args);
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

                else if (mbd.isPrototype()) { // 創(chuàng)建多例
                    // It's a prototype -> create a new instance.
                    Object prototypeInstance = null;
                    try {
                        beforePrototypeCreation(beanName);
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }

                else {
                    String scopeName = mbd.getScope();
                    final Scope scope = this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }
                        Object scopedInstance = scope.get(beanName, () -> {
                            beforePrototypeCreation(beanName);
                            return createBean(beanName, mbd, args);
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    
                }
            
        }

        // Check if required type matches the type of the actual bean instance.
        // 類型檢查
        if (requiredType != null && !requiredType.isInstance(bean)) {
                T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                if (convertedBean == null) {
                    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                }
                return convertedBean;
            
        }
        return (T) bean;
    }

整個加載過程的步驟大致如下:

  1. 轉(zhuǎn)換對應(yīng)的beanName
  2. 嘗試從緩存中加載實(shí)例
  3. bean的實(shí)例化
  4. prototype的依賴檢查
  5. 檢測parentBeanFactory
  6. 將GernericBeanDefinition 轉(zhuǎn)換為RootBeanDefinition
  7. 尋找依賴
  8. 針對不同scope創(chuàng)建bean
  9. 類型轉(zhuǎn)換

循環(huán)依賴的處理

  • 首先解釋下什么是循環(huán)依賴:
    對于兩個class A和B,以及他們對應(yīng)的bean a和b,A中有類型為B的屬性b,B中有類型為A的屬性a,兩個對象都通過spring管理并且屬性都通過spring自動注入,此時便形成了循環(huán)依賴.
  • spring對于循環(huán)依賴的解決:
    整體思路是:
  1. 使用factoryBean封裝創(chuàng)建對象的邏輯
  2. 將正在創(chuàng)建的bean對應(yīng)的factoryBean暴露到緩存中
  3. 創(chuàng)建bean時先嘗試從幾個緩存中取出bean或bean的factoryBean

主要應(yīng)用到四個對象和兩個方法:

  1. singletonObjects: 保存創(chuàng)建的bean和beanName的關(guān)系
  2. singletonFactories: 保運(yùn)BeanName和創(chuàng)建bean的工廠之間的關(guān)系
  3. earlySingletonObjects: 保存beanName和bean實(shí)例之間的關(guān)系,與singletonObjects的區(qū)別是,當(dāng)一個單例bean被放到這里后,那么即使bean還在創(chuàng)建過程中,也可以通過getBean方法從這個緩存中獲取
  4. registeredSingletons: 用來保存的當(dāng)前所有已注冊的bean
    DefaultSingletonBeanRegistry中的兩個方法
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);
            }
        }
    }
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;
    }
  • 具體步驟:
  1. spring容器創(chuàng)建單例TestBeanA, 根據(jù)無參構(gòu)造器創(chuàng)建,并通過ObjectFactory接口暴露getEarlyBeanReference()方法,將beanName和封裝該方法的ObjectFactory添加到singletonFactory緩存中
  2. 在執(zhí)行A的populateBean方法,即裝配A的屬性時,發(fā)現(xiàn)B的依賴,執(zhí)行g(shù)etBean(B)方法
  3. 創(chuàng)建單例B,將B的ObjectFactory加入到singletonFactory,之后繼續(xù)執(zhí)行B的populateBean,
  4. 發(fā)現(xiàn)依賴注入中存在TestBeanA,調(diào)用getBean(A),這時會通過getSingle(A)的方法調(diào)用到之前暴露的getEarlyBeanReference方法,這個方法如下,會返回a的實(shí)例.
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;
    }

這里附一張調(diào)用棧的截圖,一目了然.


  • 需要注意的:
    spring只會針對singleton的setter注入解決循環(huán)依賴,對于構(gòu)造器循環(huán)依賴和prototype的循環(huán)依賴會直接拋出異常,這是因?yàn)閟pring只緩存single scope的bean,也就是說上述代碼邏輯對prototype不適用.而spring將在創(chuàng)建過程中的bean放入一個threadLocal的prototypesCurrentlyInCreation對象中,通過構(gòu)造器注入的對象會無法通過此處校驗(yàn)而拋出異常.
    可以通過((XmlBeanFactory) bf).setAllowCircularReferences(false);來禁用循環(huán)引用

創(chuàng)建bean

創(chuàng)建bean的實(shí)現(xiàn)通過lambda表達(dá)式將AbstractBeanFactory的doGetBean方法封裝到factoryBean中進(jìn)行的.

  • 主要步驟:
  1. 創(chuàng)建bean的實(shí)例
    a. 確定構(gòu)造函數(shù)
    b. 判斷beanDefinition中的getMethodOverrides,決定直接反射構(gòu)造bean還是通過動態(tài)代理的方式構(gòu)造bean,是AOP實(shí)現(xiàn)的切入點(diǎn)
  2. 記錄創(chuàng)建bean的ObjectFactory, 循環(huán)依賴的解決
  3. 屬性注入
  4. 初始化bean
    a. 激活aware方法 -- 為實(shí)現(xiàn)Aware接口的的bean對象注入對應(yīng)的對象
    b. 處理器的應(yīng)用 -- 為BeanFactory增加BeanPostProcessor,一種增強(qiáng)器,在執(zhí)行初始化方法之前和之后執(zhí)行,可以添加業(yè)務(wù)邏輯
    c. 激活自定義的init方法 -- 配置文件的init-method配置
  5. 注冊DisposableBean

后記

bean的加載可以說是spring的兩大核心之一了,也是aop實(shí)現(xiàn)的切入點(diǎn),這部分代碼多多閱讀,可以把控spring的整體運(yùn)行原理,對于一些定制的需求也可以更靈活的實(shí)現(xiàn).
對于復(fù)雜的邏輯進(jìn)行分解,分成N個小函數(shù)的嵌套,每一層都是對下一層邏輯的總結(jié)和概要,這樣使得每一層邏輯會變得簡單容易理解.

附 測試代碼

  • bean加載的各個情況
public class MyTestBean {

    private String name = "testName";

    private Integer id;

    public MyTestBean(Integer id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "MyTestBean{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }

    public MyTestBean() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private void init() {
        System.out.println("測試bean被初始化了");
    }
}
public abstract class LookupTestBean {

    public void doSomething() {
        System.out.println(getTestBean());
    }

    public abstract MyTestBean getTestBean();
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean name="myTestBean" class="com.pctf.beans.MyTestBean" init-method="init">
        <property name="name" value="測試對象"></property>
        <constructor-arg index="0" value="10"></constructor-arg>

    </bean>

    <bean name="lookupTestBean" class="com.pctf.beans.LookupTestBean">
        <lookup-method name="getTestBean" bean="myTestBean"></lookup-method>
    </bean>

    <beans profile="dev">

    </beans>
</beans>
public class AppTestLaunch {

    public static void main(String[] args) {
        XmlBeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml"));
        bf.addBeanPostProcessor(new PostProcessorTest());
        MyTestBean myTestBean = (MyTestBean) bf.getBean("myTestBean");
        LookupTestBean lookupTestBean = (LookupTestBean) bf.getBean("lookupTestBean");
        lookupTestBean.doSomething();
        System.out.println(myTestBean.getName());
    }
}
  • 循環(huán)依賴的測試代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.pctf.beans.TestBeanA" name="testBeanA">
        <property name="name" value="測試BeanA"></property>
        <!--<property name="testBeanB" ref="testBeanB"></property>-->
        <constructor-arg value="testBeanB" index="0"></constructor-arg>
    </bean>

    <bean class="com.pctf.beans.TestBeanB" name="testBeanB">
        <property name="name" value="測試BeanB"></property>
        <!--<property name="testBeanA" ref="testBeanA"></property>-->
        <constructor-arg value="testBeanA" index="0"></constructor-arg>
    </bean>

</beans>
import com.pctf.beans.TestBeanA;
import com.pctf.beans.TestBeanB;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class BeanLoadingTest {

    public static void main(String[] args) {
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("bean-loading-config.xml"));
        //((XmlBeanFactory) bf).setAllowCircularReferences(false);
        TestBeanA testBeanA = (TestBeanA) bf.getBean("testBeanA");
        TestBeanB testBeanB = bf.getBean(TestBeanB.class);
        System.out.println(testBeanA.getName());
        System.out.println(testBeanB.getName());
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring源碼解析——Bean的加載前奏 User user = (User)context.getBean("...
    仗劍詩篇閱讀 963評論 0 2
  • IOC和DI是什么? Spring IOC 的理解,其初始化過程? BeanFactory 和 FactoryBe...
    justlpf閱讀 3,595評論 1 21
  • 我知道你的那顆真心,也被人傷害過。 如果你沒有和那個不嫌棄你一無所有的人結(jié)婚,那么你未來遇到的人...
    阿敏兒呀閱讀 400評論 0 1
  • 人為什么要善良,這是我聽過最好的答案 01 微博上看到這樣一段話: 出租車,剛上車幾分鐘,司機(jī)突然把車停在路邊,聲...
    菩提大叔閱讀 435評論 0 9
  • 一、朱小妍家的機(jī)密 寒假總是快樂又無聊的。這天早上馬琪吃完早飯窩在沙發(fā)里看電視,朱小妍又在窗外喊她去踢毽子。馬琪不...
    琬琰之章閱讀 674評論 1 3

友情鏈接更多精彩內(nèi)容