目录
1、EntityResolver 介绍
官网解释:如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver 方法 向 SAX 驱动器注册一个实例。
对于 XML 文档的解析,SAX首先读取文档声明,根据文档声明去找 DTD 或 XSD ,然后对文档进行验证。默认的寻找规则,是先通过网络来下载 DTD 或XSD,然而网络是不可靠的,所以需要项目本身提供一个获取 DTD或XSD的方式。
EntityResolver 的作用就是自定义获取 DTD或 XSD的方式。
EntityResolver 源码:
package org.xml.sax;
import java.io.IOException;
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId,
String systemId)
throws SAXException, IOException;
}
接收两个参数:publicId,systemId。
XSD
配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
则 publicId 为 null,systemId为:https://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-2.0.dtd
2、Spring源码中类图
其中 EntityResolver 是 org.xml.sax.EntityResolver ,SAX解析XML包的类。
这里实现类 DelegatingEntityResolver 是核心类,源码如下
package org.springframework.beans.factory.xml;
import java.io.IOException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class DelegatingEntityResolver implements EntityResolver {
public static final String DTD_SUFFIX = ".dtd";
public static final String XSD_SUFFIX = ".xsd";
private final EntityResolver dtdResolver;
private final EntityResolver schemaResolver;
// 构造函数
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
// 加载DTD文件,文中继续介绍
this.dtdResolver = new BeansDtdResolver();
// 加载XSD文件,文中继续介绍
this.schemaResolver = new PluggableSchemaResolver(classLoader);
}
public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {
Assert.notNull(dtdResolver, "'dtdResolver' is required");
Assert.notNull(schemaResolver, "'schemaResolver' is required");
this.dtdResolver = dtdResolver;
this.schemaResolver = schemaResolver;
}
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws SAXException, IOException {
if(systemId != null) {
// DTD 方式
if(systemId.endsWith(".dtd")) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
// XSD 方式
if(systemId.endsWith(".xsd")) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
public String toString() {
return "EntityResolver delegating .xsd to " + this.schemaResolver + " and " + ".dtd" + " to " + this.dtdResolver;
}
}
DTD方式
BeansDtdResolver 源码,源码内有注释:主要是截取dtd尾部文件名,在类BeansDtdResolver 同目录下获取dtd文件。
同目录下dtd 如下图:
BeansDtdResolver 源码如下(内涵注释):
package org.springframework.beans.factory.xml;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
public class BeansDtdResolver implements EntityResolver {
private static final String DTD_EXTENSION = ".dtd";
private static final String DTD_NAME = "spring-beans";
private static final Log logger = LogFactory.getLog(BeansDtdResolver.class);
public BeansDtdResolver() {
}
@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 + "]");
}
// systemId = http://www.springframework.org/dtd/spring-beans-2.0.dtd
if(systemId != null && systemId.endsWith(".dtd")) {
// ASCII码表,47 对应字符 /
// 获取最后一个斜杠位置,判断尾部有没有spring-beans
int lastPathSeparator = systemId.lastIndexOf(47);
int dtdNameStart = systemId.indexOf("spring-beans", lastPathSeparator);
if(dtdNameStart != -1) {
String dtdFile = "spring-beans.dtd";
if(logger.isTraceEnabled()) {
logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
}
try {
// 在此类(BeansDtdResolver)同目录下获取 spring-beans.dtd
Resource resource = new ClassPathResource(dtdFile, this.getClass());
// 包装后返回
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if(logger.isTraceEnabled()) {
logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
}
return source;
} catch (FileNotFoundException var8) {
if(logger.isDebugEnabled()) {
logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", var8);
}
}
}
}
return null;
}
public String toString() {
return "EntityResolver for spring-beans DTD";
}
}
XSD 方式
PluggableSchemaResolver源码,默认到 META-INF/spring.schemas 文件中找 systemId对应的 XSD文件加载。
源码如下
package org.springframework.beans.factory.xml;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
public class PluggableSchemaResolver implements EntityResolver {
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
private static final Log logger = LogFactory.getLog(PluggableSchemaResolver.class);
@Nullable
private final ClassLoader classLoader;
private final String schemaMappingsLocation;
@Nullable
private volatile Map<String, String> schemaMappings;
public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
this.schemaMappingsLocation = "META-INF/spring.schemas";
}
public PluggableSchemaResolver(@Nullable ClassLoader classLoader, String schemaMappingsLocation) {
Assert.hasText(schemaMappingsLocation, "'schemaMappingsLocation' must not be empty");
this.classLoader = classLoader;
this.schemaMappingsLocation = schemaMappingsLocation;
}
@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) {
String resourceLocation = (String)this.getSchemaMappings().get(systemId);
if(resourceLocation == null && systemId.startsWith("https:")) {
resourceLocation = (String)this.getSchemaMappings().get("http:" + systemId.substring(6));
}
if(resourceLocation != null) {
ClassPathResource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if(logger.isTraceEnabled()) {
logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
} catch (FileNotFoundException var6) {
if(logger.isDebugEnabled()) {
logger.debug("Could not find XML schema [" + systemId + "]: " + resource, var6);
}
}
}
}
return null;
}
private Map<String, String> getSchemaMappings() {
Map<String, String> schemaMappings = this.schemaMappings;
if(schemaMappings == null) {
synchronized(this) {
schemaMappings = this.schemaMappings;
if(schemaMappings == null) {
if(logger.isTraceEnabled()) {
logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
}
try {
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
if(logger.isTraceEnabled()) {
logger.trace("Loaded schema mappings: " + mappings);
}
schemaMappings = new ConcurrentHashMap(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, (Map)schemaMappings);
this.schemaMappings = (Map)schemaMappings;
} catch (IOException var5) {
throw new IllegalStateException("Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", var5);
}
}
}
}
return (Map)schemaMappings;
}
public String toString() {
return "EntityResolver using schema mappings " + this.getSchemaMappings();
}
}