容器的基本实现

《Spring源码深度解析》学习笔记

阅读源码的一个好方法:通过示例跟踪代码,如果调用层次深,可配以UML。文中源码源自Spring 4.

从一个最基本的示例开始。假设有下面的Cat类:

public class Cat {
    private String name = "Tom";    
    public void sayHello(){
        System.out.println("Hello,I am " + name);
    }
}

beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="cat" class="com.tony.test4spring.Cat" />
</beans>

BeanTest.java

public class BeanTest {

    @Test
    public void testXmlBeanFactory(){
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));
        Cat cat = (Cat) bf.getBean("cat");
        cat.sayHello();
    }
}

输出结果: Hello,I am Tom

本文就从这个例子开始,分析下面这行代码究竟发生了什么。

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));

1.配置文件的封装

Spring配置文件beans.xml的读取是通过ClassPathResource进行封装的,就像上面的new ClassPathResource(“beans.xml”) 。Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口。Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。该接口定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen),还提供了不同资源到URL(getURL)、URI(getURI)、File(getFile)类型的转换方法。对不同来源的资源文件有相应的实现类:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)等。

通过Resource相关实现类完成配置文件的封装后,进入了XmlBeanFactory使用Resource实例作为构造函数参数的方法。如下所示:

XmlBeanFactory.java

public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
}

构造函数再次调用内部构造函数:
XmlBeanFactory.java

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
}

this.reader.loadBeanDefinitions(resource)是资源加载的真正实现。在此之前,有一个调用父类构造函数初始化的过程,跟踪到AbstractAutowireCapableBeanFactory.java

public AbstractAutowireCapableBeanFactory() {
    super();
    ignoreDependencyInterface(BeanNameAware.class);
    ignoreDependencyInterface(BeanFactoryAware.class);
    ignoreDependencyInterface(BeanClassLoaderAware.class);
}

这里的ignoreDependencyInterface方法的功能是忽略给定接口的自动装配功能。举例来说,当类A中有属性类B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B。但是某些情况下B不会被初始化,比如B实现了BeanNameAware接口。所以,这里的三行ignore方法就是告诉Spring,上例中的B属性如果实现了这三个接口中的任意一个时,那么在获取A的Bean的时候,就忽略B的初始化。

2.加载Bean

回到XmlBeanDefinitionReader类型的reader属性提供的方法:this.reader.loadBeanDefinitions(resource),这是整个资源加载的切入点。
1)封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。

XmlBeanDefinitionReader.java

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

这里EncodedResource的作用是什么呢?通过名称,我们大致可以推断这个类主要是用于对资源文件的编码进行处理。其中主要逻辑天现在gerReader()方法中,当设置了编码属性的时候,Spring会使用相应的编码作为输入流的编码。

EncodedResource.java

public Reader getReader() throws IOException {
    if (this.charset != null) {
        return new InputStreamReader(this.resource.getInputStream(), this.charset);
    }
    else if (this.encoding != null) {
        return new InputStreamReader(this.resource.getInputStream(), this.encoding);
    }
    else {
        return new InputStreamReader(this.resource.getInputStream());
    }
}

当构造好了EncodedResource实例后,再次转入了可复用方法:
2)获取输入流。从Resource中获取对应的InputStream并构造InputSource.
3)通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。

XmlBeanDefinitionReader.java

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());
    }

    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        finally {
            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();
        }
    }
}

resourcesCurrentlyBeingLoaded是类XmlBeanDefinitionReader的ThreadLocal类型的成员变量,存放着和当前线程有关的数据:已经被加载的xml resource。把EncodedResource放到HashSet中,那么EncodedResource就必须覆盖Object的equals()方法和hashCode()方法。判断HashSet中元素是否重复的依据是 i)先看hashCode是否相同,如果不相同,那么equals方法的执行结果必然不相同;ii)如果hashCode相同,只能通过进一步执行equals来判定元素是否重复。

接着从encodedResource中获取已经封装的Resource对象,并再次从Resource中获取InputStream,通过SAX读取xml文件的方式准备InputSource对象。

说到这不得不提一下xml的两种解析模型,dom和sax。Dom解析(Document Object Mode)是把XML解析为一棵与XML文档结构对应的树,存储在内存中。取到XML里的每个元素、属性、文本、注释都在Document里有所体现。对于小的xml文件来说,这样处理还是很方便的。但是当遇到比较大的xml文件的时候,dom解析的缺点就显现出来了,内存占用比较大,而且查找速度比较慢。
sax解析就是为了解决这个问题出现的。SAX(Simple API for XML),对xml文档解析会从xml文档开始位置起进行解析,根据提前定义好的事件处理器来决定当前所解析的部分(元素、元素属性或者是内容)是否有必要记录并存储。

接下来把准备好的数据传入真正的核心处理部分doLoadBeanDefinitions:

XmlBeanDefinitionReader.java

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        //加载xml文件,得到对应的Document
        Document doc = doLoadDocument(inputSource, resource);
        //根据返回的Document注册Bean信息
        return registerBeanDefinitions(doc, resource);
    } ...N个catch...
}

doLoadDocument方法具体实现:
XmlBeanDefinitionReader.java

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

其中getValidationModeForResource(resource)是为了获取xml文件的验证模式。

3.获取xml的验证模式

xml的验证模式保证了xml文件的正确性,比较常用的验证模式有两种:DTD和XSD。

1)DTD与XSD区别

DTD(Document Type Definition),是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。使用DTD验证模式的时候,需要在XML文件头部声明,以下是在Spring中使用DTD声明方式的代码:

<?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>
...
</beans>

XSD(Xml Schema Definition)描述了XML文档的结构,可以用一个指定的Xml Schema来验证某个Xml文档,以检查该XML文档是否符合其要求。Xml Schema本身是一个XML,符合XML语法结构,可以用通用的xml解析器解析它。

使用xml schema检验xml时,往往需要声明namespace和对应的schema的URL地址,如下:
容器的基本实现_第1张图片

2)验证模式的读取

弄清楚了DTD和XSD的区别后,继续看getValidationModeForResource(resource)的具体实现:

XmlBeanDefinitionReader.java

protected int getValidationModeForResource(Resource resource) {
    /** * 验证模式可以通过调用XmlBeanDefinitionReader中的setValidationMode方法进行设定, * 如果没设定,getValidationMode()方法返回值就是VALIDATION_AUTO, * 接下来就是通过detectValidationMode方法侦测验证模式 */
    int validationModeToUse = getValidationMode();
    if (validationModeToUse != VALIDATION_AUTO) {
        return validationModeToUse;
    }
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
        return detectedMode;
    }
    return VALIDATION_XSD;
}

detectValidationMode(resource)的具体实现:

XmlBeanDefinitionReader.java

protected int detectValidationMode(Resource resource) {
    if (resource.isOpen()) {
        throw new BeanDefinitionStoreException(
                "Passed-in Resource [" + resource + "] contains an open stream: " +
                "cannot determine validation mode automatically. Either pass in a Resource " +
                "that is able to create fresh streams, or explicitly specify the validationMode " +
                "on your XmlBeanDefinitionReader instance.");
    }

    InputStream inputStream;
    try {
        inputStream = resource.getInputStream();
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
                "Did you attempt to load directly from a SAX InputSource without specifying the " +
                "validationMode on your XmlBeanDefinitionReader instance?", ex);
    }

    try {
        return this.validationModeDetector.detectValidationMode(inputStream);
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
                resource + "]: an error occurred whilst reading from the InputStream.", ex);
    }
}

该方法把侦测验证模式的工作委托给了专门处理类XmlValidationModeDetector,调用了其detectValidationMode方法:

XmlValidationModeDetector.java

public int detectValidationMode(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            boolean isDtdValidated = false;
            String content;
            while ((content = reader.readLine()) != null) {
                /** * 去除当前行的注释内容,比如当前行内容为<!-- a comment --> ,经过consumeCommentTokens方法后返回空字符串; */
                content = consumeCommentTokens(content);
                /** * 如果当前行在注释范围内,或者当前行不包含文本内容,就直接判断下一行 */
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }
                /** * 当前行是否含有"DOCTYPE"字符串,如果有,则使用的是DTD验证模式; */
                if (hasDoctype(content)) {
                    isDtdValidated = true;
                    break;
                }
                /** * 因为验证模式的定义一定在"<"这个字符之前,所以如果当前行包含"<",则表示获取验证模式的区域已经过去了,不再继续读下一行了 */
                if (hasOpeningTag(content)) {
                    // End of meaningful data...
                    break;
                }
            }
            /** * 如果xml中含有"DOCTYPE",则表明验证模式就是DTD,否则就是XSD */
            return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
        }
        catch (CharConversionException ex) {
            return VALIDATION_AUTO;
        }
        finally {
            reader.close();
        }
    }

从上面的代码可以看出来,Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。

4.获取Document

获得xml验证模式以后,重新回到XmlBeanDefinitionReader的doLoadDocument方法,

XmlBeanDefinitionReader.java

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

接下来进入DefaultDocumentLoader的loadDocument方法:

DefaultDocumentLoader.java

public Document loadDocument(InputSource inputSource,EntityResolver entityResolver ,ErrorHandler errorHandler, int validationMode, boolean namespaceAware ) throws Exception {
      DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode , namespaceAware);
       if (logger .isDebugEnabled()) {
           logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]" );
      }
      DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
       return builder.parse(inputSource);
 }

这部分的代码其实并没有太多需要描述的,因为通过SAX解析XML文档的套路大致都差不多,Spring在这里并没有特殊的地方,同样首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。

XmlBeanDefinitionReader的loadDocument方法的参数中的EntityResolver类型的值是通过getEntityResolver()获取的。EntityResolver的作用是什么?

1)EntityResolver的作用

解析xml时,SAX首先读取xml首部的声明部分,默认情况下,会按照声明中XSD或DTD的URI地址来下载相应的XSD或DTD文件,以此来验证xml是否正确。下载会有时间延迟,如果网络中断或者不可用导致找不到相应的XSD或DTD文件,就会报错。

EntityResolver是org.xml.sax package下面的一个接口,作用是项目本身就提供了一个如何用程序寻找XSD或DTD文件的方法,比如我们把XSD/DTD文件放在项目的某处,实现EntityResolver接口时可以直接读取并返回给SAX,这样就避免了通过网络来下载文件的问题。

EntityResolver接口包含如下方法:

public abstract InputSource resolveEntity(String publicId,String systemId ) throws SAXException, IOException;

这个方法针对不同的xml验证模式,参数publicId和systemId的值是不同的。如果是XSD,比如:
容器的基本实现_第2张图片

参数值为:
publicId:null
systemId:http://www.springframework.org/schema/beans/spring-beans.xsd

如果是DTD,比如:
这里写图片描述
参数值为:
publicId:-//Spring//DTD BEAN 2.0//EN
systemId:http:www.springframework.org/dtd/spring-beans-2.0dtd

现在回过头来看XmlBeanDefinitionReader的getEntityResolver()方法,

XmlBeanDefinitionReader.java

protected EntityResolver getEntityResolver() {
     if (this.entityResolver == null) {
          ResourceLoader resourceLoader = getResourceLoader();
          if (resourceLoader != null) {
               this.entityResolver = new ResourceEntityResolver(resourceLoader);
          }
          else {
               this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
          }
     }
     return this.entityResolver;
}

getResourceLoader()返回的是一个PathMatchingResourcePatternResolver类型的实例,所以返回的EntityResolver是ResourceEntityResolver类型的实例。

在执行builder.parse(inputSource)方法时,会调用ResourceEntityResolver(即EntityResolver类型的入参)的resolveEntity方法,该方法继续调用父类DelegatingEntityResolver的resolveEntity方法:

DelegatingEntityResolver.java

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        if (systemId != null) {
            if (systemId.endsWith(DTD_SUFFIX)) {
                return this.dtdResolver.resolveEntity(publicId, systemId);
            }
            else if (systemId.endsWith(XSD_SUFFIX)) {
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }
        return null;
    }

可以看到针对不同的xml验证模式,Spring使用了不同的解析器解析:
1)如果systemId以.dtd结尾,即DTD验证模式,使用的是BeansDtdResolver的resolveEntity方法。
2)如果systemId以.xsd结尾,即XSD验证模式,使用的是PluggableSchemaResolver的resolveEntity方法。因为大多数情况下使用xsd验证模式,所以看一下这个方法的源码:

public InputSource resolveEntity(String publicId, String systemId) throws IOException {
        if (logger.isTraceEnabled()) {
            logger.trace("Trying to resolve XML entity with public id [" + publicId +
                    "] and system id [" + systemId + "]");
        }

        if (systemId != null) {
            String resourceLocation = getSchemaMappings().get(systemId);
            if (resourceLocation != null) {
                Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
                try {
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
                    }
                    return source;
                }
                catch (FileNotFoundException ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
                    }
                }
            }
        }
        return null;
    }

在运行测试用例时,systemId传进来的值为http://www.springframework.org/schema/beans/spring-beans.xsd ,以此为key到getSchemaMappings()返回的Map中寻找匹配值,看一下getSchemaMappings()方法的源码:

private Map<String, String> getSchemaMappings() {
        if (this.schemaMappings == null) {
            synchronized (this) {
                if (this.schemaMappings == null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
                    }
                    try {
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded schema mappings: " + mappings);
                        }
                        Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
                        this.schemaMappings = schemaMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return this.schemaMappings;
    }

先把META-INF/spring.schemas中的内容加载进Properties实例中,然后再把它装进Map中,此时返回的Map内容为(一种可能的情形):

{
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans-4.0.xsd,
    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd,
    http://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd,
    http://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd,
    http://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd,
    http://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.2.xsd,
    http://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd,
    http://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd,
    http://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd,
    http://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-4.2.xsd,
    http://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd,
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd,
    http://www.springframework.org/schema/util/spring-util-4.2.xsd=org/springframework/beans/factory/xml/spring-util-4.2.xsd,
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd,
    http://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd,
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd,
    http://www.springframework.org/schema/util/spring-util-4.1.xsd=org/springframework/beans/factory/xml/spring-util-4.1.xsd,
    http://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-4.2.xsd,
    http://www.springframework.org/schema/tool/spring-tool-4.0.xsd=org/springframework/beans/factory/xml/spring-tool-4.0.xsd,
    http://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans-4.2.xsd,
    http://www.springframework.org/schema/tool/spring-tool-4.1.xsd=org/springframework/beans/factory/xml/spring-tool-4.1.xsd,
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd,
    http://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd,
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans-4.1.xsd,
    http://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd,
    http://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd,
    http://www.springframework.org/schema/tool/spring-tool-4.2.xsd=org/springframework/beans/factory/xml/spring-tool-4.2.xsd
}

根据systemId http://www.springframework.org/schema/beans/spring-beans.xsd取到的value是: org/springframework/beans/factory/xml/spring-beans-4.2.xsd,回过头来看PluggableSchemaResolver的resolveEntity方法,则是去classpath下的对应的org/springframework/beans/factory/xml/spring-beans-4.2.xsd路径下寻找spring-beans-4.2.xsd,并加载。 这样就完成了从本地项目中读取xsd而不是去网络上下载。

5.解析及注册BeanDefinitions

当把文件转换为Document后,接下来就是提取和注册Bean,程序进入XmlBeanDefinitionReader的registerBeanDefinitions方法。

XmlBeanDefinitionReader.java

public int registerBeanDefinitions (Document doc , Resource resource ) throws BeanDefinitionStoreException {
          /** * 返回的是类DefaultBeanDefinitionDocumentReader的实例 */
         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
          /** * 记录统计前BeanDefinition的加载个数 */
          int countBefore = getRegistry().getBeanDefinitionCount();
          /** * 加载和注册Bean */
          documentReader.registerBeanDefinitions(doc , createReaderContext(resource ));
          /** * 记录本次加载的BeanDefinition的个数 */
          return getRegistry().getBeanDefinitionCount() - countBefore ;
    }

跟进DefaultBeanDefinitionDocumentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext )方法,发现这个方法的主要目的就是获取root结点,以便于再次将root作为参数继续BeanDefinition的注册:

DefaultBeanDefinitionDocumentReader.java

     public void registerBeanDefinitions(Document doc , XmlReaderContext readerContext ) {
          this .readerContext = readerContext;
          logger .debug("Loading bean definitions" );
         Element root = doc .getDocumentElement();
          doRegisterBeanDefinitions( root);
    }

看一下doRegisterBeanDefinitions(root)的具体实现:

DefaultBeanDefinitionDocumentReader.java

protected void doRegisterBeanDefinitions(Element root) {
        /** * 任何一个嵌入的<beans>元素都会在这个方法中造成递归。为了正确的传递和保护<beans>的defalt-*这类的属性, * 需要跟踪当前的delegate(parent),delegate可能为null。创建一个引用指向parent新的delegate用于回退, * 最终把this.delegate设置为初始的引用(parent)。 */
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        /** * 根节点使用的是否是默认的namespace:返回true的情形:root.getNamespaceURI()要么为空, * 要么是http://www.springframework.org/schema/beans */
        if (this.delegate.isDefaultNamespace(root)) {

            /** * 如果root含有profile属性,则判断profile的值是否符合环境变量中定义的(web.xml中的context-param来定义); * 符合继续,否则return; */
            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)) {
                    return;
                }
            }
        }

        /** * preProcessXml(root)和postProcessXml(root)方法体是空的,这里应用了设计模式里的 * 模板方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解 * 析前后做一些处理的话,只需要重写这两个方法就可以了 */
        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

接下来进入parseBeanDefinitions(root,this.delegate)方法:
DefaultBeanDefinitionDocumentReader.java

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)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

在Spring xml配置文件里有两大类Bean声明,一类是使用默认的命名空间,比如:

<bean id="cat" class="test.Cat" />

另一类是使用自定义的命名空间,比如

 <tx:annotation-driven />

判断是否是默认命名空间,取决于root.getNamespaceURI()的值是否为
http://www.springframework.org/schema/beans,或者是否为空(如下代码)。

public boolean isDefaultNamespace(Node node) {
    return isDefaultNamespace(getNamespaceURI(node));
}

public boolean isDefaultNamespace(String namespaceUri) {
    return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

这两种方式的读取及解析差别很大,对于默认的namespace,使用parseDefaultElement方法进行解析;如果是自定义的namespace,则使用delegate.parseCustomElement方法解析。各自的解析细节在下一节中作讨论。

你可能感兴趣的:(spring,源码)