1. 程式人生 > >Spring原始碼閱讀——Bean的載入和獲取過程

Spring原始碼閱讀——Bean的載入和獲取過程

我們經常使用Spring,並且也都瞭解其大概原理。我想我們一定會對Spring原始碼的解讀有迫切的渴望。 我也如此。所以,我打算閱讀一下Spring的原始碼。再此之前,我也為此準備了很多。包括,去複習熟練java反射,理解常用的設計模式。當然,這些複習筆記也會在今後的複習中順便記錄在我的csdn部落格。(當然,可能寫的不好,也可能理解不正確(可以一起交流嘛)。但是樂於分享總歸是好的。) 首先看下spring的各個元件。


可以看到,在Core Container(核心容器)中包含有Core、Beans、Context和Spring Expression Language.Core和Beans模組是Spring框架的基礎部分,提供IoC控制反轉和依賴注入的特性。 Core模組主要包含著Spring框架基本的核心工具類,供其它元件使用。 Beans模組是所有應用都要用到的,它包含訪問配置檔案、建立和管理bean以及ioc、依賴注入。 Context模組構建於Core和Beans之上。是spring的上下文環境,為Spring核心提供了大量的擴充套件,天津捱了對國際化、事件傳播、資源載入等支援。ApplicationContext介面是Context模組的關鍵。 Spring Expression Language為Spring提供了一個強大的表示式語言用於在執行時查詢草操縱物件 。 現在我們已經瞭解了Spring的基礎元件,我們現在就在程式碼中跟蹤一下Spring Bean的建立和獲取過程。 beans.xml
<?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 id="hello" class="bean.HelloSpring" lazy-init="false"></bean>

</beans>
HelloSpring.java
package bean;

/**
 * Created by yuyufeng on 2016/11/17.
 */
public class HelloSpring {
    private String name;

    public HelloSpring() {
        System.out.println("##HelloSpring.HelloSpring初始化……………………………………");
    }

    public HelloSpring(String name) {
        this.name = name;
    }

    public void sayHello(String something){
        System.out.println("hello"+something);
    }

    public String getName() {
        return name;
    }

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

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

BeanFactoryTest.java
package spring.ioc;

import bean.HelloSpring;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

/**
 * Created by yuyufeng on 2016/11/18.
 * Spring中Bean的載入過程
 */
public class BeanFactoryTest {
    public static void main(String[] args) {
        //spring如何初始化有兩種方式 beanFactory applicationContext
        Resource resource = new ClassPathResource("spring/ioc/beans.xml");
        BeanFactory beanFactory = new XmlBeanFactory(resource);
        HelloSpring helloSpring = (HelloSpring) beanFactory.getBean("hello");
        helloSpring.sayHello("張三");
    }
}


先從表面上可以看到 bean的載入可大致可以分為:從xml讀取bean的資訊載入到Spring容器中,通過xml配置的id從Spring容器反射得到這個類的例項物件。 現在,我們進行詳細分析 1.Resource resource = new ClassPathResource("spring/ioc/beans.xml"); 我們通過Sring Core模組的工具從本地獲得了xml資源,並生成Resource物件。這一過程就不詳細跟進了。 2.通過XmlBeanFactory來建立BeanFactory物件。 直接debug進入
3.首先我們會跟進 DefaultSingletonBeanRegistry 其中有靜態物件需要例項化。至於為什麼會跟進這個類,我們來看下類的繼承關係就知道了(為什麼會先例項其中的靜態類,可以複習以下java物件的例項順序)
4.接著會進入DefaultListableBeanFactory建立裡面的靜態物件例項以及執行裡面的靜態模組

5.通過類載入器注入DefaultListableBeanFactory物件 然後又例項化了一個存放DefaultListableBeanFactory的map
6.接著再執行XmlBeanFactory的構造方法,其中把配置檔案Resource賦值給了resource

7.執行this.reader.loadBeanDefinitions(resource); //可以看到這個步驟是要把resource載入到容器中去了。這裡是整個資源載入進入的切入點。
8.接著再跟進,直到進入public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException ;方法
9.對匯入資源再進行一定包裝處理後進入doLoadBeanDefinitions(inputSource, encodedResource.getResource()); //對於encode我們是比較熟悉的 肯定是處理編碼相關的
10.現在已經進入到了XmlBeanDefinitionReader.java, 再包裝處理(畢竟xml檔案規則什麼的驗證啊 獲取比較麻煩,不知道你暈了沒有) xml還是包裝成了Document委託給DocumentLoader去處理執行


11.現在又進入public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException ; 程式結果以上的處理,已經獲取了xml文件的document,已經可以準備提取註冊bean了。
12.經過documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 最終我們獲取到了root,protected void doRegisterBeanDefinitions(Element root)這個方法,開始真正的解析已經處理過的資源。
13.解析完成後就是註冊了, debug到如下程式碼
14.可以看到在這裡,把bean存到了beanDefinitionMap中,
對於beanDefinitionMap是什麼,就是存在記憶體中的map,bean就存在裡面供外部獲取。
跟蹤了這麼多的原始碼,肯定有點亂。做下總結吧。 Spring中bean的載入過程 1.獲取配置檔案資源 2.對獲取的xml資源進行一定的處理檢驗 3.處理包裝資源 4.解析處理包裝過後的資源 5.載入提取bean並註冊(新增到beanDefinitionMap中) 至於bean的獲取,那就比上面的簡單多了。 斷點進入AbstractBeanFactory
入口
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
進入之後
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
   // Quick check on the concurrent map first, with minimal locking.
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
   if (mbd != null) {
      return mbd;
}
   return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
再進入
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
我們發現又進入了 DefaultListableBeanFactory.java,是不是有種熟悉的感覺。
當你看到這條語句,你就豁然開朗了,
BeanDefinition bd = this.beanDefinitionMap.get(beanName);
就是之前載入bean放入到的map嗎? 其實整個過程還是比較容易理解的,就是裡面的包裝解析很複雜