美麗涵涵童裝店--說我博客名字,給你們打折!
          隨筆 - 82  文章 - 266  trackbacks - 0
          <2014年11月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          30123456


          點(diǎn)擊這里給楊愛友發(fā)消息
          美麗涵涵童裝店
          說我博客名字,給你們打折!

          常用鏈接

          留言簿(6)

          隨筆分類

          隨筆檔案

          文章檔案

          好友的BLOG

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          1. 場景

          一個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="studentBean" class="my.StudentBean"></bean>
          </beans>

          StudentBean
          public class StudentBean{
              
          public void getName(String name) {
                  System.out.println(
          "你的名字是:" + name);
              }
          }

          單元測試類
           1 public class MyTest {
           2 
           3     public static void main(String[] args) {
           4         ClassPathResource res = new ClassPathResource("my/applicationContext.xml");
           5         
           6         XmlBeanFactory bf = new XmlBeanFactory(res);
           7         
           8         StudentBean bean = (StudentBean)bf.getBean("studentBean");
           9         
          10         bean.getName("yangay");
          11     }
          12 }
          運(yùn)行單元測試,打印出“你的名字是:yangay”,測試類只有四行代碼,但Spring到底為我們做了些什么?下面我們就基于這樣的場景去分析bean的加載過程。

          2. 初步分析

          (1) 獲取配置文件
          ClassPathResource res = new ClassPathResource("my/applicationContext.xml");
              這一句只是讀入配置文件,并封裝成Spring提供的Resource對象,供后面邏輯使用。
              在Spring內(nèi)部,有超過十個以Resource結(jié)尾的類或文件,他們處理不同類型的資源文件,如FileSystemResource、ClassPathResource、UrlResource等,處理過程大同小異,內(nèi)部細(xì)節(jié)可以不必關(guān)心,跟其他組件邏輯幾乎沒關(guān)系。
          (2) 解析配置文件并注冊bean
           XmlBeanFactory bf = new XmlBeanFactory(res);
              這里面的邏輯相當(dāng)復(fù)雜,涉及到眾多Factory、Reader、Loader、BeanDefinition、Perser、Registry系列接口和類,但他們做的基本事情就是將applicationContext.xml配置的Bean信息構(gòu)成BeanDefinition對象,然后放到Factory的map中(這一步就是所謂的注冊),這樣以后程序就可以直接從Factory中拿Bean信息了。
              要跟蹤這個處理過程,大致流程如下:
              a. 構(gòu)造XmlBeanFactory時,會調(diào)用Reader對象的loadBeanDefinitions方法去加載bean定義信息
              b. 在Reader對象的doLoadBeanDefinitions驗(yàn)證文檔(配置文件)模式,然后通過documentLoader對象處理資源對象,生成我們Document對象;
              c. 調(diào)用BeanDefinitionDocumentReader對象的doRegisterBeanDefinitions去注冊bean定義信息;
              d. parseBeanDefinitions從xml文檔根節(jié)點(diǎn)遞歸循環(huán)處理各個節(jié)點(diǎn),對bean節(jié)點(diǎn)真正的處理工作委托給了BeanDefinitionParserDelegate,方法parseBeanDefinitionElement將一個bean節(jié)點(diǎn)轉(zhuǎn)換成一個BeanDefinitionHolder對象,這才是最終的解析過程;
              e. DefaultListableBeanFactory.registerBeanDefinition利用解析好的beanDefinition對象完成最終的注冊,其實(shí)就是把beanName和beanDefinition作為鍵值對放到beanFactory對象的map;
          (3) 實(shí)例化Bean
          StudentBean bean = (StudentBean)bf.getBean("studentBean");
              這一步Spring同樣做了復(fù)雜的處理,但基本原理就是利用反射機(jī)制,通過bean的class屬性創(chuàng)建一個bean的實(shí)例,例子中是創(chuàng)建了一個StudentBean對象。
          (4) 調(diào)用對象的方法,沒什么好說的。當(dāng)然如果方法上做了事務(wù)、AOP之類的聲明,這一步的處理就不會那么簡單了。

          3. 解析配置文件并注冊bin對象

          我們分析bean的注冊過程,就是下面這行代碼,他完成了配置文件的解析和bin的注冊功能,我們看看Spring到底為我們做了多少事情。
          XmlBeanFactory bf = new XmlBeanFactory(res);

          public class XmlBeanFactory extends DefaultListableBeanFactory {
              
          //這里為容器定義了一個默認(rèn)使用的bean定義讀取器
              private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
              
          public XmlBeanFactory(Resource resource) throws BeansException {
                  
          this(resource, null);
              }
              
          //在初始化函數(shù)中使用讀取器來對資源進(jìn)行讀取,得到bean定義信息。
              public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
                  
          super(parentBeanFactory);
                  
          this.reader.loadBeanDefinitions(resource);
              }
          我們跟進(jìn)去,發(fā)現(xiàn)他實(shí)際調(diào)用了XmlBeanDefinitionReader對象的loadBeanDefinitions方法。
          3.1 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());
                      }
          這個方法是整個資源加載的切入點(diǎn),我們先大致看看這個方法的處理流程:
          a. 封裝資源文件
              new EncodedResource(resource)
          b. 獲取輸入流
              從EncodedResource對象中獲取InputStream并構(gòu)造InputSource對象
          c. 然后調(diào)用doLoadBeanDefinitions方法完成具體的加載過程
          3.2 doLoadBeanDefinitions方法
          int validationMode = getValidationModeForResource(resource);
          Document doc 
          = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
          return registerBeanDefinitions(doc, resource);
          這個方法的代碼很長,如果不考慮異常處理,其實(shí)只做了三件事情:
          a. 獲取對XML文件的驗(yàn)證模式
          b. 加載XML文件,并得到對應(yīng)的Document對象
          c. 根據(jù)返回的Document對象注冊bean信息

          這里對驗(yàn)證模式不進(jìn)行討論;
          這里不對Document對象的加載過程進(jìn)行討論;
          這里直接進(jìn)入bean的注冊方法registerBeanDefinitions
          3.3 registerBeanDefinitions方法
              public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
                  
          // 這里定義解析器,使用XmlBeanDefinitionParser來解析xml方式的bean定義文件 - 現(xiàn)在的版本不用這個解析器了,使用的是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;
              }
          當(dāng)把文檔轉(zhuǎn)換為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文件的子節(jié)點(diǎn),比如各個bean節(jié)點(diǎn)         
                      NodeList nl = root.getChildNodes();
                      
          //這里對每個節(jié)點(diǎn)進(jìn)行分析處理
                      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)) {
                                  
          //這里是解析過程的調(diào)用,對缺省的元素進(jìn)行分析比如bean元素
                                  parseDefaultElement(ele, delegate);
                              }
          else {
                                  delegate.parseCustomElement(ele);
                              }
                          }
                      }
                  } 
          else {
                      delegate.parseCustomElement(root);
                  }
              }
          經(jīng)過艱難險阻,山路十八彎,我們終于走到了核心邏輯的底部doRegisterBeanDefinitions,如果說以前一直是XML加載解析的準(zhǔn)備階段,
          那么這個方法算是真正地開始進(jìn)行解析了,我們期待的核心部分真正開始了。
          這個方法的代碼我們比較熟悉,讀取Document對象,循環(huán)每一個bean節(jié)點(diǎn),然后進(jìn)行處理。
          Spring有兩類Bean,一個是默認(rèn)的,一個是自定義的bean,這個方法對他們分別調(diào)用了不同方法進(jìn)行處理。
          3.5 對默認(rèn)標(biāo)簽的處理 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方法進(jìn)行元素解析,返回BeanDefinitionHolder對象bdHolder,經(jīng)過這個方法,bdHolder實(shí)例已經(jīng)包含我們配置文件中對bean的所有配置信息了,如name、class等。
          b. 對bdHolder進(jìn)行裝飾
          c. 解析完成后,要對bdHolder進(jìn)行注冊,同樣,注冊過程委托給了BeanDefinitionReaderUtils去處理
          3.6 delegate.parseBeanDefinitionElement(ele)
          這個方法便是對默認(rèn)標(biāo)簽解析的全過程了,他將一個element節(jié)點(diǎn)轉(zhuǎn)換成BeanDefinitionsHolder對象,其中ele和bdHolder中的屬性是對應(yīng)的。不論是常用的還是不常用的我們都看到了,盡管有些復(fù)雜屬性還需要進(jìn)一步解析,但絲毫不會影響我們興奮的心情。
          a. 提取元素的id和name屬性
          b. 進(jìn)一步解析其他所有屬性并統(tǒng)一封裝到BeanDefinition類型的實(shí)例
          c. 將獲取到的信息封裝到BeanDefinitionHolder實(shí)例中待續(xù)。。。
          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);
          跟進(jìn)去,我們就看到解析的最底層了,如parseMetaElements,這里不再進(jìn)行往下分析。

          對于配置文件,解析也解析完了,裝飾也裝飾完了,已經(jīng)把xml中bean元素的各屬性封裝到了BeanDefinition對象,已經(jīng)可以滿足后續(xù)的使用要求了,剩下的工作便是注冊解析的BeanDefinition。
          3.7 BeanDefinitionReaderUtils.registerBeanDefinition
          這個方法并沒有做太多事情,而是直接調(diào)用了BeanDefinitionRegistry的注冊方法。BeanDefinitionRegistry是一個接口,有多個實(shí)現(xiàn)類,這里我們使用了默認(rèn)的實(shí)現(xiàn)DefaultListableBeanFactory。
          3.8 DefaultListableBeanFactory.registerBeanDefinition
          代碼啰嗦了一大堆,實(shí)際上所謂的注冊,就是把beanName和beanDefinition對象作為鍵值對放到BeanFactory對象的beanDefinitionMap。
          但Spring經(jīng)常把簡單的邏輯寫的非常“啰嗦”,仔細(xì)分析代碼,發(fā)現(xiàn)他完成了幾個事情:
          a. 對bean對象的校驗(yàn)
          b. 檢查beanFactory中是否已經(jīng)有同名的bean,如果有,進(jìn)行相應(yīng)處理
          c. 把bean對象放到beanDefinitionMap中(這就是最終所謂的注冊)
          d. 清除整個過程緩存的對象數(shù)據(jù)

          以上便是Spring對bean解析注冊的全過程,總結(jié)一下大致步驟:
          1. 加載XML文件,封裝成Resource對象
          2. 調(diào)用Reader對象方法讀取XML文件內(nèi)容,并將相關(guān)屬性放到BeanDefinition實(shí)例
          3. 將BeanDefinition對象放到BeanFactory對象

          4. 實(shí)例化bean

           

          詳細(xì)過程,預(yù)留位置

           

          posted on 2014-08-29 10:47 楊愛友 閱讀(10868) 評論(3)  編輯  收藏

          FeedBack:
          # re: Spring源碼學(xué)習(xí)-bean加載 2014-09-25 11:36 李金龍
          這個不錯  回復(fù)  更多評論
            
          # re: Spring源碼學(xué)習(xí)-bean加載 2014-11-14 11:17 duicky
          不錯,調(diào)試看下,基本按上述流程  回復(fù)  更多評論
            
          # re: Spring源碼學(xué)習(xí)-bean加載 2016-06-29 11:09 飛天奔月
          詳細(xì)過程,預(yù)留位置?

          2年了 哦   回復(fù)  更多評論
            

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          美麗涵涵童裝店
          親,說我博客名字,給你們打折!
          主站蜘蛛池模板: 巫溪县| 英德市| 天等县| 遂宁市| 睢宁县| 时尚| 电白县| 塔河县| 鹤峰县| 东丽区| 泗水县| 枣强县| 武汉市| 乌恰县| 汪清县| 中牟县| 巫溪县| 日土县| 洛扎县| 都匀市| 阳春市| 中江县| 修文县| 新郑市| 平潭县| 松江区| 湘西| 安岳县| 盖州市| 曲沃县| 弥勒县| 抚州市| 突泉县| 铜梁县| 昌图县| 松潘县| 大化| 玉环县| 沂水县| 遵义市| 吉木乃县|