通俗理解spring源码(四)—— 获取Docment
2020-04-21 16:08:56来源:博客园 阅读 ()
通俗理解spring源码(四)—— 获取Docment
通俗理解spring源码(四)—— 获取Docment
上节讲到了xmlBeanDefinitionReader.doLoadDocument(InputSource inputSource, Resource resource)方法:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
getValidationModeForResource(resource)在这里,看看getEntityResolver():
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; }
这里有两种类型的entityResolver,对于xmlBeanDefinitionReader来说,会new一个ResourceEntityResolver,后面会解释什么是EntityResolver。
然后将EntityResolver返回作为loadDocument()的参数。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
调用DefaultDocumentLoader的loadDocument方法
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isTraceEnabled()) { logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); } protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory, @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler) throws ParserConfigurationException { DocumentBuilder docBuilder = factory.newDocumentBuilder(); if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); } if (errorHandler != null) { docBuilder.setErrorHandler(errorHandler); } return docBuilder; }
在loadDocument方法中,使用的DocumentBuilderFactory 、DocumentBuilder 等都是jdk中的类,加载xml资源也是调用的java中的方法,这里就不多说了,重点是docBuilder.setEntityResolver(entityResolver)方法,该方法将得到的entityResolver放到docBuilder中,也就是DocumentBuilder ,然后调用docBuilder.parse()。
那么EntityResolver在生成document过程中起到什么作用呢?
1、EntityResolver
EntityResolver不是属于spring定义的,是jdk中org.xml.sax下的一个接口。
官方是这样解释EntityResolver的:如果SAX应用程序实现自定义处理外部实体,则必须实现此接口,并使用setEntityResolver方法向SAX 驱动器注册一个实例。
也就是说,对于解析一个xml,sax首先会读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档的进行验证,默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,就会找不到相应的dtd,就会报错。
EntityResolver 的作用就是项目本身就可以提供一个如何寻找DTD 的声明方法,即:由程序来实现寻找DTD声明的过程,比如我们将DTD放在项目的某处在实现时直接将此文档读取并返回个SAX即可,这样就避免了通过网络来寻找DTD的声明。
首先看看EntityResolver 接口声明的方法:
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
接收2个参数,publicId ,systemId ,并返回一个InputStream对象
如果我们在解析验证模式为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-2.5.xsd"> ... </beans>
读取得到以下参数
publicId : null
systemId : http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
如果我们解析的是DTD的配置文件,代码如下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> ... </beans>
上面说过,EntityResolver 会被set到DocumentBuilder 对象中,也就是说,在 DocumentBuilder .parse()过程中,EntityResolver 对象的resolveEntity会得到相应的参数publicId 和systemId ,然后调用其resolveEntity ()方法,返回的流InputSource 就是相应的DTD文件流或者XSD文件流,然后完成校验工作。
所以,现在重点就是,不同的EntityResolver实现是以何种方式返回DTD或XSD文件流的。来看具体实现。
2、DelegatingEntityResolver
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { return this.schemaResolver.resolveEntity(publicId, systemId); } } // Fall back to the parser's default behavior. return null; }
根据systemId后缀,委派给dtdResolver和schemaResolver完成,dtdResolver和schemaResolver也是EntityResolver的实现,在 DelegatingEntityResolver构造中初始化。
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) { this.dtdResolver = new BeansDtdResolver(); this.schemaResolver = new PluggableSchemaResolver(classLoader); }
3、BeansDtdResolver
BeansDtdResolver负责找到classpath下spring-beans.dtd文件的位置,返回文件流。
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; //dtdFile="spring-beans.dtd" if (logger.isTraceEnabled()) { logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath"); } try { //定位classpath资源文件 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; }
spring-beans.dtd文件在如下位置:
4、PluggableSchemaResolver
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas"; private volatile Map<String, String> schemaMappings; public PluggableSchemaResolver(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION; }
PluggableSchemaResolver是重点,负责找到相对应的XSD文件,一般我们都是使用XSD作为检验文件。
DEFAULT_SCHEMA_MAPPINGS_LOCATION ,指明资源文件路径。
可以看到,spring很贴心的将所有版本的xsd文件都准备了,即使没有指定版本号,也会有默认的xsd文件。
schemaMappings负责保存资源路径与校验文件url的映射。即systemId为key,resourceLocation为value。
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,也就是验证文件的url,找到本地文件路径 String resourceLocation = getSchemaMappings().get(systemId); //如果没有找到,并且url是https开头,就转换为http开头的url 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); } } } } // Fall back to the parser's default behavior. return null; }
其中getSchemaMappings获取或初始化map容器,这里用到了懒加载
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 { //这里负责初始化map 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, schemaMappings); this.schemaMappings = schemaMappings; } catch (IOException ex) { throw new IllegalStateException( "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex); } } } } return schemaMappings; }
通过调试,可以看到map中的中的内容
4、ResourceEntityResolver
在xmlBeanDefinitionReader中,默认会用这种EntityResolver。
public class ResourceEntityResolver extends DelegatingEntityResolver { public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws SAXException, IOException { InputSource source = super.resolveEntity(publicId, systemId); //调用父类DelegatingEntityResolver的方法,如果没有,就从网络获取 if (source == null && systemId != null) { String resourcePath = null; try { String decodedSystemId = URLDecoder.decode(systemId, "UTF-8"); 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 + "]"); } Resource resource = this.resourceLoader.getResource(resourcePath); 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 String url = systemId; if (url.startsWith("http:")) { url = "https:" + url.substring(5); } try { 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; } }
该类首先会调用父类DelegatingEntityResolver的方法获取资源文件,如果没有就会从网络获取。不过一般都能从本地获取到。
走的太远,不要忘记为什么出发!
再来看看这个方法,是不是清楚多了?
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
至此,document对象获取完成。
参考:spring源码深度解析。
原文链接:https://www.cnblogs.com/xiaohang123/p/12722065.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- Spring系列.ApplicationContext接口 2020-06-11
- springboot2配置JavaMelody与springMVC配置JavaMelody 2020-06-11
- 给你一份超详细 Spring Boot 知识清单 2020-06-11
- SpringBoot 2.3 整合最新版 ShardingJdbc + Druid + MyBatis 2020-06-11
- 掌握SpringBoot-2.3的容器探针:实战篇 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash