XML全稱爲Extensible MarkupLanguage,意思是可擴展的標記語言。XML語法上和HTML比較相似,但HTML中的元素是固定的,而XML的標籤是可以由用戶自定義的。
W3C在1998年2月發佈1.0版本,2004年2月又發佈1.1版本,但因爲1.1版本不能向下兼容1.0版本,所以1.1沒有人用。同時,在2004年2月W3C又發佈了1.0版本的第三版。我們要學習的還是1.0版本!!!
配置文件
<?xml version="1.0"encoding="UTF-8"?>
<web-app version="2.5">
<servlet>
<servlet-name>HelloMyServlet</servlet-name>
<servlet-class>cn.itcast.HelloMyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloMyServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
存放數據
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<personid="p001">
<name>張三</name>
</person>
<personid="p002">
<name>李四</name>
</person>
</persons>
XML文檔聲明
<?xml version="1.0"encoding="UTF-8"?>
1. 文檔聲明必須爲<?xml開頭,以?>結束;
2. 文檔聲明必須從文檔的0行0列位置開始;
3. 文檔聲明只有三個屬性:
versioin:指定XML文檔版本。必須屬性,因爲我們不會選擇1.1,只會選擇1.0;
encoding:指定當前文檔的編碼。可選屬性,默認值是utf-8;
standalone:指定文檔獨立性。可選屬性,默認值爲yes,表示當前文檔是獨立文檔。如果爲no表示當前文檔不是獨立的文檔,會依賴外部文件。
元素
<servlet>
1. 元素是XML文檔中最重要的組成部分,
2. 普通元素的結構開始標籤、元素體、結束標籤組成。例如:<hello>大家好</hello>
3. 元素體:元素體可以是元素,也可以是文本,例如:<b><a>你好</a></b>
4. 空元素:空元素只有開始標籤,而沒有結束標籤,但元素必須自己閉合,例如:<c/>
5. 元素命名:
區分大小寫
不能使用空格,不能使用冒號:
不建議以XML、xml、Xml開頭
6. 良好的XML文檔,必須有一個根元素。
屬性
<web-app version="2.5">
1. 屬性是元素的一部分,它必須出現在元素的開始標籤中
2. 屬性的定義格式:屬性名=屬性值,其中屬性值必須使用單引或雙引
3. 一個元素可以有0~N個屬性,但一個元素中不能出現同名屬性
4. 屬性名不能使用空格、冒號等特殊字符,且必須以字母開頭
註釋
XML的註釋與HTML相同,即以「<!--」開始,以「-->」結束。註釋內容會被XML解析器忽略!
轉義字符
XML中的轉義字符與HTML一樣。
因爲很多符號已經被XML文檔結構所使用,所以在元素體或屬性值中想使用這些符號就必須使用轉義字符,例如:「<」、「>」、「’」、「」」、「&」。
CDATA區
<![CDATA[
任意內容
]]>
當大量的轉義字符出現在xml文檔中時,會使xml文檔的可讀性大幅度降低。這時如果使用CDATA段就會好一些。
在CDATA段中出現的「<」、「>」、「」」、「’」、「&」,都無需使用轉義字符。這可以提高xml文檔的可讀性。
在CDATA段中不能包含「]]>」,即CDATA段的結束定界符。
DTD(Document TypeDefinition),文檔類型定義,用來約束XML文檔。規定XML文檔中元素的名稱,子元素的名稱及順序,元素的屬性等。
開發中,我們很少自己編寫DTD約束文檔,通常情況我們都是通過框架提供的DTD約束文檔,編寫對應的XML文檔。常見框架使用DTD約束有:struts2、hibernate等。
通過提供的DTD「web-app_2_3.dtd」編寫XML
<?xml version="1.0"encoding="UTF-8"?>
<!--
傳智播客DTD教學實例文檔。
模擬servlet2.3規範,如果開發人員需要在xml使用當前DTD約束,必須包括DOCTYPE。
格式如下:
<!DOCTYPEweb-app SYSTEM "web-app_2_3.dtd">
-->
<!ELEMENT web-app (servlet*,servlet-mapping* ,welcome-file-list?) >
<!ELEMENT servlet(servlet-name,description?,(servlet-class|jsp-file))>
<!ELEMENT servlet-mapping(servlet-name,url-pattern) >
<!ELEMENT servlet-name (#PCDATA)>
<!ELEMENT servlet-class (#PCDATA)>
<!ELEMENT url-pattern (#PCDATA)>
<!ELEMENT welcome-file-list (welcome-file+)>
<!ELEMENT welcome-file (#PCDATA)>
<!ATTLIST web-app version CDATA #IMPLIED>
DTD語法
1. 內部DTD,在XML文檔內部嵌入DTD,只對當前XML有效。
<?xml version="1.0"encoding="utf-8" standalone="yes" ?>
<!DOCTYPE web-app [
... //具體的語法
]>
<web-app>
</web-app>
2. 外部DTD—本地DTD,DTD文檔在本地系統上,公司內部自己項目使用。
<?xml version="1.0"encoding="utf-8" standalone="no" ?>
<!DOCTYPE web-app SYSTEM"web-app_2_3.dtd">
<web-app>
</web-app>
3. 外部DTD—公共DTD,DTD文檔在網絡上,一般都有框架提供。
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems,Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
</web-app>
定義元素語法:<!ELEMENT元素名 元素描述>
元素名:自定義
元素描述包括:符號和數據類型
常見符號:? * + () | ,
常見類型:#PCDATA 表示內容是文檔,不能是子標籤
實例
<!ELEMENT web-app (servlet*,servlet-mapping* ,welcome-file-list?) >
web-app 包括3個標籤,且必須順序出現。
servlet子標籤個數任意
servlet-mapping 子標籤個數任意
welcome-file-list 子標籤最多隻能出現一次
<!ELEMENT servlet(servlet-name,description?,(servlet-class|jsp-file))>
servlet 有3個子標籤,且必須順序出現
servlet-name,必須有,且只能出現一次
description,可選一次
servlet-class 和 jsp-file 二選一,且只能出現一次
<!ELEMENT servlet-name (#PCDATA)>
servlet-name的標籤體必須是文本
<!ELEMENT welcome-file-list (welcome-file+)>
welcome-file-list至少有一個子標籤welcome-file
屬性的語法:
<!ATTLIST元素名
屬性名屬性類型 約束
屬性名屬性類型 約束
...
>
元素名:屬性必須是給元素添加,所有必須先確定元素名
屬性名:自定義
屬性類型:ID、CDATA、枚舉 …
ID : ID類型的屬性用來標識元素的唯一性
CDATA:文本類型
枚舉:(e1 | e2 | ...) 多選一
約束:
#REQUIRED:說明屬性是必須的;
#IMPLIED:說明屬性是可選的;
實例
<!ATTLIST web-app version CDATA #IMPLIED>
給web-app元素添加 version屬性,屬性值必須是文本,且可選。
<web-appversion="2.3"> 和 <web-app>都符號約束
Schema是新的XML文檔約束;
Schema要比DTD強大很多,是DTD 替代者;
Schema本身也是XML文檔,但Schema文檔的擴展名爲xsd,而不是xml。
Schema 功能更強大,數據類型更完善
Schema 支持名稱空間
與DTD一樣,要求可以通過schema約束文檔編寫xml文檔。常見框架使用schema的有:Spring等
要求:可以通過提供「web-app_2_5.xsd」編寫xml文檔
<?xml version="1.0"encoding="UTF-8"?>
<!--
傳智播客Schema教學實例文檔。
模擬servlet2.5規範,如果開發人員需要在xml使用當前Schema約束,必須包括指定命名空間。
格式如下:
<web-appxmlns="http://www.example.org/web-app_2_5"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.example.org/web-app_2_5web-app_2_5.xsd"
version="2.5">
-->
<xsd:schemaxmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/web-app_2_5"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.example.org/web-app_2_5"
elementFormDefault="qualified">
<xsd:elementname="web-app">
<xsd:complexType>
<xsd:choiceminOccurs="0" maxOccurs="unbounded">
<xsd:element name="servlet">
<xsd:complexType>
<xsd:sequence>
<xsd:elementname="servlet-name"></xsd:element>
<xsd:elementname="servlet-class"></xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="servlet-mapping">
<xsd:complexType>
<xsd:sequence>
<xsd:elementname="servlet-name"></xsd:element>
<xsd:elementname="url-pattern"></xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:elementname="welcome-file-list">
<xsd:complexType>
<xsd:sequence>
<xsd:elementname="welcome-file"maxOccurs="unbounded"></xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:attributename="version" type="double"use="optional"></xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
如果一個XML文檔中使用多個Schema文件,而這些Schema文件中定義了相同名稱的元素時就會出現名字衝突。這就像一個Java文件中使用了importjava.util.*和import java.sql.*時,在使用Date類時,那麼就不明確Date是哪個包下的Date了。
總之名稱空間就是用來處理元素和屬性的名稱衝突問題,與Java中的包是同一用途。如果每個元素和屬性都有自己的名稱空間,那麼就不會出現名字衝突問題,就像是每個類都有自己所在的包一樣,那麼類名就不會出現衝突。
當W3C提出Schema約束規範時,就提供「官方約束文檔」。我們通過官方文檔,必須「自定義schema 約束文檔」,開發中「自定義文檔」由框架編寫者提供。我們提供「自定義文檔」限定,編寫出自己的xml文檔。
聲明命名空間
默認命名空間:<xxxxmlns=」」> ,使用<標籤>
顯式命名空間:<xxxxmlns:別名=」」> , 使用<別名:標籤>
實例:web-app_2_5.xsd
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"…>
表示自定義schema約束文檔引用官方文檔作爲顯示命名空間。如果要使用官方提供的元素或屬性,必須使用xsd前綴(自定義,此處表示官方文檔,所以使用xsd)
<xsd:schema>標籤就有官方文檔提供,默認命名空間直接使用。
實例:web.xml
<web-appxmlns=http://www.example.org/web-app_2_5 …>
表示 xml 文檔引用「自定義約束文檔」作爲默認命名空間
因爲使用默認命名空間,<web-app>直接使用
自定義約束:web-app_2_5.xsd
<xsd:schematargetNamespace=http://www.example.org/web-app_2_5
表示給當前自定義約束文檔進行起名,提供給xml文檔使用。
xml文檔:web.xml
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=http://www.example.org/web-app_2_5web-app_2_5.xsd
xmlns:xsi=」…」固定寫法
表示是一個schema實例文檔,就是被schema文檔約束的xml文檔。
xsi:schemaLocation=」名稱 路徑 名稱路徑 名稱 路徑…」
表示用於確定當前xml文檔使用到的schema文檔的位置。「名稱 路徑」是成對出現,與xmlns引用命名空間對應。
當將數據存儲在XML後,我們就希望通過程序獲得XML的內容。如果我們使用Java基礎所學習的IO知識是可以完成的,不過你需要非常繁瑣的操作纔可以完成,且開發中會遇到不同問題(只讀、讀寫)。人們爲不同問題提供不同的解析方式,並提交對應的解析器,方便開發人員操作XML。
l 開發中比較常見的解析方式有三種,如下:
1. DOM:要求解析器把整個XML文檔裝載到內存,並解析成一個Document對象。
優點:元素與元素之間保留結構關係,故可以進行增刪改查操作。
缺點:XML文檔過大,可能出現內存溢出顯現。
2. SAX:是一種速度更快,更有效的方法。它逐行掃描文檔,一邊掃描一邊解析。並以事件驅動的方式進行具體解析,每執行一行,都將觸發對應的事件。(瞭解)
優點:處理速度快,可以處理大文件
缺點:只能讀,逐行後將釋放資源。
3. PULL:Android內置的XML解析方式,類似SAX。(瞭解)
解析器:就是根據不同的解析方式提供的具體實現。有的解析器操作過於繁瑣,爲了方便開發人員,有提供易於操作的解析開發包。
常見的解析開發包:
JAXP:sun公司提供支持DOM和SAX開發包
JDom:dom4j兄弟
jsoup:一種處理HTML特定解析開發包
dom4j:比較常用的解析開發包,hibernate底層採用。
XML DOM 和 HTML DOM類似,XML DOM 將 整個XML文檔加載到內存,生成一個DOM樹,並獲得一個Document對象,通過Document對象就可以對DOM進行操作
DOM中的核心概念就是節點,在XML文檔中的元素、屬性、文本等,在DOM中都是節點!
如果需要使用dom4j,必須導入jar包。
dom4j 必須使用核心類SaxReader加載xml文檔獲得Document,通過Document對象獲得文檔的根元素,然後就可以操作了。
常用API如下:
1. SaxReader對象
a) read(…) 加載執行xml文檔
2. Document對象
getRootElement() 獲得根元素
3. Element對象
elements(…) 獲得指定名稱的所有子元素。可以不指定名稱
element(…) 獲得指定名稱第一個子元素。可以不指定名稱
getName() 獲得當前元素的元素名
attributeValue(…) 獲得指定屬性名的屬性值
elementText(…) 獲得指定名稱子元素的文本值
getText() 獲得當前元素的文本內容
@Test
public void demo03() throws Exception{
//#1 獲得document
SAXReadersaxReader = new SAXReader();
Documentdocument = saxReader.read(new File("src/cn/itcast/a_xml/web.xml"));
//#2 獲得根元素
ElementrootElement = document.getRootElement();
//打印version屬性值
Stringversion = rootElement.attributeValue("version");
System.out.println(version);
//#3 獲得所有子元素。例如:<servlet>/<servlet-mapping>
List<Element>allChildElement = rootElement.elements();
//#4 遍歷所有
for(Element childElement : allChildElement) {
// #5.1 打印元素名
String childEleName =childElement.getName();
System.out.println(childEleName);
// #5.2 處理<servlet> ,並獲得子標籤的內容。例如:<servlet-name> 等
if("servlet".equals(childEleName)){
// 方式1:獲得元素對象,然後獲得文本
ElementservletNameElement = childElement.element("servlet-name");
StringservletName = servletNameElement.getText();
System.out.println("\t"+ servletName);
// 方式2:獲得元素文本值
StringservletClass = childElement.elementText("servlet-class");
System.out.println("\t"+ servletClass);
}
// #5.3 處理<servlet-mapping> 省略...
}
}
爲了模擬服務器端程序,且可以同時存在多個類似程序。故提供接口,接口中有3個方法,我們人爲約定三個方法的調用順序。
public interface MyServlet {
publicvoid init(); //1.初始化
publicvoid service(); //2.執行
publicvoid destory(); //3.銷燬
}
在爲接口編寫實現類
public class HelloMyServlet implements MyServlet {
@Override
publicvoid init() {
System.out.println("1.初始化");
}
@Override
publicvoid service() {
System.out.println("2.執行中....");
}
@Override
publicvoid destory() {
System.out.println("3.銷燬");
}
}
測試,創建實現類實例對象
public class TestApp {
@Test
publicvoid demo01(){
//手動創建執行
MyServlet myServlet = new HelloMyServlet();
myServlet.init();
myServlet.service();
myServlet.destory();
}
}
測試程序我們直接new HelloServlet,這種編程方式我們稱爲硬編碼,及代碼寫死了。爲了後期程序的可擴展,開發中通常使用實現類的全限定類名(cn.itcast.e_web.HelloMyServlet),通過反射加載字符串指定的類,並通過反射創建實例。
@Test
public void demo02() throws Exception{
/* 反射創建執行
* 1) Class.forName 返回指定接口或類的Class對象
* 2) newInstance() 通過Class對象創建類的實例對象,相當於new Xxx();
*/
StringservletClass = "cn.itcast.e_web.HelloMyServlet";
//3 獲得字符串實現類實例
Classclazz = Class.forName(servletClass);
MyServletmyServlet = (MyServlet) clazz.newInstance();
//4 執行對象的方法
myServlet.init();
myServlet.service();
myServlet.destory();
}
使用反射我們已經可以創建對象的實例,此時我們使用的全限定類名,在程序是仍寫死了,我們將器配置到xml文檔中。
xml文檔內容:
<?xml version="1.0"encoding="UTF-8"?>
<web-app version="2.5">
<servlet>
<servlet-name>HelloMyServlet</servlet-name>
<servlet-class>cn.itcast.e_web.HelloMyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloMyServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HelloMyServlet2</servlet-name>
<servlet-class>cn.itcast.e_web.HelloMyServlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloMyServlet2</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
</web-app>
解析實現
@Test
public void demo03() throws Exception{
/* 讀取xml配置文件,獲得<servlet-class>配置的內容,取代固定字符串
*/
//1.1 加載xml配置文件,並獲得document對象
SAXReadersaxReader = new SAXReader();
Documentdocument = saxReader.read(new File("src/cn/itcast/e_web/web.xml"));
//1.2 獲得根元素
ElementrootElement = document.getRootElement();
//1.3 獲得第一個<servlet> 子元素
ElementservletElement = rootElement.element("servlet");
//1.4 獲得字符串實現類 <servlet-class>的值
StringservletClass = servletElement.elementText("servlet-class");
//3 獲得字符串實現類實例
Classclazz = Class.forName(servletClass);
MyServletmyServlet = (MyServlet) clazz.newInstance();
//4 執行對象的方法
myServlet.init();
myServlet.service();
myServlet.destory();
}
上面我們已經解析xml,不過我們獲得內容是固定。我們希望如果用戶訪問的路徑是/hello,將執行cn.itcast.e_web.HelloMyServlet程序,如果訪問時/hello2,將執行cn.itcast.e_web.HelloMyServlet2程序。
在執行測試程序前(@Before),解析xml文件,將解析的結果存放在Map中,map中數據的格式爲:路徑=實現類。
解析xml思路:先解析<servlet>,將結果存放map,name=class,然後再解析<servlet-mapping>通過name獲得class,再將url=class存放到map,最後將name=class移除。
//最終存放key=請求路徑,value=實現類
private Map<String, String> data = newHashMap<String,String>();
@Before
public void demo04Before() throws Exception{
//在執行前執行,解析xml,並將結果存放到Map<路徑,實現類>中
//1 獲得document
SAXReadersaxReader = new SAXReader();
Documentdocument = saxReader.read(new File("src/cn/itcast/e_web/web.xml"));
//2 獲得根元素
ElementrootElement = document.getRootElement();
//3 獲得所有的子元素 <servlet> 、<servlet-mapping>等
List<Element>allChildElement = rootElement.elements();
/* 4 遍歷所有
* 1)解析到<servlet>,將其子標籤<servlet-name>與<servlet-class>存放到Map中
* 2)解析到<servlet-mapping>,獲得子標籤<servlet-name>和<url-pattern>,從map中獲得1的內容,組合成 url = class 鍵值對
*/
for(Element childElement : allChildElement) {
//4.1 獲得元素名
String eleName = childElement.getName();
//4.2 如果是servlet,將解析內容存放到Map中
if("servlet".equals(eleName)){
StringservletName = childElement.elementText("servlet-name");
StringservletClass = childElement.elementText("servlet-class");
data.put(servletName,servletClass);
}
//4.3 如果是servlet-mapping,獲得之前內容,組成成key=url,value=class並添加到Map中
if("servlet-mapping".equals(eleName)){
StringservletName = childElement.elementText("servlet-name");
StringurlPattern = childElement.elementText("url-pattern");
// 獲得<servlet-name>之前存放在Map中<servlet-class>值
StringservletClass= data.get(servletName);
// 存放新的內容 url = class
data.put(urlPattern,servletClass);
// 將之前存放的數據刪除
data.remove(servletName);
}
//打印信息
System.out.println(data);
}
}
模擬瀏覽器請求路徑,通過url從map獲得class,並使用反射執行實現類。
@Test
public void demo04() throws Exception{
//1 模擬路徑
String url= "/hello";
// String url = "/hello2";
//2 通過路徑獲得對應的實現類
StringservletClass = data.get(url);
//3 獲得字符串實現類實例
Classclazz = Class.forName(servletClass);
MyServletmyServlet = (MyServlet) clazz.newInstance();
//4 執行對象的方法
myServlet.init();
myServlet.service();
myServlet.destory();
}
使用Socket編寫服務,通過瀏覽器可以訪問,並解析瀏覽器發送的請求數據,最終獲得請求路徑。
訪問路徑:http://localhost:8888/hello
@Test
public void demo05() throws Exception{
//使用socket獲得請求路徑
//1.1 給本地計算機綁定端口8888
ServerSocketserverSocket = new ServerSocket(8888);
//1.2 程序阻塞,等待瀏覽器請求。
Socketaccept = serverSocket.accept();
//1.3 獲得請求所有數據
BufferedReaderreader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
//1.4 獲得第一行數據,請求行,例如:GET /hello HTTP/1.1
StringfirstLine = reader.readLine();
//1.5 請求行三部分數據由空格連接,獲得中間數據。表示請求路徑
String url= firstLine.split(" ")[1];
System.out.println(url);
//2 通過路徑獲得對應的實現類
StringservletClass = data.get(url);
//3 獲得字符串實現類實例
Classclazz = Class.forName(servletClass);
MyServletmyServlet = (MyServlet) clazz.newInstance();
//4 執行對象的方法
myServlet.init();
myServlet.service();
myServlet.destory();
//5 釋放資源
reader.close();
client.close();
}
顯示效果
控制檯顯示效果
幾秒之後,服務程序沒有做出任何響應,瀏覽器將顯示「無法訪問」