前言
對于Java開發(fā)者來說蜜唾,Spring無疑是最常用也是最基礎的框架之一杂曲。(此處省略1w字吹Spring)。相信很多同行跟我一樣袁余,只是停留在會用的階段擎勘,比如用@Component寫一個組件、用@Autowired注入其他組件等等颖榜,但是不知道為什么可以這么做棚饵,Spring是怎么實現(xiàn)的。為了了解這些掩完,我閱讀了《Spring源碼深度解析》噪漾,這本書講的很詳細,但是因為步驟多而復雜容易記混藤为,我就做了一下梳理怪与,先呈現(xiàn)大致流程,但對每個步驟進行詳細描述缅疟。
概念
概念上的東西還是要提一嘴的:
Spring用IoC容器來管理Bean分别。
BeanFactory和ApplicationContext是SpringIoC容器的兩種表現(xiàn)形式。
BeanFactory定義了簡單IoC容器的基本功能存淫。
ApplicationContext實現(xiàn)了BeanFactory耘斩,且通過繼承MessageSource、ResourceLoader桅咆、ApplicationEventPublisher接口括授,添加了許多高級容器的特性。
XmlBeanFactory
這里以XmlBeanFactory為代表岩饼,看容器是怎么工作的荚虚。
新建一個XmlBeanFactory很簡單,只需要你有一個符合格式的xml文件籍茧,里面用<bean>標簽設置你希望被容器加載的Bean:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("test.xml"))
新建了一個ClassPathResource資源對象作為參數(shù)傳入XmlBeanFactory的構(gòu)造函數(shù)版述。
點進XmlBeanFactory類,XmlBeanFactory會先調(diào)用父類構(gòu)造器寞冯,一直跟蹤到AbstractAutowireCapableBeanFactory渴析,會看到調(diào)用了三次ignoreDependencyInterface用來忽略給定接口的自動裝配功能(后面會提到);再調(diào)用this.reader.loadBeanDefinitions(Resource)吮龄,用自己持有的XmlBeanDefinitionReader解析傳入的資源俭茧。所以最宏觀的三個步驟:
1、新建了一個ClassPathResource資源漓帚。抽象出一個資源類來表示資源母债;
2、調(diào)用了ignoreDependencyInterface忽略指定接口的自動裝配功能胰默;
3场斑、委托XmlBeanDefinitionReader解析資源漓踢。
重點肯定在第三步了,點進XmlBeanDefinitionReader漏隐,到loadBeanDefinitions(EncodedResource)喧半,先從
Resource獲取InputStream構(gòu)造成InputSource,作為參數(shù)調(diào)用doLoadBeanDefinitions(InputSource, Resource)開始真正的解析青责。所以第三步下面是兩個小步驟:
3.1挺据、從Resource獲取輸入流;
3.2脖隶、調(diào)用doLoadBeanDefinitions繼續(xù)解析扁耐。
再看doLoadBeanDefinitions方法,主要是兩個方法产阱,doLoadDocument(inputSource, resource)解析輸入流返回一個Document對象婉称;registerBeanDefinitions(doc, resource)繼續(xù)解析Document返回解析的Bean數(shù)量,所以3.2下面是兩個步驟:
3.2.1构蹬、將資源解析成Document對象(這個步驟這邊就不展開了王暗,有興自究);
3.2.2庄敛、解析Document俗壹,提取注冊Bean。
來到registerBeanDefinitions方法藻烤,這里創(chuàng)建了一個BeanDefinitionDocumentReader對象負責具體解析(是不是覺得又冒出了不認識的類绷雏,框架就是這樣,遵循單一職責的原則怖亭,把一個集中的邏輯放到其他類中處理)涎显,它調(diào)用doRegisterBeanDefinitions(doc.getDocumentElement()),提取Document的root作為參數(shù)繼續(xù)解析兴猩,先查找解析profile屬性棺禾,將表示環(huán)境的屬性注冊到Environment;然后遍歷root每個子節(jié)點峭跳,如果是默認標簽,調(diào)用parseDefaultElement進行解析缺前;如果是自定義標簽蛀醉,就調(diào)用delegate.parseCustomElement。所以3.2.2下面是這幾個步驟:
3.2.2.1衅码、創(chuàng)建BeanDefinitionDocumentReader委托解析對象拯刁;
3.2.2.2、解析profile屬性到Environment逝段;
3.2.2.3垛玻、遍歷子節(jié)點割捅,繼續(xù)解析默認標簽和自定義標簽。
我們這邊主要分析默認標簽的解析帚桩,自定義的有興自究亿驾。首先根據(jù)標簽類型選擇不同的處理方法,類型分別是import账嚎、alias莫瞬、bean和beans。重點肯定是對bean標簽的解析郭蕉,進入processBeanDefinition方法疼邀,我們看到里面先委托BeanDefinitionParserDelegate解析出一個持有bean信息的BeanDefinitionHolder;如果BeanDefinitionHolder不為空且子節(jié)點下存在自定義標簽召锈,再解析它們旁振;然后對解析完成后的BeanDefinitionHolder進行注冊,注冊過程很簡單就是將BeanDefinitionHolder持有的beanName和BeanDefinition的鍵值對涨岁、beanName和每個alias別名的鍵值對保存在容器中拐袜;最后發(fā)出bean已注冊完成的事件通知,所以這里分為4步:
3.2.2.3.1卵惦、委托BeanDefinitionParserDelegate解析返回BeanDefinitionHolder阻肿;
3.2.2.3.2、解析存在的自定義標簽沮尿;
3.2.2.3.3丛塌、解析完成后注冊;
3.2.2.3.4畜疾、發(fā)出響應事件赴邻。
到了這里終于開始具體的解析,過程其實就是先解析出beanName啡捶、alias別名姥敛,然后把其余各種標簽,如class瞎暑、scope彤敛、lazy-init等屬性解析成用于屬性承載的BeanDefinition對象的成員變量,最后將beanName了赌、alias數(shù)組和BeanDefinition封裝成BeanDefinitionHolder對象返回墨榄。具體各種屬性的功能和規(guī)則這邊就不展開了,有興自究勿她。
對于import袄秩、alias和beans標簽,簡述一下:alias的解析和bean中的alias解析差不多,也是將每一個別名與beanName以map形式保存之剧;impot可以導入其他配置文件郭卫,解析過程就是找到那個文件然后遞歸進行解析;beans就是把多個bean標簽包起來背稼,然后遍歷解析每一個bean標簽贰军。
總結(jié)
畫一個流程圖作為總結(jié):