zoukankan      html  css  js  c++  java
  • Spring源码分析(五)获取Document

    摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。

    这一篇开始进行Document加载了,XmlBeanFactoryReader类对于文档读取并没有亲历亲为,而是委托给了DocumentLaoder去执行,DocumentLoader是个接口,真正调用的是DefaultDocumentLoader,解析代码如下:

    /**
     * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
     * XML parser.
     */
    @Override
    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对象。这里有必要提及一下EntityResolver,对于参数entityResolver,传入的是通过getEntityResolver() 函数获取的返回值,如下代码:

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

    那么,EntityResolver到底是做什么用的呢?

    EntityResolver用法

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

    enntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找相应的声明。

    首先看enntityResolver的接口方法声明:

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

    这里,它接受两个参数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/schema/beans/spring-beans.xsd">
    <beans">
        ......
    </beans>

    读取到以下两个参数:

    • publicId:-//Spring//DTD BEAN 2.0//EN
    • systemId:http://www.springframework.org/schema/beans/spring-beans.xsd

    之前已经提到过,验证文件默认的加载方式是通过URL进行网络下载,这样会造成延时,用户体验也不好,一般的做法是将验证文件放置在自己的工程里,那么怎么做才能将这个URL转换为自己工程里对应的地址文件呢?我们以加载DTD文件为例来看看Spring中是如何实现的。根据之前Spring中通过getEntityResolver()方法对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntity实现方法如下:

    @Override
    @Nullable
    public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
        if (systemId != null) {
            if (systemId.endsWith(DTD_SUFFIX)) {
                // 如果是dtd从这里解析
                return this.dtdResolver.resolveEntity(publicId, systemId);
            }
            else if (systemId.endsWith(XSD_SUFFIX)) {
                // 通过调用META-INF/Spring.schemas解析
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }
        return null;
    }

    我们可以看到,对不同的验证模式,Spring使用了不同的解析器解析。这里简单描述一下原理,比如加载DTD类型的BeanDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载。下面是BeansDtdResolver的源码:

    @Override
    @Nullable
    public InputSource resolveEntity(String publicId, @Nullable 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_EXTENSION)) {
            int lastPathSeparator = systemId.lastIndexOf('/');
            int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
            if (dtdNameStart != -1) {
                String dtdFile = DTD_NAME + DTD_EXTENSION;
                if (logger.isTraceEnabled()) {
                    logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
                }
                try {
                    Resource resource = new ClassPathResource(dtdFile, getClass());
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                    }
                    return source;
                }
                catch (IOException ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
                    }
                }
    
            }
        }
    
        // Use the default behavior -> download from website or wherever.
        return null;
    }
  • 相关阅读:
    7、对齐工具
    6、复制命令
    5、木地板制作
    4、创建扩展基本体
    3、创建标准基本体
    2、标准基本体
    1、视图导航和显示控制
    2、如何复印双面都有文字的纸张?
    让人误解的英语俚语
    MATLAB PDE工具箱简单教程
  • 原文地址:https://www.cnblogs.com/warehouse/p/9376955.html
Copyright © 2011-2022 走看看