1. 程式人生 > >spring載入bean的過程

spring載入bean的過程

首先,我在這裡舉個demo,大致演示一下怎麼獲取配置檔案中的bean:

一個applicationContext.xml配置檔案,這個不可少;
一個bean,這裡我沒用介面,直接用一個普通的類做為Spring的bean;
一個Junit測試類;

applicationContext.xml中的程式碼如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <bean id="myBean" class="com.hushenjian.MyBean"></bean>
</beans>
MyBean類中的程式碼如下:
public class MyBean{
    public void getName(String name) {
        System.out.println("我的名字是:" + name);
    }
}
單元測試類的程式碼如下:
public class MyTest {

      public static void main(String[] args) {
          ClassPathResource res = new ClassPathResource("applicationContext.xml");
          
          XmlBeanFactory bf = new XmlBeanFactory(res);
          
          MyBean bean = (MyBean)beanFactory.getBean("myBean");
          
         bean.getName("hushenjian");
     }
 }
執行單元測試,打印出“我的名字是:hushenjian”,測試類只有四行程式碼,但Spring到底為我們做了些什麼?下面我們就基於這樣的場景去分析bean的載入過程。

分析:

(1) 獲取配置檔案
ClassPathResource res = new ClassPathResource("applicationContext.xml");
這一句只是讀入配置檔案,並封裝成Spring提供的Resource物件,供後面邏輯使用。
    在Spring內部,有超過十個以Resource結尾的類或檔案,他們處理不同型別的資原始檔,如FileSystemResource、ClassPathResource、UrlResource等,處理過程大同小異。
(2) 解析配置檔案並註冊bean
XmlBeanFactory bf = new XmlBeanFactory(res);
這裡面的邏輯相當複雜,涉及到眾多Factory、Reader、Loader、BeanDefinition、Perser、Registry系列介面和類,但他們做的基本事情就是將applicationContext.xml配置的Bean資訊構成BeanDefinition物件,然後放到Factory的map中(這一步就是所謂的註冊),這樣以後程式就可以直接從Factory中拿Bean資訊了
    要跟蹤這個處理過程,大致流程如下:
    a. 構造XmlBeanFactory時,會呼叫Reader物件的loadBeanDefinitions方法去載入bean定義資訊
    b. 在Reader物件的doLoadBeanDefinitions驗證文件(配置檔案)模式,然後通過documentLoader物件處理資源物件,生成我們Document物件;
    c. 呼叫BeanDefinitionDocumentReader物件的doRegisterBeanDefinitions去註冊bean定義資訊;
    d. parseBeanDefinitions從xml文件根節點遞迴迴圈處理各個節點,對bean節點真正的處理工作委託給了BeanDefinitionParserDelegate,方法parseBeanDefinitionElement將一個bean節點轉換成一個BeanDefinitionHolder物件,這才是最終的解析過程;
    e. DefaultListableBeanFactory.registerBeanDefinition利用解析好的beanDefinition物件完成最終的註冊,其實就是把beanName和beanDefinition作為鍵值對放到beanFactory物件的map;

(3) 例項化Bean
MyBean bean = (MyBean)bf.getBean("myBean");
    這一步Spring同樣做了複雜的處理,但基本原理就是利用反射機制,通過bean的class屬性建立一個bean的例項,例子中是建立了一個StudentBean物件。

(4) 呼叫物件的方法,沒什麼好說的。當然如果方法上做了事務、AOP之類的宣告,這一步的處理就不會那麼簡單了。

下面來分析一下bean的解析和註冊過程:

bean的解析和註冊過程就是下面這句程式碼執行的:

XmlBeanFactory bf = new XmlBeanFactory(res);
其具體程式碼為:
public class XmlBeanFactory extends DefaultListableBeanFactory {
    //這裡為容器定義了一個預設使用的bean定義讀取器
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }
    //在初始化函式中使用讀取器來對資源進行讀取,得到bean定義資訊。
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }
跟進去之後發現實際呼叫了XmlBeanDefinitionReader物件的loadBeanDefinitions方法

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //封裝資原始檔
        return loadBeanDefinitions(new EncodedResource(resource));
    }
InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
這個方法是整個資源載入的切入點,我們先大致看看這個方法的處理流程: a. 封裝資原始檔
    new EncodedResource(resource)
b. 獲取輸入流
    從EncodedResource物件中獲取InputStream並構造InputSource物件
c.
然後呼叫doLoadBeanDefinitions方法完成具體的載入過程 3.2 doLoadBeanDefinitions方法
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);

這個方法的程式碼很長,如果不考慮異常處理,其實只做了三件事情: a. 獲取對XML檔案的驗證模式
b. 載入XML檔案,並得到對應的Document物件
c. 根據返回的Document物件註冊bean資訊

這裡對驗證模式不進行討論;
這裡不對Document物件的載入過程進行討論;
這裡直接進入bean的註冊方法registerBeanDefinitions
3.3 registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        // 這裡定義解析器,使用XmlBeanDefinitionParser來解析xml方式的bean定義檔案 - 現在的版本不用這個解析器了,使用的是XmlBeanDefinitionReader
        if (this.parserClass != null) {
            XmlBeanDefinitionParser parser =
                    (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
            return parser.registerBeanDefinitions(this, doc, resource);
        }
        // 具體的註冊過程,首先得到XmlBeanDefinitionReader,來處理xml的bean定義檔案
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getBeanFactory().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getBeanFactory().getBeanDefinitionCount() - countBefore;
    }
當把文件轉換為Document物件後,提取及註冊bean就是我們的重頭戲了。
這裡並沒有看到我們想要的程式碼,而是把工作委託給了BeanDefinitionDocumentReader物件去處理
3.4 BeanDefinitionDocumentReader.doRegisterBeanDefinitions方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        Element root = doc.getDocumentElement();

        BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
        preProcessXml(root);
        parseBeanDefinitions(root, delegate);
        postProcessXml(root);
    }
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
            //這裡得到xml檔案的子節點,比如各個bean節點         
            NodeList nl = root.getChildNodes();
            //這裡對每個節點進行分析處理
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    String namespaceUri = ele.getNamespaceURI();
                    if (delegate.isDefaultNamespace(namespaceUri)) {
                        //這裡是解析過程的呼叫,對預設的元素進行分析比如bean元素
                        parseDefaultElement(ele, delegate);
                    }else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }
    }
終於走到了核心邏輯的底部doRegisterBeanDefinitions,如果說以前一直是XML載入解析的準備階段,
那麼這個方法算是真正地開始進行解析了。 這個方法的程式碼我們比較熟悉,讀取Document物件,迴圈每一個bean節點,然後進行處理。
Spring有兩類Bean,一個是預設的,一個是自定義的bean,這個方法對他們分別呼叫了不同方法進行處理。 3.5 對預設標籤的處理 processBeanDefinition方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }
a. 首先利用委託類的parseBeanDefinitionElement方法進行元素解析,返回BeanDefinitionHolder物件bdHolder,經過這個方法,bdHolder例項已經包含我們配置檔案中對bean的所有配置資訊了,如name、class等。
b. 對bdHolder進行裝飾
c. 解析完成後,要對bdHolder進行註冊,同樣,註冊過程委託給了BeanDefinitionReaderUtils去處理
3.6 delegate.parseBeanDefinitionElement(ele)
這個方法便是對預設標籤解析的全過程了,他將一個element節點轉換成BeanDefinitionsHolder物件,其中ele和bdHolder中的屬性是對應的。
a. 提取元素的id和name屬性
b. 進一步解析其他所有屬性並統一封裝到BeanDefinition型別的例項
c. 將獲取到的資訊封裝到BeanDefinitionHolder例項中。
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

AbstractBeanDefinition bd = createBeanDefinition(className, parent);

parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
            
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);

return new BeanDefinitionHolder(bd, beanName, aliasesArray);
剩下的工作便是註冊解析的BeanDefinition。 3.7 BeanDefinitionReaderUtils.registerBeanDefinition
這個方法並沒有做太多事情,而是直接呼叫了BeanDefinitionRegistry的註冊方法。BeanDefinitionRegistry是一個介面,有多個實現類,這裡我們使用了預設的實現DefaultListableBeanFactory。 3.8 DefaultListableBeanFactory.registerBeanDefinition
程式碼囉嗦了一大堆,實際上所謂的註冊,就是把beanName和beanDefinition物件作為鍵值對放到BeanFactory物件的beanDefinitionMap。
但Spring經常把簡單的邏輯寫的非常“囉嗦”,仔細分析程式碼,發現他完成了幾個事情:
a. 對bean物件的校驗
b. 檢查beanFactory中是否已經有同名的bean,如果有,進行相應處理
c. 把bean物件放到beanDefinitionMap中(這就是最終所謂的註冊)
d. 清除整個過程快取的物件資料


以上便是Spring對bean解析註冊的全過程,總結一下大致步驟:
1. 載入XML檔案,封裝成Resource物件
2. 呼叫Reader物件方法讀取XML檔案內容,並將相關屬性放到BeanDefinition例項
3. 將BeanDefinition物件放到BeanFactory物件