Spring源碼閱讀系列之obtainFreshBeanFactory()實現

接着上一篇文章,本篇來接着談談 Spring 容器的創建及 bean 定義的解析。Debug 使用的代碼和上一篇文章提供的保持一致

本篇文章由於代碼調用流程較長,所以先給大家呈現一張調用流程圖,方便大家更容易理解調用層次,廢話不多說了,先上圖一睹爲快。

創建BeanFactory及註冊BeanDefinition流程

  1. 直接來看下obtainFreshBeanFactory源碼實現

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    // Step1:創建BeanFactory並註冊BeanDefinition定義
    // 通過這個方法可以看出,所有實現是在這個方法裏面完成的
    refreshBeanFactory();
    // Step2:獲取創建好的BeanFactory對象,並返回
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}

  1. refreshBeanFactory實現其實調用了其子類AbstractRefreshableApplicationContext.refreshBeanFactory()方法。

protected final void refreshBeanFactory() throws BeansException {
    // Step1:判斷BeanFactory是否已經創建好,一般情況下第一次初始化Spring容器的時候if是不成立的,因爲在此之前沒有地方可以創建出BeanFactory對象
    if (hasBeanFactory()) {
      // 如果已經創建好,先將創建好的bean實例,相關緩存,BeanDefinition定義全部清空
      // Spring源碼中大量運用了緩存,來提高Spring容器初始化的速度
      destroyBeans();
      // 然後在將BeanFactory置爲null
      closeBeanFactory();
    }
    try {
      // Step2:創建一個DefaultListableBeanFactory對象,該對象用於存儲Bean實例、相關緩存、BeanDefinition定義等等
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      beanFactory.setSerializationId(getId());
      // Step3:設置是否允許BeanDefinition是否可以被覆蓋
      // 設置循環引用
      customizeBeanFactory(beanFactory);
      // Step4:加載BeanDefinition定義,並將其註冊到BeanFactory中
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
        this.beanFactory = beanFactory;
      }
    }
    catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

  1. loadBeanDefinitions() 實現由AbstractRefreshableApplicationContext子類AbstractXmlApplicationContext.loadBeanDefinitions()方法實現的。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    // Step1:創建一個XmlBeanDefinitionReader對象,用於讀取xml文檔
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
    // resource loading environment.
    // Step2:設置環境變量
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
    // Step3:初始化BeanDefinitionReader,設置是否啓用xml文檔校驗,默認情況下是需要校驗的
    // 說白了就是是否要校驗xml文檔的合法性,避免錯誤解析
    initBeanDefinitionReader(beanDefinitionReader);
    // Step4:加載beanDefinitions
    loadBeanDefinitions(beanDefinitionReader);
}

  1. 初始化XmlBeanDefinitionReader對象完成後,開始讀取配置文件資源。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    // Step1:首先讀取ClassPathXmlApplicationContext裏面存放點的Resource資源
    Resource[] configResources = getConfigResources();
    if (configResources != null) {
      // Step1.1:如果Resource資源不爲空,則開始加載Resource資源
      reader.loadBeanDefinitions(configResources);
    }
    // Step2:讀取AbstractRefreshableConfigApplicationContext指定配置文件資源
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
      // Step2.1:如果Location資源不爲空,則開始加載Location資源
      reader.loadBeanDefinitions(configLocations);
    }
}

  1. 獲取到配置文件路徑後,開始遍歷解析每個配置文件,不管是第一種資源還是第二種資源,最後都統一調用了XmlBeanDefinitionReader類的父類AbstractBeanDefinitionReader的loadBeanDefinitions()方法。

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // Step1:獲取資源讀取器
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
      throw new BeanDefinitionStoreException(
          "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }
    // Step2:如果當前的資源讀取器實現了ResourcePatternResolver接口
    if (resourceLoader instanceof ResourcePatternResolver) {
      // Resource pattern matching available.
      try {
        // Step2.1:獲取resource資源
        Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
        // Step2.2:加載所有指定的resource資源裏面定義的BeanDefinitions
        int loadCount = loadBeanDefinitions(resources);
        if (actualResources != null) {
          for (Resource resource : resources) {
            actualResources.add(resource);
          }
        }
        if (logger.isDebugEnabled()) {
          logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
        }
        return loadCount;
      }
      catch (IOException ex) {
        throw new BeanDefinitionStoreException(
            "Could not resolve bean definition resource pattern [" + location + "]", ex);
      }
    }
    else {
      // Can only load single resources by absolute URL.
      // Step3:如果沒有實現ResourcePatternResolver接口,則根據當前的resourceLoader獲取Resource資源
      Resource resource = resourceLoader.getResource(location);
      // Step3.1:加載指定的Resource資源定義的BeanDefinitions
      int loadCount = loadBeanDefinitions(resource);
      if (actualResources != null) {
        actualResources.add(resource);
      }
      if (logger.isDebugEnabled()) {
        logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
      }
      return loadCount;
    }
}

  1. 上面這段代碼主要作用是使用不同的BeanDefinitionReader讀取器讀取不同類型的配置文件。拿我們提供的測試例子,最後其實調用了XmlBeanDefinitioneader.loadBeanDefinitions()方法。在這個方法裏面我們看到一個比較特別的方法,叫做doLoadBeanDefinitions(),其實真正加載BeanDefinition是在這個方法裏面實現的。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isInfoEnabled()) {
      logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }
    // Step1:獲取當前的Resource對象,剛開始肯定是沒有的
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
      // Step1.1:將當前Resource設置進去
      currentResources = new HashSet<>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    // Step2:只有在第一次添加的時候纔會返回true,也就意味着如果當前resource已經被設置過了,則出現了重複加載,直接拋出異常
    if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
          "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
      // Step3:根據resource對象獲取對應的InputStream
      InputStream inputStream = encodedResource.getResource().getInputStream();
      try {
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
          inputSource.setEncoding(encodedResource.getEncoding());
        }
        // Step4:拿到inputSource對象,開始正式讀取配置文件,並創建配置文件中定義的BeanDefinition
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
      }
      finally {
        // Setp5:關閉輸入流
        inputStream.close();
      }
    }
    catch (IOException ex) {
      throw new BeanDefinitionStoreException(
          "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
        this.resourcesCurrentlyBeingLoaded.remove();
      }
    }
}

  1. 拿到了InputSource之後,來看看配置文件到底是如何被解析的。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
    try {
      // Step1:加載Document對象,其實就是把xml文件解析成一個文檔對象返回
      Document doc = doLoadDocument(inputSource, resource);
      // Step2:對Document對象遍歷,讀取不同的標籤名,創建對應的BeanDefinition,並將創建出來的BeanDefinition註冊到BeanFactory中去
      return registerBeanDefinitions(doc, resource);
    }
    // 此處爲了方便閱讀,將各種異常代碼用.....代替
    ......
    catch (Throwable ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
          "Unexpected exception parsing XML document from " + resource, ex);
    }
}

  1. 先來看看Document對象是怎麼創建出來的。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
        getValidationModeForResource(resource), isNamespaceAware());
}

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    // Step1:創建Document工廠,這裏面其實就是設置配置文件的文件頭
    // 比如applicationContext.xml文件裏面最開始,beans標籤裏面指定的 .xsd。瞭解即可
    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isDebugEnabled()) {
      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    // Step2:利用Document工廠,創建對應的DocumentBuilder。
    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    // Step3:利用builder去解析輸入流,從而構建出Document對象
    return builder.parse(inputSource);
}

// 這個方法就是文檔的一些解析處理,瞭解一下
public Document parse(InputSource is) throws SAXException, IOException {
        if (is == null) {
            throw new IllegalArgumentException(
                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
                "jaxp-null-input-source", null));
        }
        if (fSchemaValidator != null) {
            if (fSchemaValidationManager != null) {
                fSchemaValidationManager.reset();
                fUnparsedEntityHandler.reset();
            }
            resetSchemaValidator();
        }
        domParser.parse(is);
        Document doc = domParser.getDocument();
        domParser.dropDocumentReferences();
        return doc;
}

  1. 到現在,已經將 xml 文件解析成Document對象了,接下來看看根據Document對象如何創建BeanDefinition對象並註冊到BeanFactory裏面。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 這裏我們只關注如何把Document對象註冊到BeanFactory裏面
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

protected void doRegisterBeanDefinitions(Element root) {
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    // Step1:以beans開始,是否可以取到 “profile”標籤,我們沒有在xml文件裏面指定,所以這個地方可以忽略
    if (this.delegate.isDefaultNamespace(root)) {
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) {
        String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
            profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
          if (logger.isInfoEnabled()) {
            logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                "] not matching: " + getReaderContext().getResource());
          }
          return;
        }
      }
    }
    // Step2:解析前工作,默認沒有實現,留出來擴展
    preProcessXml(root);
    // Step3:開始解析Document
    parseBeanDefinitions(root, this.delegate);
    // Step4:解析後工作,默認沒有實現,留出來擴展
    postProcessXml(root);

    this.delegate = parent;
}

  1. 重點來了,我們在 xml 文件裏面配置的Bean標籤就是在這個裏面被轉化成一個個BeanDefinition對象的。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // Step1:如果是默認的命名空間,使用默認的解析邏輯
    if (delegate.isDefaultNamespace(root)) {
      // Step1.1:獲取所有子節點
      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;
          // Step1.2:如果子節點是 “import”、“alias”、“bean”、“beans”等等的
          // 則走默認的標籤解析處理邏輯
          if (delegate.isDefaultNamespace(ele)) {
            parseDefaultElement(ele, delegate);
          }
          else {
            // Step1.3:如果不是上面指定的這些標籤的話,則走特殊的標籤解析邏輯
            delegate.parseCustomElement(ele);
          }
        }
      }
    }
    // Step2:不是默認的命名空間,則走自己的解析邏輯
    else {
      delegate.parseCustomElement(root);
    }
}

總結

從本篇文章不難看出,obtainFreshBeanFactory 就是創建了一個 BeanFactory 對象,然後把 xml 配置文件解析成 Document 對象,然後對 Document 對象遍歷,創建出相應的 BeanDefinition 定義,並把 BeanDefinition 對象註冊到 BeanFactory 裏面。

到這裏我們拿到了 xml 配置的 bean 標籤。限於文章篇幅過長,我在下一篇文章中來繼續解釋,bean 標籤是如何被解析的,比如 id,class,scope 等等屬性。


歡迎關注我,共同學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章