本文主要介绍如何获取 XML 配置文件的 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 实例,目录结构如下:
- 1、DocumentLoader
- 2、DefaultDocumentLoader
- 3、EntityResolver
- 3.1、EntityResolver 的4个子类
- 3.1.1、BeansDtdResolver
- 3.1.2、PluggableSchemaResolver
- 3.1.3、DelegatingEntityResolver
- 3.1.4、ResourceEntityResolver
- 3.2 自定义 EntityResolver
- 3.3、其他说明
- 4、参考
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 来自定义如何寻找【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());
3.2、自定义 EntityResolver
前面介绍 EntityResolver 时已经举过例子了,主要就是重写 EntityResolver.resolveEntity(String publicId, String systemId) 方法,自定义解析过程,此不赘述。
3.3、其他说明
自定义如何寻找【DTD/XSD 验证文件】的逻辑