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>
-
- publicld: null
- systemld: http://www.springframework.org/schema/beans/spring-beans.xsd
- DTD 验证模式
- 配置如下:
<?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>
-
- publicId:-//Spring//DTD BEAN 2.0//EN
- systemId:http://www.springframework.org/dtd/spring-beans.dtd
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 :这个从命名就可以大概知道是个委托类,内部其实是直接委托给 BeansDtdResolver 和 PluggableSchemaResolver 来进行解析,实现任意类型的解析( 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、参考
- spring 官方文档 5.2.3.RELEASE:https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html
- Spring源码深度解析(第2版),郝佳,P37-P40
- 死磕Spring系列:死磕 Spring or 【死磕 Spring】----- IOC 之 获取 Document 对象
- 芋道源码:http://svip.iocoder.cn/Spring/IoC-load-Document/
- 相关注释可参考笔者 github 链接:https://github.com/wpbxin/spring-framework
- 相关的 UML 图都可以子模块的 diagram 目录下查找
加载BeanDefinition: