为什么要学习xml文件的相关知识?
最近学习Java的过程中,遇到了很多xml后缀的配置文件,如web.xml, pom.xml, mybatis-config.xml等。绝大多数框架和软件使用的配置文件都是xml,这体现了xml应用的广泛性。这些框架既然使用了这些xml配置文件,那么难免需要解析它,对于我们开发者而言,学习如何解析xml文件有利于帮助我们理解框架的设计思想和使用。
本篇博客将要总结的内容
- 我会先总结一下xml文件的格式,了解它的大体结构以及如何使用
- 从xml文件格式来分析总结使用它有哪些好处,以及它的主要用途
- 如何利用Java语言去解析xml文件,解析的方法?方法的优劣?具体怎么使用
- 结合xml以及反射的技术来做一个示例程序
xml文件简介
xml的英文全称是extensible markup language, 它的大体格式在下方我分别展示了mybatis-config.xml和pom.xml中的部分内容。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
</envrionment>
</environments>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.person</groupId>
<artifactId>mybatis-crud002</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
第一行 <?xml version="1.0" encoding="UTF-8" ?>
声明了这是一个xml格式的文件,它的版本号是1.0,字符编码是UTF-8,这没啥好说的,基本是固定格式
第二行,我们发现两个文件内容是不同的,第一个xml文件中,它的内容是
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
它表明了当前的xml文件的根标签应该是configuration,这与html文件的声明根标签是html类似, 下面的网址指向了一个dtd文件,dtd的全称是Document Type Definition,它的作用是规定了当前xml文件可以使用哪些标签,应该遵守哪些规范。
为什么要有这个规范要求呢?对于一个框架而言,它必须得规定其配置文件的格式,这样在运行时才能正确的解析。就像人与人之间交流需要先确定统一的语言。由于xml的标签可以自定义(这个在后面的特点分析中总结),那么这种规范就是必须的。
了解html的读者就会发现xml与它的第一个不同之处了,w3c已经规定了html中有哪些标签,正确的结构是怎么样的,你只有按照要求编写,浏览器才能正确解析。
当然dta文件一般是框架开发者才需要编写的东西,对于我们应用层的开发者而言,遵守框架的配置规范即可。
那么第二个文件为什么是下面这样呢?
<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">
<modelVersion>4.0.0</modelVersion>
...
</project>
xsi:schemaLocation同样是一个url地址,它指向的是一个xsd文件,xsd文件是dtd文件的升级,但它们的做一个是相同的,都是xml文件的规范,唯一值得我们开发者注意的是,它的url地址直接作为一个属性写在了根结点上,不需要声明根结点了。
上面是maven和mybatis的配置文件,那么我们自己怎么编写xml文件呢?
<?xml version="1.0" encoding="UTF-8" ?>
<school>
<students>
<student id="2201">
<name>张三</name>
<age>22</age>
</student>
</students>
<teachers>
<teacher id="6603">
<name>罗翔</name>
<age>42</age>
</teacher>
</teachers>
</school>
我们自定义了一个根标签,其中嵌套了students和teachers标签,students和teachers分别还可以嵌套子标签, 标签还可以自己的属性(如id)。我们发现它的语法和html几乎一样,事实上它们都是标记语言,自然写法一样。
我们发现,school, teacher, student这些标签都是我们自定义的,想取什么名字都可以, 这种嵌套的结构层次结构分明,一目了然。同时,还可以发现,我们并没有写dtd或者xsd文件,因为该xml文件仅仅是我们个人用来存储数据,而没有给别人使用,自然不需要定义规范。
xml文件的特点和用途
上面简单的展示了一下xml怎么写,它的大概结构是怎么样的。下面我根据网上一些资料以及自己的理解来总结一下xml文件的特点以及用途。
- xml可以自定义标签以及标签的属性,需要注意的是它对大小写敏感
- xml是一个高度结构化的语言,层次清晰
- xml是一个纯文本,这样与平台无关,每一台机器上xml文件都是一致的
上面是我觉得最主要的三个特点,由此也衍生出xml文件的用途
存储和传输数据
在上面的例子中,我们事实上保存一个student和teacher类型的数据,student分别包括name和age字段,teacher同样也具有name和age字段。这意味着如果我们的应用需要存储什么信息就可以用一个xml文件保存,可读性很强。
由于它是一个纯文本,可以跨平台,自然就可以作为数据传输的载体。浏览器中有个对象叫做XMLHttpRequest,它对应的技术叫做AJAX,全称是Asynchronous JavaScript and XML,可以揣测,在JSON流行之前,XML是前后端通信的主要数据格式。
用作配置文件
这个作用也是我学习XML的初衷,绝大多数的Java框架都是用XML文件作为配置文件。这是由于XML文件本身的灵活性决定的,标签自由配置使得配置项丰富灵活,高度结构化为解析带来了一定的便利。
这里插入一个额外的讨论,就是JSON和XML谁做配置文件更好的问题。JSON的格式简介,在解析上的速度绝对是快于XML文件的,但我个人觉得XML文件的可读性更好(个人观点,有些人觉得json可读性更好),之所以有这种感受,是因为在python中我接触了到一些深度学习算法采用json作为配置文件,看起来眼花缭乱。当然JSON的优势还是大于XML的,毕竟是后面出现的技术。
解析XML文件
四种方式
- DOM方法解析
- SAX方式解析
- JDom方法
- dom4j方法
xml与html在结构和语法上相同,自然而然可以通过DOM来解析,这是一种与平台无关的规范。核心思想就是将文件看作一个文档对象,每一个结点看作一个对象,一个文档对象有一个根结点,结点之间的关系用树的数据结构来描述。解析时DOM会在内存中构建一棵DOM树,这样的坏处是容易超内存,解析速度慢。
SAX方法也是与平台无关的,速度较于DOM也更快,但是不能修改结点。
JDom和dom4j都是基于Java语言的,但是dom4j更为强大,是四个中效率最高的。
dom4j解析XML文件
使用dom4j需要相应的jar包支持,我是用maven管理的项目,直接引入依赖即可,如果不会maven,可以从网上找到dom4j的jar包资源,配置到系统的环境变量CLASSPATH下也可以使用。
package person.example;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import java.io.InputStream;
public class Dom4jTest {
@Test
public void testDom() throws Exception {
InputStream is = Dom4jTest.class.getClassLoader().getResourceAsStream("school.xml");
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(is);
System.out.println(document);
}
}
第一步, 获得一个输入流,这里为了代码具有更好的移植性,采用的是
InputStream is = Dom4jTest.class.getClassLoader().getResourceAsStream("school.xml");
这样从类的根路径下进行查找,而静态资源也是往往放到类的根路径下。
第二步,创建一个解析对象
SAXReader saxReader = new SAXReader();
第三步,将输入流传递到解析对象中,它会帮助我们解析流的内容,返回一个文档对象
Document document = saxReader.read(is);
通过document.getRootElement()我们可以获得当前文档的根结点, 返回的是Element类型,每一个结点都是Element类型
Element root = document.getRootElement()
List<Element> elements = root.elements();
elements.forEach(e -> System.out.println(e.getName()));
elements()用来返回当前结点下所有子结点, getName可以获得当前标签的名字
可是如果我们这样一层层的去找到需要的元素,在编码效率上未免太低了,所以dom4j提供了一种xpath的检索结点的方法
Node node = document.selectSingleNode("school/students/student")
这个方法通过传入一个路径的检索形式返回一个Node类型的结点,Node类型是Element类型的父类,有许多方法不能用,在使用时需要向下转型成Element
Element element = (Element) document.selectSingleNode("school/students/student");
注意要想转型成功,需要在pom.xml中引入jaxen这个依赖
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
System.out.println(element.getName());
System.out.println(element.attribute("id").getValue());
拿到结点以后可以获取其属性的值
xpath还有其它一些用法,如下
List<Element> students = root.selectNodes("students/student");
students.forEach(e -> {
System.out.println(e.getName());
});
selectNodes可以返回指定结点类型的列表
List<Element> students = root.selectNodes("//name");
students.forEach(e -> {
System.out.println(e.getData());
});
//name 前面的两个/ 表示忽略层级和位置,找到root结点下所有的name结点,通过getData的方法,可以拿到它包含的数据。
List<Element> students = root.selectNodes("//*[@id]");
students.forEach(e -> {
System.out.println(e.getName());
});
*表示通配符,可以匹配任意结点 【@属性值】,表示拥有id的属性的任意标签
List<Element> students = root.selectNodes("//*[@id='110']");
表示任意i包含id属性,且id="110"的标签
以上的匹配规则基本够用了,下面我们就开始利用这些知识来实践一下。
实践
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="carMapper.xml"/>
</mappers>
</configuration>
下面我们尝试解析一下mybatis的配置文件,来一个简单的任务,找到dataSource下面的property标签,拿到它们的value值,连接数据库,之前,需要引入数据库的驱动依赖。
public void testBatis() throws Exception{
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(is);
List<Element> property = document.selectNodes("//dataSource/property");
String url = "";
String username = "";
String driver = "";
String password = "";
for(Element p : property){
String name = p.attributeValue("name");
String value = p.attributeValue("value");
if(name.equals("url")){
url = value;
}else if(name.equals("username")){
username = value;
}else if(name.equals("driver")){
driver = value;
}else if(name.equals("password")){
password = value;
}
}
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println(connection);
}