天天看點

spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()

我們在最開始接觸spring的時候,看到不少書spring入門的例子如下

ApplicationContext atx = new ClassPathXmlApplicationContext("application.xml");
atx.getBean("benefitService");           

上面這個例子第一行是表示如何初始化一個spring 容器,第二表示如何從一個已經初始化後的spring容器中按bean id得到這個bean, 絕大部分spring應用中,bean都是按業務模闆和層次分别配置在不同的xml檔案中, spring容器根據配置的xml檔案名路徑去分别解析這些xml 配置檔案,生成相應的BeanDefinition 執行個體,一個bean對應一個BeanDefinition, 解析完成bean 的xml配置檔案之後,spring容器就開始初始bean,大概的過程如下:

spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()

這篇文章主要分析第一個階段,即xml配置檔案 ---->BeanDefinition這個過程,首先根據IDE工具看一下ClassPathXmlApplicationContext 這個類的繼承關系:

spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()

通過這個繼續關系,發現ClassPathXmlApplicationContext也是間接實作了ResourceLoader這個接口, ResourceLoader的實作類主要用于根據給定的資源檔案位址傳回對應的Resource,在本例中,這個資源檔案就是application.xml;

接着往下看

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, 
                     ApplicationContext parent)  throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}           

代碼到了含有三個參數的構造方法,主要有三個步驟

super(parent)

這個步驟主要是調父類的構造器初始化容器的parent對象,這示例中,parent這個參數為空,其次是初始化資源模式解析器resourcePatternResolver,是一個實作了ResourceLoader的類,源碼如下 :

public AbstractApplicationContext(ApplicationContext parent) {
        this.parent = parent;
        this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
        Assert.notNull(resourceLoader, "ResourceLoader must not be null");
        this.resourceLoader = resourceLoader;
}           

setConfigLocations(configLocations)

這行代碼,主要就是初始化configLocations這個數組字段,源碼如下:

private String[] configLocations;
public void setConfigLocations(String[] locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}           

其中resolvePath主要解析并填充資源路徑中的一些系統占位符,

如開始符:${,

結束符: }

分割符: :

refresh()

前面兩步基本上都是容器本身的設定初始化,這個步驟才是spring 容器解析,建立初始化bean的關鍵步驟,點時去,我們發現這個方法長,隻分析xml解析的過程,其它的在這裡不一 一細說,在refresh方法中,第二代碼是這樣的:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();           

直接進入org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()這個方法,看一下這個方法體:

@Override
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(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. 先判斷有沒有beanFactory, 如果已經存在beanFactory,就先銷毀beanFactory中所有的bean,再關閉這個beanFactory
  2. 建立一個DefaultListableBeanFactory 類型執行個體的beanFactory;
  3. 設定beanFactory的id,其中id是根據類名生成的,具體代碼是:
    obj.getClass().getName() + "@" + getIdentityHexString(obj)           
  4. 定置化beanFactory, 主要是設定: Bean是否需求覆寫重寫,是否允bean循環引用,參數自動發現和注釋字段bean比對解析,例如Autowired注解;

    5.解析xml文檔,并生成BeanDefinition對象執行個體集合;

重點看第5個步驟,即loadBeanDefinitions(beanFactory)這行代碼,根據繼承關系,直接進入org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)這個方法

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
        // resource loading environment.
    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.
    initBeanDefinitionReader(beanDefinitionReader);
    loadBeanDefinitions(beanDefinitionReader);
}           

這個方法的邏輯也很清晰

  1. 建立一個XmlBeanDefinitionReader 對象,這個類主要定義讀取的Document,并注冊BeanDefinition的功能;
  2. 設定beanDefinitionReader 對象的ResourceLoader,即ClassPathXmlApplicationContext類對象;
  3. 設定beanDefinitionReader 對象的解析對象:ResourceEntityResolver
  4. 初始化beanDefinitionReader 對象,在spring架構中,這個方法為空,是一個重寫spring架構的擴充點;
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}           
  1. 正式解析xml檔案,并生成BeanDefinitions, 方法體如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}           

針對每個資源檔案,重點看下面這兩行代碼:

Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);           

第一行代碼主是擷取路徑中以classpath:開頭的xml檔案,當然也有很多其它字首開頭的資源,org.springframework.util.ResourceUtils 這個類中有詳細的說明

第二主要是生成加載BeanDefinitions

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}           

前面都是一些準備過程,接下來,到了真正執行loadBeanDefinitions

spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()
spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()
  1. getValidationModeForResource這個方法主要是擷取xml檔案的驗證模式,主要有DTD和XSD兩種驗證模式,這兩種驗證模式,spring都支援,方法源碼如下:
    spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()

DTD模式示例:

spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()

XSD模式示例:

spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()
  1. 加載xml,并得到轉換成對應的Document執行個體
  2. 解析并注冊xml檔案定義相關的bean;

    重點看一下步驟3:

相關的方法源碼如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 使用 DefaultBeanDefinitionDocumentReader執行個體化BeanDefinitionDocumentReader對象
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // 記錄統計前已經加載的BeanDefinition數量
    int countBefore = getRegistry().getBeanDefinitionCount();
         // 加載并注冊bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        // 記錄本次加載的 BeanDefinition數量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}           

重點分析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource))這行代

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;

        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();

        BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);

        preProcessXml(root);
        parseBeanDefinitions(root, delegate);
        postProcessXml(root);
}           

registerBeanDefinitions這個方法的邏輯也比較清晰

  1. 指派readerContext
  2. 擷取xml對象加載包裝後對應Document對象
  3. 建立解析器 BeanDefinitionParserDelegate 類型的對象執行個體:delegate
  4. 處理xml檔案的前置處理,預設為空,這是一個擴充點;
  5. 解析xml檔案,并注冊到spring容器中
  6. 處理xml檔案的後軒處理,預設為空,這也是一個擴充點;

    重點看一下parseBeanDefinitions這個方法,方法實作源碼如下:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        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;
                if (delegate.isDefaultNamespace(ele)) {
                                        //預設标簽bean的解析
                                        //<bean id="benefitService" class="com.tmall.xxx">
                    parseDefaultElement(ele, delegate);
                } else {
                                       //自定義标簽bean的解析
                                        <tx:annotation-driven />
                                       <dubbo:service  timeout="3000" interface="com.tmall.xxx" ref="xxxx"/>
                                       <context:component-scan base-package="com.tmall.xxx"/>
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }  else {
              //自定義标簽bean的解析
              <context:component-scan base-package="com.tmall.xxx"/>
        delegate.parseCustomElement(root);
        }
}           

這個方法主要是針對bean的xml配置檔案中預設标簽和自定義标簽分别進行解析

預設标簽的解析方法parseDefaultElement的方法體如下:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {  // 對import标簽的解析處理
        importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 對alias标簽的解析處理
        processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//對bean标簽的解析處理
        processBeanDefinition(ele, delegate);
      }
      else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//對beanS标簽的解析處理
        processBeanDefinition(ele, delegate);
       }
}           

到這個方法,我們終于看到與我們平時寫spring bean配置檔案相關的代碼,下面結bean标簽的解析處理代碼進行分析

bean标簽的解析最終會到parseBeanDefinitionElement方法,這個方法的部分代碼如下:

spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()

這個方法的主要工作内容包括:

  1. 提供元素中的id和name屬性
  2. 進一步解析其他所有屬性并統一封裝到AbstractBeanDefinition類型的執行個體中
  3. 如果檢測到bean沒有指定的beanName,那麼使用預設的規則為此Bean生成beanName

    4.将擷取到的資訊封裝到BeanDefinitionHolder的執行個體中。

步驟2中對其它标簽的解析過程部分源碼如下:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
            BeanDefinition containingBean, AbstractBeanDefinition bd) {

    if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {  // scope屬性
        // Spring 2.x "scope" attribute
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
        if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // singleton 屬性
            error("Specify either 'scope' or 'singleton', not both", ele);
        }
    }
    else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
        // Spring 1.x "singleton" attribute
        bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?
                BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);
    }
    else if (containingBean != null) {
        // Take default from containing bean in case of an inner bean definition.
        bd.setScope(containingBean.getScope());
    }

    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {   // abstract屬性
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);  // lazy-init屬性
    if (DEFAULT_VALUE.equals(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);  //autowire屬性
    bd.setAutowireMode(getAutowireMode(autowire));

    String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
    bd.setDependencyCheck(getDependencyCheck(dependencyCheck));

    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {  // depends-on屬性
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, 
                       BEAN_NAME_DELIMITERS));
    }

         // autowire-candidate 屬性
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
        String candidatePattern = this.defaults.getAutowireCandidates();
        if (candidatePattern != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
                bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    }
    else {
        bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }
        // primary 屬性
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }
        // init-method屬性
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        if (!"".equals(initMethodName)) {
            bd.setInitMethodName(initMethodName);
        }
    }
    else {
        if (this.defaults.getInitMethod() != null) {
            bd.setInitMethodName(this.defaults.getInitMethod());
            bd.setEnforceInitMethod(false);
        }
    }
        // destroy-method屬性
    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
        String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
        if (!"".equals(destroyMethodName)) {
            bd.setDestroyMethodName(destroyMethodName);
        }
    }
    else {
        if (this.defaults.getDestroyMethod() != null) {
            bd.setDestroyMethodName(this.defaults.getDestroyMethod());
            bd.setEnforceDestroyMethod(false);
        }
    }
       // factory-method 屬性
    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
       // factory-bean 屬性
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }
    return bd;
}

// 解析購造函數constructor-arg标簽元素
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && 
                      nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
            parseConstructorArgElement((Element) node, bd);
        }
    }
}
//解析property屬性
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
            parsePropertyElement((Element) node, bd);
        }
    }
}           

其它的标簽解析過程請參考下面這個類org.springframework.beans.factory.xml.BeanDefinitionParserDelegate

上面解析分析了預設标簽bean的源碼解析過程,下面再來看一下自定标簽的解析過程,方法位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(Element, BeanDefinition)源碼如下:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
         //擷取對應的命名空間
    String namespaceUri = getNamespaceURI(ele);
        // 根據命名空間得到對相應的NamespaceHandler 
    NamespaceHandler handler = 
                  this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + 
                        namespaceUri + "]", ele);
        return null;
    }
        // 調用自定義的NamespaceHandler handler進行解析
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}           

由于spring自定義标簽大家在平時用得比較少,由于時間關系和篇幅關系,本來就不對自定義标簽進行詳細分析,下一篇文章會結合spring源碼對自定義标簽的使用和解析原理進行詳細的介紹分析

由于時間關系,文中有些地方沒有寫細緻,講得不夠清楚,可能不少地方還會出現低級的錯誤,請大家指正。