zoukankan      html  css  js  c++  java
  • Spring5源码分析(009)——IoC篇之加载BeanDefinition:获取 Document 实例

    注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总


      上一篇《Spring5源码分析(007)——IoC篇之加载BeanDefinition总览》 中提到,加载 bean 的核心方法 doLoadBeanDefinitions(InputSource inputSource, Resource resource) 中,分为3个步骤来进行:

    • 1、通过调用 getValidationModeForResource(Resource resource) 来获取指定 XML 资源的验证模式,也即是 xml 开头常见到的各种 DTD 和 XSD 了。(参考博客:Spring5源码分析(008)——IoC篇之加载BeanDefinition:获取XML的验证模式
    • 2、通过调用 DocumentLoader.loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 来获取实际的 Document 实例。
    • 3、调用 registerBeanDefinitions(Document doc, Resource resource),根据 Document 解析和注册 BeanDefinition。

      本文主要介绍第2个步骤,也就是获取 Document 实例,目录结构如下:

      XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法并没有亲自处理 Document 实例的加载获取,而是委托给专门的处理接口 DocumentLoader ,通过调用 DocumentLoader.loadDocument 来获取 Document 实例,这里实际使用的是其默认实现类 DefaultDocumentLoader 。 

    1、DocumentLoader

      org.springframework.beans.factory.xml.DocumentLoader :定义从资源文件加载到转换为 Document 的功能。其内部接口声明如下:

    /**
     * Strategy interface for loading an XML {@link Document}.
     * <p>加载 XML 的策略接口
     */
    public interface DocumentLoader {
    
       /**
        * Load a {@link Document document} from the supplied {@link InputSource source}.
        * @param inputSource the source of the document that is to be loaded 将要加载的 document 的 Resource 输入资源
        * @param entityResolver the resolver that is to be used to resolve any entities 解析器
        * @param errorHandler used to report any errors during document loading 处理加载文档过程中出现的错误
        * @param validationMode the type of validation XML验证模式
        * {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD}
        * or {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD})
        * @param namespaceAware {@code true} if support for XML namespaces is to be provided 是否支持 XML 命名空间,true 表示支持 XML 命名空间
        * @return the loaded {@link Document document}
        * @throws Exception if an error occurs
        */
       Document loadDocument(
             InputSource inputSource, EntityResolver entityResolver,
             ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
             throws Exception;
    
    }

       摘抄一下入参说明:

    • inputSource: the source of the document that is to be loaded ,将要加载的 document 的 Resource 输入资源
    • entityResolver: the resolver that is to be used to resolve any entities ,解析器
    • errorHandler: used to report any errors during document loading ,处理加载文档过程中出现的错误
    • validationMode: the type of validation ,XML验证模式
    • namespaceAware: true if support for XML namespaces is to be provided ,是否支持 XML 命名空间,true 表示支持 XML 命名空间

    2、DefaultDocumentLoader

      实际上进行处理的是 DocumentLoader 的默认实现类 org.springframework.beans.factory.xml.DefaultDocumentLoader ,具体如下:

    /**
     * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
     * XML parser.
     * <p>在提供的 InputSource 上通过标准的 JAXP 配置的 XML 解析器来加载 Document 实例
     */
    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        // 1、创建 DocumentBuilderFactory 实例
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isTraceEnabled()) {
            logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        // 2、创建 DocumentBuilder 实例
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        // 3、解析 XML InputSource 然后返回 Document 实例对象
        return builder.parse(inputSource);
    }

       这里其实就是通过 SAX 解析 XML 文档。首先是创建 DocumentBuilderFactory ,再通过 DocumentBuilderFactory 创建 DocumentBuilder ,进而解析 inputSource 来返回 Document 实例对象。

    • 首先,1、创建 DocumentBuilderFactory 实例:这里通过调用 createDocumentBuilderFactory(int validationMode, boolean namespaceAware) 方法来创建 javax.xml.parsers.DocumentBuilderFactory 实例对象:
    /**
     * Create the {@link DocumentBuilderFactory} instance.
     * <p>创建 DocumentBuilderFactory 实例
     * @param validationMode the type of validation: {@link XmlValidationModeDetector#VALIDATION_DTD DTD}
     * or {@link XmlValidationModeDetector#VALIDATION_XSD XSD})
     * @param namespaceAware whether the returned factory is to provide support for XML namespaces
     * @return the JAXP DocumentBuilderFactory
     * @throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory
     */
    protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
            throws ParserConfigurationException {
        // 创建 DocumentBuilderFactory 实例
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(namespaceAware);    // 设置命名空间支持
        // 有验证模式
        if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
            factory.setValidating(true);    // 开启校验
            // XSD 验证模式
            if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
                // Enforce namespace aware for XSD...
                factory.setNamespaceAware(true);    // XSD 验证模式支持命名空间
                try {
                    factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
                }
                catch (IllegalArgumentException ex) {
                    ParserConfigurationException pcex = new ParserConfigurationException(
                            "Unable to validate using XSD: Your JAXP provider [" + factory +
                            "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                            "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                    pcex.initCause(ex);
                    throw pcex;
                }
            }
        }
    
        return factory;
    }
    • 然后,2、创建 DocumentBuilder 实例:通过调用 createDocumentBuilder(DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler) 方法,创建 javax.xml.parsers.DocumentBuilder 实例对象:
    /**
     * Create a JAXP DocumentBuilder that this bean definition reader
     * will use for parsing XML documents. Can be overridden in subclasses,
     * adding further initialization of the builder.
     * <p>创建这个 bean definition reader 用于解析 XML 文档的 JAXP DocumentBuilder。
     * 子类可以重写,以便为 builder 添加更多的初始化配置。
     * @param factory the JAXP DocumentBuilderFactory that the DocumentBuilder
     * should be created with
     * @param entityResolver the SAX EntityResolver to use
     * @param errorHandler the SAX ErrorHandler to use
     * @return the JAXP DocumentBuilder
     * @throws ParserConfigurationException if thrown by JAXP methods
     */
    protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
            @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
            throws ParserConfigurationException {
    
        // 创建 DocumentBuilder 对象
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        // 设置 EntityResolver 属性
        if (entityResolver != null) {
            docBuilder.setEntityResolver(entityResolver);
        }
        // 设置 ErrorHandler 属性
        if (errorHandler != null) {
            docBuilder.setErrorHandler(errorHandler);
        }
        return docBuilder;
    }
    •  最后,3、解析 XML InputSource 然后返回 Document 实例对象:通过调用 DocumentBuilder.parse(org.xml.sax.InputSource is) 对 InputSource 进行解析,返回对应的 Document 实例对象。

      这部分代码和通常的通过 SAX 解析 XML 文档的方式大致一致。 Spring 在这里的处理并没有什么特殊的地方,同样首先创建 DocumentBuilderFactory ,再通过 DocumentBuilderFactory 创建 DocumentBuilder ,进而解析 inputSource 来返回 Document 对象。

      这里需要注意的是 DocumentBuilder 的 EntityResolver 属性,接下来会进行讲解。

    3、EntityResolver

      在 DocumentLoader.loadDocument(...) 方法中涉及一个参数 EntityResolver ,该参数是通过 XmlBeanDefinitionReader.getEntityResolver() 方法来获取的,代码如下:

    /**
     * Return the EntityResolver to use, building a default resolver
     * if none specified.
     * <p>返回使用的 EntityResolver ,如果没有指定的话返回默认的 resolver
     */
    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;
    }
    •  如果 ResourceLoader 不为 null ,则根据指定的 ResourceLoader 创建一个 ResourceEntityResolver 对象。(这里的 resourceLoader 基本上就是之前提到的 PathMatchingResourcePatternResolver ,而 ResourceEntityResolver 是 DelegatingEntityResolver 的子类)
    • 如果 ResourceLoader 为 null ,则创建一个 DelegatingEntityResolver 对象。该 Resolver 委托给默认的 BeansDtdResolver 和 PluggableSchemaResolver 。

      后面会对这几个子类进行说明。这里先回到 org.xml.sax.EntityResolver (JDK 本身的类),它有什么作用呢?我们先看下 EntityResolver 的 javadoc 说明:


      如果 SAX 应用程序需要实现对外部实体的定制处理,它就必须实现这个接口并使用 setEntityResolver 方法向 SAX 驱动程序注册一个实例。

      然后, XML reader 将允许应用程序在包含任何外部实体之前拦截它们(包括外部 DTD 子集和外部参数实体,如果有的话)。

      许多 SAX 应用程序将不需要实现这个接口,但是对于从数据库或其他专用输入源构建 XML 文档的应用程序,或者使用 url 以外的 URI 类型的应用程序,它将特别有用。

      下面这个解析器将为系统标识符为“http://www.myhost.com/today”的实体提供一个特殊的字符流:

    import org.xml.sax.EntityResolver;
    import org.xml.sax.InputSource;
    
    public class MyResolver implements EntityResolver {
        public InputSource resolveEntity (String publicId, String systemId)
        {
            if (systemId.equals("http://www.myhost.com/today")) {
                 // return a special input source
                 MyReader reader = new MyReader();
                return new InputSource(reader);
            } else {
                // use the default behaviour
                return null;
            }
        }
    }

       应用程序还可以使用这个接口将系统标识符重定向到本地 uri,或者在目录中查找替换的标识符(可能使用公共标识符)。


       再来看看《Spring源码深度解析(第2版)》P38 的一段说明:

      对于解析一个 XML, SAX 首先读取该 XML 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档进行一个验证。 默认的寻找规则,即通过网络(实现上就是声明的 DTD 的 URI 地址)来下载相应的 DTD 声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的 DTD 声明没有被找到的原因。

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

      这下子就清晰点了,EntityResolver 的作用:通过实现 EntityResolver 来自定义如何寻找【DTD/XSD 验证文件】的逻辑。EntityResolver 接口代码如下: 

    public interface EntityResolver {
    
        public abstract InputSource resolveEntity (String publicId, String systemId)
            throws SAXException, IOException;
    
    }

       唯一的方法 InputSource resolveEntity (String publicId, String systemId) 有2个入参 publicId 和 systemId ,返回的是 org.xml.sax.InputSource 对象(null 则表示解析器需要打开一个常规的系统标识符 URI 连接)。2个参数声明如下:

    • publicId :被引用的外部实体的公共标识符,如果没有提供,则是 null
    • systemId :被引用的外部实体的系统标识符。

      这两个参数于具体的验证模式关系如下:

    • XSD 验证模式
      • 配置如下:
    <?xml version="l.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>
    <?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>

    3.1、EntityResolver 的4个子类

      前面的 getEntityResolver() 方法一共提到了 EntityResolver 的4个子类:ResourceEntityResolver / DelegatingEntityResolver / BeansDtdResolver / PluggableSchemaResolver ,接下来一一进行介绍。

    3.1.1、BeansDtdResolver

      org.springframework.beans.factory.xml.BeansDtdResolver : EntityResolver 实现类,Spring beans DTD 的解析器,从 Spring 的 classpath 或者 JAR 文件中加载 DTD。具体实现也是很直接:检查是否 .dtd 后缀,然后再判定是否包含 spring-beans ,是的话,之后便是构造 ClassPathResource 对象(类路径下的 spring-beans.dtd ,即 /org/springframework/beans/factory/xml/spring-beans.dtd ),通过此 ClassPathResource 的输入流再构造 InputSource ,并设置内部的 publicId、systemId 属性,然后返回。如果都不是,或者构造失败,则直接返回 null 。代码如下:

    /**
     * <p>EntityResolver 实现类,Spring beans DTD 的解析器,从 Spring 的 classpath 或者 JAR 文件中加载 DTD。
     * <p>无论是否在 DTD 名称中指定了包含 "spring-beans" 的 URL,
     * 还是使用 "https://www.springframework.org/dtd/spring-beans-2.0.dtd",
     * 都会从 classpath 资源 "/org/springframework/beans/factory/xml/spring-beans.dtd" 下
     * 抓取 spring-beans.dtd" 文件 ,
     *
     */
    public class BeansDtdResolver implements EntityResolver {
        // dtd 扩展名
        private static final String DTD_EXTENSION = ".dtd";
        // Spring beans DTD 的文件名
        private static final String DTD_NAME = "spring-beans";
    
        @Override
        @Nullable
        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 + "]");
            }
            // 以 .dtd 结尾
            if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
                // 获取最后一个 / 的索引位置
                int lastPathSeparator = systemId.lastIndexOf('/');
                // 获取 spring-beans 的位置
                int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
                // 存在 spring-beans
                if (dtdNameStart != -1) {
                    String dtdFile = DTD_NAME + DTD_EXTENSION;
                    if (logger.isTraceEnabled()) {
                        logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
                    }
                    try {
                        // 创建 ClassPathResource 对象,相对于当前类对象路径的资源,其实就是 /org/springframework/beans/factory/xml/spring-beans.dtd
                        Resource resource = new ClassPathResource(dtdFile, getClass());
                        // 创建 InputSource 对象,并设置 publicId 和 systemId
                        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;
        }
    
    }

      

    3.1.2、PluggableSchemaResolver

      org.springframework.beans.factory.xml.PluggableSchemaResolver :EntityResolver 实现类,使用一组映射文件将模式 url 解析为本地类路径资源的 EntityResolver 实现。默认情况下,读取 classpath 下所有的 META-INF/spring.schemas ,转化成一个 schema URL 与 local schema path 的 map 。PluggableSchemaResolver 的解析过程如下:

    /**
     * The location of the file that defines schema mappings.
     * Can be present in multiple JAR files.
     * <p>定义 schema 映射的文件路径,可以出现在多个JAR文件中。
     */
    public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
    
    @Nullable
    private final ClassLoader classLoader;
    // schema 映射文件路径
    private final String schemaMappingsLocation;
    
    // schema URL 到 本地 schema 路径 的映射集合
    /** Stores the mapping of schema URL -> local schema path. */
    @Nullable
    private volatile Map<String, String> schemaMappings;
    
    @Override
    @Nullable
    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 对应的 Resource 所在路径
            String resourceLocation = getSchemaMappings().get(systemId);
            if (resourceLocation == null && systemId.startsWith("https:")) {
                // Retrieve canonical http schema mapping even for https declaration
                // 若申明的 https 映射找不到,找检索对应的 http 模式映射
                resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
            }
            if (resourceLocation != null) {
                // 创建 ClassPathResource 实例对象
                Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
                try {
                    // 创建 InputSource 对象,并设置 publicId 和 systemId
                    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;
    }

       上面提到的 schema URL 与 local schema path 的 map ,其实是通过调用 getSchemaMappings() 方法来实现的:

    /**
     * Load the specified schema mappings lazily.
     * <p>懒加载指定的 schema mappings ,默认从 "META-INF/spring.schemas" 进行加载
     */
    private Map<String, String> getSchemaMappings() {
        Map<String, String> schemaMappings = this.schemaMappings;
        // 双重检查锁,实现 schemaMappings 单例
        if (schemaMappings == null) {
            synchronized (this) {
                schemaMappings = this.schemaMappings;
                if (schemaMappings == null) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
                    }
                    try {
                        // 加载 schemaMappingsLocation(默认是"META-INF/spring.schemas")文件中的属性
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loaded schema mappings: " + mappings);
                        }
                        schemaMappings = new ConcurrentHashMap<>(mappings.size());
                        // 将 Properties 转成 schemaMappings
                        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;
    }

       spring-beans 模块中默认的 META-INF/spring.schemas 文件如下,可以看到都映射到了类路径下的 xsd 了。

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

      

    3.1.3、DelegatingEntityResolver

      org.springframework.beans.factory.xml.DelegatingEntityResolver :这个从命名就可以大概知道是个委托类,内部其实是直接委托给 BeansDtdResolverPluggableSchemaResolver 来进行解析,实现任意类型的解析( dtd 和 xsd )。

    public class DelegatingEntityResolver implements EntityResolver {
    
        // dtd 文件后缀
        /** Suffix for DTD files. */
        public static final String DTD_SUFFIX = ".dtd";
    
        // xsd 文件后缀
        /** Suffix for schema definition files. */
        public static final String XSD_SUFFIX = ".xsd";
    
        // dtd 解析器
        private final EntityResolver dtdResolver;
    
        // xsd 解析器
        private final EntityResolver schemaResolver;
    
        /**
         * Create a new DelegatingEntityResolver that delegates to
         * a default {@link BeansDtdResolver} and a default {@link PluggableSchemaResolver}.
         * <p>创建一个新的 DelegatingEntityResolver 实例对象,委托给默认的 BeansDtdResolver 和默认的 PluggableSchemaResolver 。
         *
         * <p>Configures the {@link PluggableSchemaResolver} with the supplied
         * {@link ClassLoader}.
         * <p>使用提供的类加载器配置 PluggableSchemaResolver 。
         *
         * @param classLoader the ClassLoader to use for loading
         * (can be {@code null}) to use the default ClassLoader)
         */
        public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
            this.dtdResolver = new BeansDtdResolver();
            this.schemaResolver = new PluggableSchemaResolver(classLoader);
        }
    
        @Override
        @Nullable
        public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
                throws SAXException, IOException {
    
            if (systemId != null) {
                // .dtd 后缀的解析,即 DTD 模式
                if (systemId.endsWith(DTD_SUFFIX)) {
                    return this.dtdResolver.resolveEntity(publicId, systemId);
                }
                // .xsd 后缀的解析,即 XSD 模式
                else if (systemId.endsWith(XSD_SUFFIX)) {
                    return this.schemaResolver.resolveEntity(publicId, systemId);
                }
            }
    
            // Fall back to the parser's default behavior.
            return null;
        }
    
    }

       没什么特殊的处理,就是直接通过 systemId 的后缀来进行不同类型的解析委托。

    3.1.4、ResourceEntityResolver

      org.springframework.beans.factory.xml.ResourceEntityResolver :继承自 DelegatingEntityResolver ,先委托父类 DelegatingEntityResolver 进行解析,如果失败,则通过 resourceLoader 进行加载,甚至是直接打开 URL 流进行加载。具体实现如下:

    @Override
    @Nullable
    public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
            throws SAXException, IOException {
    
        // 先委托父类 DelegatingEntityResolver 的方法进行解析
        InputSource source = super.resolveEntity(publicId, systemId);
    
        // 解析失败,使用 resourceLoader 进行解析
        if (source == null && systemId != null) {
            String resourcePath = null;
            try {
                // 使用 UTF-8 解码 systemId
                String decodedSystemId = URLDecoder.decode(systemId, "UTF-8");
                // 转换成 URL 字符串
                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 + "]");
                }
                // 通过 resourceLoader 获取资源
                Resource resource = this.resourceLoader.getResource(resourcePath);
                // 创建 InputSource 实例对象,并设置 publicId 和 systemId 属性
                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
                // 将 http 声明转成 https
                String url = systemId;
                if (url.startsWith("http:")) {
                    url = "https:" + url.substring(5);
                }
                try {
                    // 通过构造 URL 来获取 InputSource 实例对象,并设置 publicId 和 systemId 属性
                    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;
    }

       接下来我们来验证下错误引用时是很么情况,XML 配置如下 ( cn/wpbxin/spring-xmlbeans.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-beans2.xsd">
    
    </beans>

       读取的代码如下: 

    package cn.wpbxin.bean;
    
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;
    
    public class XmlBeanDefinitionReaderTest {
       public static void main(String[] args) {
          Resource resource = new ClassPathResource("cn/wpbxin/bean/spring-xmlbeans.xml");  // (1.1)
          // ClassPathResource resource = new ClassPathResource("beans.xml");  // (1.2)
          // Resource[] resources = PathMatchingResourcePatternResolver.getResources(locationPattern);  // (1.3),需要遍历获取 BeanDefinition
          DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
          XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
          reader.loadBeanDefinitions(resource);
       }
    
    }

      最终可发现是在 source = new InputSource(new URL(url).openStream()); 这里就出问题了,因为确实连接不上 'http://www.springframework.org/schema/beans/spring-beans2.xsd' 。报错日志如下:

    3.2、自定义 EntityResolver

      前面介绍 EntityResolver 时已经举过例子了,主要就是重写 EntityResolver.resolveEntity(String publicId, String systemId) 方法,自定义解析过程,此不赘述。

    3.3、其他说明

      Doument 实例的获取是常规的 SAX 解析 XML 文档。后面着重介绍的 EntityResolver ,其实是 自定义如何寻找【DTD/XSD 验证文件】的逻辑 ,这样就大概知道了配置文件中开头的 xsd 声明的作用了。

    4、参考

    加载BeanDefinition:

  • 相关阅读:
    CSS 文本
    javascript:void(0)的问题
    剑指offer
    牛课--C/C++
    Linux学习--第二波
    面经-csdn
    初学Linux
    二分查找法的实现和应用汇总
    vs2013下git的使用
    win10+vs2013+Qt5.4 安装方法
  • 原文地址:https://www.cnblogs.com/wpbxin/p/13436741.html
Copyright © 2011-2022 走看看