zoukankan      html  css  js  c++  java
  • 通俗理解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源码深度解析。

  • 相关阅读:
    C# 图片与Base64的相互转化
    LeetCode 303. Range Sum Query – Immutable
    LeetCode 300. Longest Increasing Subsequence
    LeetCode 292. Nim Game
    LeetCode 283. Move Zeroes
    LeetCode 279. Perfect Squares
    LeetCode 268. Missing Number
    LeetCode 264. Ugly Number II
    LeetCode 258. Add Digits
    LeetCode 257. Binary Tree Paths
  • 原文地址:https://www.cnblogs.com/xiaohang123/p/12722065.html
Copyright © 2011-2022 走看看