引子
今天整理这方面的内容是因为最近在看《Spring 源码深度解析》这本书,书中一开始就讲到了如何解析Spring配置文件,加载Bean的过程,所以今天就来回顾下,java是如何处理XML文件的。
XML文档结构
- XML文档
文档头有下面两种形式:
<?xml version="1.0"?>
<?xml version="1.0" encoding="UTF-8"?>
- 文档类型定义DTD
文档类型定义是确保文档正确的一个重要机制,但它并不是必须的。 - 元素
元素由根元素和子元素组成,元素内部有文本和属性,如:
<size unit="pt">36</size>
unit就是属性,36就是文本。
- CDATA部分
用<![CDATA[...]]>来包括一些特殊字符串,不让它们被解释为标记,如:
<!CDATA[you & me]>
在XML文件中来表示字符串"you & me"。
- 处理指令
XML文档中要执行的指令用<?...?>来包括指令,如:
<?xml version="1.0">
XML文档解析
java库提供了两种XML解析器:
- 像DOM解析器这样的树型解析器:将XML文档转换为树结构
- 像SAX解析器这样的流机制解析器
下面是使用DOM解析器来解析XML文件的例子:
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
File file = null;
Document document = builder.parse(file);
// 获取根元素
Element root= document.getDocumentElement();
// 获取子元素
NodeList children = root.getChildNodes();
for(int i=0;i<children.getLength();i++){
Node node = children.item(i);
if(node instanceof Element){
Element element = (Element) node; // 这才是子元素
} else if(node instanceof Attr){
Attr attr = (Attr) node;
} else if(node instanceof Text) {
Text text = (Text) node;
}
// ...
}
} catch (Exception e){
System.out.println(e);
}
通过DocumentBuilder对象将XML文件解析为Document对象。这里面要注意的是获取子元素的时候,getChildNodes方法返回的是元素下的所有Node接口实例,除了元素,还有属性,文本,它们都是Node接口的实现类。在IDEA中使用Ctrl+H快捷键可得:
继承关系
回到例子中,当节点类型为Text时,要想获取文本的内容,可以使用: text.getData();方法,另外如果想遍历某个元素的所有属性,可以使用如下方法:
NamedNodeMap attributes = element.getAttributes();
for (int j=0;j<attributes.getLength();j++){
Node attribute = attribute.item(i);
String name = attribute.getNodeName();
String value = attribute.getNodeValue();
}
如果想获取指定属性的值,则可以使用如下:
String unit = element.getAttribute("unit");
验证XML
如果在特定场景下,我们想控制XML文档中某个元素下只能有A属性,这就要求我们在解析XML的时候,自行校验,并且如果添加了其他属性,那还得同时去修改校验的代码,这无疑不利于代码的维护,灵活性也低。为此引入下面要讲的内容:
如果要指定XML文档结构,比如提供一个文档类型定义(DTD)或一个XML Schema定义(XSD),它们包含了用于解释文档应如何构成的规则。
XSD和DTD相比,由于XSD是用XML语言实现的,可以表达更复杂的验证条件,而DTD的规则配置是自身的语法,所以XSD是设计用来替代DTD的。spring boot中pom.xml文件就是使用XSD开校验的,根节点配置如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
添加XML验证的优点就在于比如你想控制某个元素只有两个子元素name,size,那么就不需要再使用之前遍历DOM树那套繁琐的代码了,而是直接使用下面这种方式就可以直接获取了:
Element nameElement = (Element) children.item(0);
Element sizeElement = (Element) children.item(1);
使用XPATH来定位信息
之前使用遍历的DOM树的方法来获取某一层的元素的内容,比如下面XML文件中的version的值:
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/>
</parent>
</project>
这种方法是比较繁琐的,如果使用XPath语言,就可以用表达式/project/parent/version来直接定位到version元素上,操作要简单的多。
另外,还可以使用"/project/parent"方式来表示parent元素下的所有子元素。
在java api中提供了XPATH的方法调用:
// 获取元素的文本
String version = xPath.evaluate("/project/parent/version",document);
// 获取节点
Node node = (Node)xPath.evaluate("/project/parent/version",document, XPathConstants.NODE);
// 获取一组节点
NodeList nodeList = (NodeList)xPath.evaluate("/project/parent",document, XPathConstants.NODESET);
使用命名空间
java语言使用包来避免相同的类名冲突,只要在不同包中即可。XML中也有类似的命名空间namespace机制,命名空间用URI来表示。回到POM.xml文件中:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/>
</parent>
</project>
其中xmlns:prefix="..." 的形式用于定义命名空间和前缀,此处前缀是xsi,这样xsi:schemaLocation指的是命名空间http://maven.apache.org/POM/4.0.0中的xsi。
使用SAX解析器
DOM解析器将XML文档解析为树形的数据结构,当文档很大,处理算法比较简单的时候,可以在运行时解析节点,而不需要看到完整的树形结构,此时为提高效率,可以使用流机制解析器:SAX解析器。
SAX解析器使用的是事件回调,在XML解析过程中,会产生各种事件,我们需要一个处理器为不同的事件定义事件动作。所以相比于DOM树解析器,使用SAX解析器的优点在于不需要关心要找的元素出现的上下文环境,而且也不必存储树形结构。ContentHandle接口定义了这些回调方法:
ContentHandler接口
- startElement在遇到起始标签时调用
- characters在遇到字符数据时调用
- startDocument在文档开始时调用
在使用时覆盖接口这些方法就能添加自己的控制。
下面是一个使用SAX解析器解析XML文档的示例:
try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParser parser = saxParserFactory.newSAXParser();
File file = null;
DefaultHandler defaultHandler = new DefaultHandler(){
public void startElement (String uri, String localName,String qName, Attributes attributes){
// 处理逻辑
}
};
parser.parse(file,defaultHandler);
} catch (Exception e){
}
SAX解析器在执行parse方法进行解析的时候,传入的DefaultHandler对象就是之前提到的处理器:ContentHandle接口的实现类。
网友评论