spring源码日记04: XML中获取DOM树
2020-02-10 本文已影响0人
BugPool
所有文章已迁移至csdn,csdn个人主页https://blog.csdn.net/chaitoudaren
上节讲到 xml文件 -> Resource -> InputStram,并且提到spring的一个有趣的规则,即以do开头的均为真正的核心逻辑。
本节继续跟踪XmlBeanDefinitionReader.java中的doLoadBeanDefinitions。即流程图的第3点,inputStream -> DOM树
spring解析阶段.jpg
inputStream -> DOM
- Xml文件读取示例
在读源码之前,先复习一下XML文件的读取
解析xml文档一般有两种技术:
1.dom:Document Object Model(spring使用,因此我们只关心这种)
2.sax:Simple API for XML
参考:jaxp解析器用dom方式操作xml文档总结
代码的核心就只有加了注释的那3句,而且代码很固定,因此spring也是这么用的。当我们阅读源码到这段代码时便豁然开朗,原来spring源码也是人写出来的,和我写的好像也没什么区别
@Test
public void XmlReader() throws IOException, ParserConfigurationException, SAXException {
InputStream inputStream = new ClassPathResource("spring-config.xml").getInputStream();
/**
* 获取DocumentBuilderFactory
* 通过DocumentBuilderFactory获取DocumentBuilder
* 解析inputStream
*/
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.parse(inputStream);
// 获取element并循环输出
Element documentElement = doc.getDocumentElement();
NodeList nodes = documentElement.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attributes = node.getAttributes();
if (null != attributes) {
System.out.println(attributes.getNamedItem("id"));
System.out.println(attributes.getNamedItem("class"));
}
}
}
// 输出
id="myBean"
class="jianshu1.MyBean"
- inputStream转化为dom树
// XmlBeanDefinitionReader.java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 将文件流转化为DOM树
Document doc = doLoadDocument(inputSource, resource);
// DOM树转化为BeanDefinition,并注册到对应map中
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch () {
throw ...;
}
}
又看到了熟悉的命名方式doLoadDocument,继续跟踪
// XmlBeanDefinitionReader.java
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
/**
* getValidationModeForResource:获取验证模式DTD或者XSD
* getEntityResolver:获取检验文件DTD的方式,一般是从网络下载,但是通过EntityResolver由程序来定义寻找过程,
* 比如我们将DTD放在工程的某个文件夹下,这样就避免了通过网络的问题
*/
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
这段代码跟我们想要看到的不大一样,真正的核心代码在更里面一层,该层目的是准备好生成DocumentBuilder所需要的配置。例如:验证模式说明当前xml属于DTD或者XSD,EntityResolver等。这些参数有兴趣的自行查阅dom读取xml的API,我们这里只挑获取校验模式进行讲解。
- 获取校验模式
// XmlBeanDefinitionReader.java
protected int getValidationModeForResource(Resource resource) {
// 如果用户手动配置了校验模式,则直接使用用户自定义校验模式
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 否则通过detectValidationMode自动检测获取
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
// 还是没有则默认使用XSD模式
return VALIDATION_XSD;
}
下面的代码虽然多,但是都是在做校验,最关键的核心代码就最后一个函数hasDoctype
spring在做校验模式获取比我想象中要粗糙一些,只要文件包含"DOCTYPE"就是DTD否则就是XSD,也许这么判断已经做够了
// XmlBeanDefinitionReader.java
protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}
InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}
try {
return this.validationModeDetector.detectValidationMode(inputStream);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
// 判断是否属于DTD模式
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
// 关键代码就这一行,只要文件当中包含"DOCTYPE"就是DTD,否则就是XSD
private boolean hasDoctype(String content) {
// 判断是否包含DOCTYPE
return content.contains(DOCTYPE);
}
- loadDocumen核心代码
继续往里跳转,兜兜转转这么久,这一段就是inputSteram -> DOM树的真容,除了新加了一些配置参数以外,是不是跟我们写的一毛一样?
// XmlBeanDefinitionReader.java
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 获取DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 通过DocumentBuilderFactory获取DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 解析inputStream
return builder.parse(inputSource);
}