Spring源码解析---XML解析

1.获取xml文件的验证模式

2.加载XML文件,并得到对应的Document。

3.根据赶回的Document注册Bean信息。

这三个步骤支撑整个Spring容器的部分实现。

1.获取XML的验证模式
XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD

DTD和XSD区别
DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符合规则

1.1验证模式的读取
Spring通过getValidationModeForResource方法来获取对应资源的验证模式

protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = this.getValidationMode();
    //如果手动指定了验证模式则使用指定的验证模式
    if(validationModeToUse != 1) {
        return validationModeToUse;
    } else {
        //如果未指定则使用自动检测
        int detectedMode = this.detectValidationMode(resource);
        return detectedMode != 1?detectedMode:3;
    }
}
自动检测验证模式的功能是在函数detectValidationMode方法中实现的,在detectValidationMode函数中又将自动检测验证模式的工作委托给了专门处理类XmlValidationModeDetector的detectValidationMode方法

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.");
    } else {
        InputStream inputStream;
        try {
            inputStream = resource.getInputStream();
        } catch (IOException var5) {
            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?", var5);
        }

        try {
            return this.validationModeDetector.detectValidationMode(inputStream);
        } catch (IOException var4) {
            throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", var4);
        }
    }
}
XmlValidationModeDetector类中的detectValidationMode方法如下

public int detectValidationMode(InputStream inputStream) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

    try {
        boolean isDtdValidated = false;

        while(true) {
            String content;
            if((content = reader.readLine()) != null) {
                content = this.consumeCommentTokens(content);
                //如果读取的行是空或者注释则略过
                if(this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }

                if(this.hasDoctype(content)) {
                    isDtdValidated = true;
                } else if(!this.hasOpeningTag(content)) {//读取到<开始符号,验证模式一定会会在开始符号之前
                    continue;
                }
            }

            int var6 = isDtdValidated?2:3;
            return var6;
        }
    } catch (CharConversionException var9) {
        ;
    } finally {
        reader.close();
    }

    return 1;
}
Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD

2.获取Document
经过了验证模式准备的步骤就可以进行Document加载了,对于文档的读取委托给了DocumentLoader去执行,这里的DocumentLoader是个接口,而真正调用的是DefaultDocumentLoader,解析代码如下

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

    DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
首选创建DocumentBuildFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析InputSource来返回Document对象。对于参数entityResolver,传入的是通过getEntityResolver()函数获取的返回值,代码如下

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

    return this.entityResolver;
}
EntityResolver用法
如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证,默认的寻找规则,即通过网络(实现上就是声明DTD的URI地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因

EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可,entityResolver的接口方法声明:

InputSource resolveEntity(String publicId, String systemId)
1
这里,它接收两个参数publicId和systemId,并返回一个InputSource对象,以特定配置文件来进行讲解

(1)如果在解析验证模式为XSD的配置文件,代码如下

<?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">
....
</beans>
读取到以下两个参数

publicId:null
systemId:http://www.Springframework.org/schema/beans/Spring-beans.xsd
(2)如果解析验证模式为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>
读取到以下两个参数

publicId:-//Spring//DTD BEAN 2.0//EN
systemId:http://www.Springframework.org/dtd/Spring-beans-2.0.dtd
一般都会把验证文件放置在自己的工程里,如果把URL转换为自己工程里对应的地址文件呢?以加载DTD文件为例来看看Spring是如何实现的。根据之前Spring中通过getEntityResolver()方法对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntity实现方法如下:

//DelegatingEntityResolver.java

    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    if(systemId != null) {
        if(systemId.endsWith(".dtd")) {
            //dtd从这里解析
            return this.dtdResolver.resolveEntity(publicId, systemId);
        }

        if(systemId.endsWith(".xsd")) {
            //通过调用META-INF/Spring.schemas解析
            return this.schemaResolver.resolveEntity(publicId, systemId);
        }
    }

    return null;
}
对不同的验证模式,Spring使用了不同的解析器解析,比如加载DTD类型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载

//BeansDtdResolver.java

    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 && systemId.endsWith(".dtd")) {
        int lastPathSeparator = systemId.lastIndexOf("/");
        String[] var7 = DTD_NAMES;
        int var6 = DTD_NAMES.length;

        for(int var5 = 0; var5 < var6; ++var5) {
            String DTD_NAME = var7[var5];
            // DTD_NAMES = {"Spring-beans-2.0", "Spring-beans"}
            int dtdNameStart = systemId.indexOf(DTD_NAME);
            if(dtdNameStart > lastPathSeparator) {
                String dtdFile = systemId.substring(dtdNameStart);
                if(logger.isTraceEnabled()) {
                    logger.trace("Trying to locate [" + dtdFile + "] in Spring jar");
                }

                try {
                    ClassPathResource ex = new ClassPathResource(dtdFile, this.getClass());
                    InputSource source = new InputSource(ex.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if(logger.isDebugEnabled()) {
                        logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                    }

                    return source;
                } catch (IOException var12) {
                    if(logger.isDebugEnabled()) {
                        logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", var12);
                    }
                }
            }
        }
    }

    return null;
}
3.解析并注册BeanDefinition
当把文件转换成Document后,接下来就是对bean的提取及注册,当程序已经拥有了XML文档文件的Document实例对象时,就会被引入下面这个方法

//XmlBeanDefinitionReader.java

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    //记录统计之前BeanDefinition的加载个数
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    //加载及注册bean
    documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
    //记录本次加载的BeanDefinition个数
    return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinitionDocumentReader是一个接口,而实例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader了,进入DefaultBeanDefinitionDocumentReader后,发现这个方法的重要目的之一就是提取root,以便于再次将root作为参数继续BeanDefinition的注册

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    this.logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    BeanDefinitionParserDelegate delegate = this.createHelper(readerContext, root);
    //解析前处理,留给子类实现
    this.preProcessXml(root);
    this.parseBeanDefinitions(root, delegate);
    //解析后处理,留给子类实现
    this.postProcessXml(root);
}
解析并注册BeanDefinition
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //对beans的处理
    if(delegate.isDefaultNamespace(delegate.getNamespaceURI(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;
                String namespaceUri = delegate.getNamespaceURI(ele);
                if(delegate.isDefaultNamespace(namespaceUri)) {
                    //对bean的处理
                    this.parseDefaultElement(ele, delegate);
                } else {
                    //对bean的处理
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值