• 通俗理解spring源码(四)—— 获取Docment


    通俗理解spring源码(四)—— 获取Docment

    上节讲到了xmlBeanDefinitionReader.doLoadDocument(InputSource inputSource, Resource resource)方法:

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

     getValidationModeForResource(resource)在这里,看看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,对于xmlBeanDefinitionReader来说,会new一个ResourceEntityResolver,后面会解释什么是EntityResolver。

      然后将EntityResolver返回作为loadDocument()的参数。

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

      调用DefaultDocumentLoader的loadDocument方法

        public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    
            DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
            if (logger.isTraceEnabled()) {
                logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
            }
            DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
            return builder.parse(inputSource);
        }
        protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
                @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
                throws ParserConfigurationException {
    
            DocumentBuilder docBuilder = factory.newDocumentBuilder();
            if (entityResolver != null) {
                docBuilder.setEntityResolver(entityResolver);
            }
            if (errorHandler != null) {
                docBuilder.setErrorHandler(errorHandler);
            }
            return docBuilder;
        }

      在loadDocument方法中,使用的DocumentBuilderFactory 、DocumentBuilder 等都是jdk中的类,加载xml资源也是调用的java中的方法,这里就不多说了,重点是docBuilder.setEntityResolver(entityResolver)方法,该方法将得到的entityResolver放到docBuilder中,也就是DocumentBuilder ,然后调用docBuilder.parse()。

      那么EntityResolver在生成document过程中起到什么作用呢?

    1、EntityResolver

      EntityResolver不是属于spring定义的,是jdk中org.xml.sax下的一个接口。

      官方是这样解释EntityResolver的:如果SAX应用程序实现自定义处理外部实体,则必须实现此接口,并使用setEntityResolver方法向SAX 驱动器注册一个实例。

      也就是说,对于解析一个xml,sax首先会读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档的进行验证,默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,就会找不到相应的dtd,就会报错。  

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

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

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

      接收2个参数,publicId ,systemId ,并返回一个InputStream对象

      如果我们在解析验证模式为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-2.5.xsd">
    ...
    </beans>

      读取得到以下参数
      publicId : null
      systemId : http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

      如果我们解析的是DTD的配置文件,代码如下

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans>
            ...
    </beans>

      上面说过,EntityResolver 会被set到DocumentBuilder 对象中,也就是说,在 DocumentBuilder .parse()过程中,EntityResolver 对象的resolveEntity会得到相应的参数publicId 和systemId ,然后调用其resolveEntity ()方法,返回的流InputSource 就是相应的DTD文件流或者XSD文件流,然后完成校验工作。

      所以,现在重点就是,不同的EntityResolver实现是以何种方式返回DTD或XSD文件流的。来看具体实现。

    2、DelegatingEntityResolver

        public InputSource resolveEntity(@Nullable String publicId, @Nullable 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);
                }
            }
    
            // Fall back to the parser's default behavior.
            return null;
        }

      根据systemId后缀,委派给dtdResolver和schemaResolver完成,dtdResolver和schemaResolver也是EntityResolver的实现,在 DelegatingEntityResolver构造中初始化。

        public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
            this.dtdResolver = new BeansDtdResolver();
            this.schemaResolver = new PluggableSchemaResolver(classLoader);
        }

    3、BeansDtdResolver

      BeansDtdResolver负责找到classpath下spring-beans.dtd文件的位置,返回文件流。

        public InputSource resolveEntity(@Nullable 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;
                    //dtdFile="spring-beans.dtd"
                    if (logger.isTraceEnabled()) {
                        logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
                    }
                    try {
                        //定位classpath资源文件
                        Resource resource = new ClassPathResource(dtdFile, getClass());
                        InputSource source = new InputSource(resource.getInputStream());
                        source.setPublicId(publicId);
                        source.setSystemId(systemId);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                        }
                        return source;
                    }
                    catch (FileNotFoundException ex) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
                        }
                    }
                }
            }
    
            // Fall back to the parser's default behavior.
            return null;
        }

    spring-beans.dtd文件在如下位置:

    4、PluggableSchemaResolver

        public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
        private volatile Map<String, String> schemaMappings;
        public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
            this.classLoader = classLoader;
            this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
        }

      PluggableSchemaResolver是重点,负责找到相对应的XSD文件,一般我们都是使用XSD作为检验文件。  

      DEFAULT_SCHEMA_MAPPINGS_LOCATION ,指明资源文件路径。

       可以看到,spring很贴心的将所有版本的xsd文件都准备了,即使没有指定版本号,也会有默认的xsd文件。

      schemaMappings负责保存资源路径与校验文件url的映射。即systemId为key,resourceLocation为value。

        public InputSource resolveEntity(@Nullable 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,也就是验证文件的url,找到本地文件路径
                String resourceLocation = getSchemaMappings().get(systemId);
                //如果没有找到,并且url是https开头,就转换为http开头的url
                if (resourceLocation == null && systemId.startsWith("https:")) {
                    // Retrieve canonical http schema mapping even for https declaration
                    resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
                }
                //定位本地资源文件
                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.isTraceEnabled()) {
                            logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
                        }
                        return source;
                    }
                    catch (FileNotFoundException ex) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
                        }
                    }
                }
            }
    
            // Fall back to the parser's default behavior.
            return null;
        }

      其中getSchemaMappings获取或初始化map容器,这里用到了懒加载

        private Map<String, String> getSchemaMappings() {
            Map<String, String> schemaMappings = this.schemaMappings;
            if (schemaMappings == null) {
                synchronized (this) {
                    schemaMappings = this.schemaMappings;
                    if (schemaMappings == null) {
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
                        }
                        try {
                            //这里负责初始化map
                            Properties mappings =
                                    PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
                            if (logger.isTraceEnabled()) {
                                logger.trace("Loaded schema mappings: " + mappings);
                            }
                            schemaMappings = new ConcurrentHashMap<>(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 schemaMappings;
        }

       通过调试,可以看到map中的中的内容

     4、ResourceEntityResolver

      在xmlBeanDefinitionReader中,默认会用这种EntityResolver。

    public class ResourceEntityResolver extends DelegatingEntityResolver {
    public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
                throws SAXException, IOException {
    
            InputSource source = super.resolveEntity(publicId, systemId);
            //调用父类DelegatingEntityResolver的方法,如果没有,就从网络获取
            if (source == null && systemId != null) {
                String resourcePath = null;
                try {
                    String decodedSystemId = URLDecoder.decode(systemId, "UTF-8");
                    String givenUrl = new URL(decodedSystemId).toString();
                    String systemRootUrl = new File("").toURI().toURL().toString();
                    // Try relative to resource base if currently in system root.
                    if (givenUrl.startsWith(systemRootUrl)) {
                        resourcePath = givenUrl.substring(systemRootUrl.length());
                    }
                }
                catch (Exception ex) {
                    // Typically a MalformedURLException or AccessControlException.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex);
                    }
                    // No URL (or no resolvable URL) -> try relative to resource base.
                    resourcePath = systemId;
                }
                if (resourcePath != null) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]");
                    }
                    Resource resource = this.resourceLoader.getResource(resourcePath);
                    source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Found XML entity [" + systemId + "]: " + resource);
                    }
                }
                else if (systemId.endsWith(DTD_SUFFIX) || systemId.endsWith(XSD_SUFFIX)) {
                    // External dtd/xsd lookup via https even for canonical http declaration
                    String url = systemId;
                    if (url.startsWith("http:")) {
                        url = "https:" + url.substring(5);
                    }
                    try {
                        source = new InputSource(new URL(url).openStream());
                        source.setPublicId(publicId);
                        source.setSystemId(systemId);
                    }
                    catch (IOException ex) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Could not resolve XML entity [" + systemId + "] through URL [" + url + "]", ex);
                        }
                        // Fall back to the parser's default behavior.
                        source = null;
                    }
                }
            }
    
            return source;
        }
    
    }

      该类首先会调用父类DelegatingEntityResolver的方法获取资源文件,如果没有就会从网络获取。不过一般都能从本地获取到。 

      走的太远,不要忘记为什么出发!

      再来看看这个方法,是不是清楚多了?

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

       至此,document对象获取完成。

      参考:spring源码深度解析。

  • 相关阅读:
    GNU编码标准
    glade2 与 glade 3 (转)
    简单 gcc 参数
    gtk 主循环函数
    指针与数组关联导致的一些现象
    C语言声明数组变量时,在什么情况下,可不指定数组大小
    文件复制函数的效率比较
    关于VMware虚拟机的上网
    errno的基本用法
    查找数组中最大的2个数
  • 原文地址:https://www.cnblogs.com/xiaohang123/p/12722065.html
Copyright © 2020-2023  润新知