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:

posted @ 2020-08-04 23:10  心明谭  阅读(301)  评论(0编辑  收藏  举报