这里主要是使用org.springframework:spring-beans:5.2.0.RELEASE进行分析
一. DefaultDocumentLoader
-
XmlBeanFactoryReader类把文档读取委托给DocumentLoader执行
-
DefaultDocumentLoader是DocumentLoader的实现类
loadDocument() 重写接口的方法源码如下
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//同样首先创建DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//再通过DocumentBuilderFactory创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析inputSource来返回Document对象
return builder.parse(inputSource);
}
- 下面分析一下上面的参数(EntityResolver entityResolver)
二. EntityResolver
参数entityResolver是getEntityResolver() 函数获取的返回值
getEntityResolver()方法 源码如下
public EntityResolver getEntityResolver() {
EntityResolver entityResolver = null;
try {
XMLEntityResolver xmlEntityResolver =
(XMLEntityResolver)fConfiguration.getProperty(ENTITY_RESOLVER);
if (xmlEntityResolver != null) {
if (xmlEntityResolver instanceof EntityResolverWrapper) {
entityResolver =
((EntityResolverWrapper) xmlEntityResolver).getEntityResolver();
}
else if (xmlEntityResolver instanceof EntityResolver2Wrapper) {
entityResolver =
((EntityResolver2Wrapper) xmlEntityResolver).getEntityResolver();
}
}
}
catch (XMLConfigurationException e) {
// do nothing
}
return entityResolver;
}
- 在loadDocument方法中涉及一个参数EntityResolver,官网这样解释:如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。
- 也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。
- 默认的寻找规则,即通过网络(实现上就是声明的DTD的URL地址)来下载相应的DTD声明,并进行认证。下载的过程漫长,而且当网络中断或不可用的时候,这里会报错,就是因为相应的DTD声明没有被找到的原因。
- enntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程
EntityResolver接口 源码如下
public interface EntityResolver {
//两个参数publicId和systemId
//返回一个inputSource对象
public abstract InputSource resolveEntity (String publicId,
String systemId)
throws SAXException, IOException;
}
3. 自定义beans.xml验证模式为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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java对象 , 由Spring创建和管理-->
<bean id="hello" class="com.xizi.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>
对于两个参数
publicId:null
systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
- 自定义beans.dtd验证模式为DTD配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN"
"http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
</beans>
对于两个参数
publicId:-//Spring//DTD BEAN 2.0//EN
systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
三. 加载DTD文件
-
将这个URL转换为自己工程里对应的地址文件,以加载DTD文件为例来看看Spring中是如何实现的
-
之前Spring中通过getEntityResolver()方法对EntityResolver的获取
-
spring中使用DelegatingEntityResolver类为EntityResolver的实现类
resolveEntity() 源码如下
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
// 如果是dtd从这里解析
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
// 通过调用META-INF/Spring.schemas解析
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
// Fall back to the parser's default behavior.
return null;
}
- dtdResolver的BeanresolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找
- 加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载
BeansDtdResolver中的resolveEntity() 源码如下
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);
@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.endsWith(DTD_EXTENSION)) {
int lastPathSeparator = systemId.lastIndexOf('/');
int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
if (dtdNameStart != -1) {
String dtdFile = DTD_NAME + DTD_EXTENSION;
if (logger.isTraceEnabled()) {
logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
}
try {
Resource resource = new ClassPathResource(dtdFile, 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 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;
}
@Override
public String toString() {
return "EntityResolver for spring-beans DTD";
}
}
PluggableSchemaResolver中的resolveEntity() 源码如下
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 = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
}
public PluggableSchemaResolver(@Nullable ClassLoader classLoader, String schemaMappingsLocation) {
Assert.hasText(schemaMappingsLocation, "'schemaMappingsLocation' must not be empty");
this.classLoader = classLoader;
this.schemaMappingsLocation = schemaMappingsLocation;
}
@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) {
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation == null && systemId.startsWith("https:")) {
// Retrieve canonical http schema mapping even for https declaration
resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
}
if (resourceLocation != null) {
Resource 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 ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}