<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Mongoose House

    Technical Edition

    統(tǒng)計(jì)

    留言簿(4)

    積分與排名

    閱讀排行榜

    在 Ajax 應(yīng)用程序中實(shí)現(xiàn)數(shù)據(jù)交換

    作者:Andrei Cioroianu
    了解如何利用 XML 和 JavaScript Object Notation 在 Ajax 客戶端和 Java 服務(wù)器之間傳輸數(shù)據(jù)。

    2006 年 6 月發(fā)表

    Ajax 核心 API(即所謂的 XMLHttpRequest)的唯一用途就是發(fā)送 HTTP 請(qǐng)求,在 Web 瀏覽器與服務(wù)器之間進(jìn)行數(shù)據(jù)交換。Web 頁(yè)面中運(yùn)行的 JavaScript 代碼,可以使用 XMLHttpRequest 將該請(qǐng)求參數(shù)提交至服務(wù)器端腳本,例如 Servlet 或 JSP 頁(yè)面。調(diào)用的 Servlet/JSP 將發(fā)回一個(gè)響應(yīng),其中包含了一般用于不需刷新整個(gè)頁(yè)面即可更新用戶查看內(nèi)容的數(shù)據(jù)。此種方法在性能和可用性方面均體現(xiàn)出了獨(dú)有的優(yōu)勢(shì),因?yàn)檫@將降低網(wǎng)絡(luò)通信量,而且 Web UI 的使用幾乎與桌面 GUI 一樣。

    但是,開發(fā)這種用戶界面并不簡(jiǎn)單,因?yàn)槟仨氃诳蛻舳松鲜褂?JavaScript、在服務(wù)器端上使用 Java(或等效語言)實(shí)施數(shù)據(jù)交換、驗(yàn)證以及處理。然而,在許多情況下,考慮到將會(huì)由此獲得的益處,付出額外精力構(gòu)建一個(gè)基于 Ajax 的界面是值得的。

    在本文中,我將介紹一種用于在 Ajax 客戶端和服務(wù)器之間傳輸數(shù)據(jù)的主要方法,并比較傳統(tǒng) Web 應(yīng)用程序模型與該 Ajax 模型的不同點(diǎn)。此外,文中還將探討在服務(wù)器端與客戶端處理數(shù)據(jù)的技巧。

    首先,您將了解如何在客戶端使用 JavaScript 編碼請(qǐng)求對(duì)象的參數(shù)。您可以使用所謂的 URL 編碼(Web 瀏覽器使用的默認(rèn)編碼),或可將請(qǐng)求參數(shù)包含在 XML 文檔中。服務(wù)器將處理該請(qǐng)求,并返回一個(gè)其數(shù)據(jù)也必須進(jìn)行編碼的響應(yīng)。本文將探討 JavaScript Object Notation (JSON) 和 XML,這些都是主要的響應(yīng)數(shù)據(jù)格式選項(xiàng)。

    本文的大部分內(nèi)容將主要介紹 Ajax 應(yīng)用程序中通常使用的與 XML 相關(guān)的 API。在客戶端,XML API 的作用雖非常有限,但已夠用。在多數(shù)情況下,利用 XMLHttpRequest 即可完成所有必需操作。此外,還可使用 JavaScript 在 Web 瀏覽器中分析 XML 文檔并串行化 DOM 樹。在服務(wù)器端,可用于處理 XML 文檔的 API 和框架有很多種。本文將介紹如何使用針對(duì) XML 的標(biāo)準(zhǔn) Java API 來實(shí)施基本任務(wù),該 API 支持 XML 模式、XPath、DOM 以及許多其他標(biāo)準(zhǔn)。

    通過本文,您可以了解到在 Ajax 應(yīng)用程序中實(shí)現(xiàn)數(shù)據(jù)交換所用的最佳技巧和最新的 API。其中涉及的示例代碼分別位于以下三個(gè)程序包中:util、model 和 feed。util 程序包中的類提供了用于 XML 分析、基于模式的驗(yàn)證、基于 XPath 的查詢、DOM 串行化以及 JSON 編碼的方法。model 程序包包含的示例數(shù)據(jù)模型可用于從 XML 文檔進(jìn)行初始化,然后再轉(zhuǎn)換至 JSON 格式。model 目錄中還有一個(gè) Schema 示例,可用于 XML 驗(yàn)證。feed 程序包中的類可用于模擬數(shù)據(jù)饋送,其通過 Ajax 每 5 秒檢索一次來獲得信息,以刷新 Web 頁(yè)面。本文闡釋了如何通過終止未完成的 Ajax 請(qǐng)求并在使用完 XMLHttpRequest 對(duì)象后將其刪除,避免 Web 瀏覽器的內(nèi)存泄漏。

    web 目錄中包含了 JSP 和 JavaScript 示例。ajaxUtil.js 中包含了發(fā)送 Ajax 請(qǐng)求、終止請(qǐng)求以及處理 HTTP 錯(cuò)誤的實(shí)用函數(shù)。該文件還提供了可用于 XML 和 URL 編碼、XML 分析以及 DOM 串行化的 JavaScript 實(shí)用程序。ajaxCtrl.jsp 文件充當(dāng) Ajax 控制器,接收每一個(gè) Ajax 請(qǐng)求、轉(zhuǎn)發(fā)參數(shù)至數(shù)據(jù)模型,或供給處理,然后返回 Ajax 響應(yīng)。其余的 Web 文件都是演示如何使用該實(shí)用方法的示例。

    在客戶端構(gòu)建請(qǐng)求

    將數(shù)據(jù)發(fā)送至 Web 服務(wù)器的最簡(jiǎn)單方法是將請(qǐng)求編碼為查詢字符串,該字符串根據(jù)使用的 HTTP 方法,既可附加至 URL,也可包含在請(qǐng)求正文中。如果需要發(fā)送復(fù)雜的數(shù)據(jù)結(jié)構(gòu),更好的解決方案是將信息編碼在 XML 文檔中。我將在本部分中介紹這兩種方法。

    編碼請(qǐng)求參數(shù)。開發(fā)傳統(tǒng) Web 應(yīng)用程序時(shí),無需擔(dān)心表單數(shù)據(jù)的編碼,因?yàn)?Web 瀏覽器會(huì)在用戶提交數(shù)據(jù)時(shí)自動(dòng)執(zhí)行該操作。但是,在 Ajax 應(yīng)用程序中,您必須親自編碼請(qǐng)求參數(shù)。JavaScript 提供了一個(gè)非常有用的函數(shù) escape(),該函數(shù)用 %HH(其中 HH 是十六進(jìn)制代碼)替換任何無法成為 URL 一部分的字符。例如,任何空白字符都用 %20 替換。

    示例代碼下載中提供了一個(gè)實(shí)用函數(shù) buildQueryString(),該函數(shù)可連接檢索自數(shù)組的參數(shù),通過 = 將每個(gè)參數(shù)的名稱和值相分離,并將 & 字符置于每個(gè)名稱-值對(duì)之間:

    1 function ?buildQueryString(params)? {
    2 ?? var ?query? = ? "" ;
    3 ?? for ?( var ?i? = ? 0 ;?i? < ?params.length;?i ++ )? {
    4 ????query? += ?(i? > ? 0 ? ? ? " & " ?:? "" )? + ?escape(params[i].name)? + ? " = " ? + ?escape(params[i].value);
    5 ??}

    6 ?? return ?query;
    7 }

    假設(shè)您要編碼以下參數(shù):

    1 var ?someParams? = ?[
    2 {?name: " name " ,??value: " John?Smith " ?} ,
    3 {?name: " email " ,?value: " john@company.com " ?} ,
    4 {?name: " phone " ,?value:? " (123)?456?7890 " ?}
    5 ];

    buildQueryString(someParams) 調(diào)用將生成包含以下內(nèi)容的結(jié)果:

    name=John%20Smith&email=john@company.com&phone=%28123%29%20456%207890

    如果希望使用 GET 方法,則必須將查詢附加至 URL 的 ? 字符之后。使用 POST 時(shí),應(yīng)通過 setRequestHeader() 將 Content-Type 標(biāo)題設(shè)置為 application/x-www-form-urlencoded,且必須將該查詢字符串傳遞至 XMLHttpRequest 的 send() 方法,這會(huì)將該 HTTP 請(qǐng)求發(fā)送至服務(wù)器。

    創(chuàng)建 XML 文檔。利用字符串通過其屬性和數(shù)據(jù)構(gòu)建元素是用 JavaScript 創(chuàng)建 XML 文檔最簡(jiǎn)單的方法。如果采用這種解決方案,則需要一個(gè)實(shí)用方法來轉(zhuǎn)義 &、<、>、"、以及 ' 字符:

    ?1 function ?escapeXML(content)? {
    ?2 ???? if ?(content? == ?undefined)? return ? "" ;
    ?3 ???? if ?( ! content.length? || ? ! content.charAt)?content? = ? new ?String(content);
    ?4 ???? var ?result? = ? "" ;
    ?5 ???? var ?length? = ?content.length;
    ?6 ???? for ?( var ?i? = ? 0 ;?i? < ?length;?i ++ )? {
    ?7 ???????? var ?ch? = ?content.charAt(i);
    ?8 ???????? switch ?(ch)? {
    ?9 ???????? case ?' & ':
    10 ????????????result? += ? " & " ;
    11 ???????????? break ;
    12 ???????? case ?' < ':
    13 ????????????result? += ? " < " ;
    14 ???????????? break ;
    15 ???????? case ?' > ':
    16 ????????????result? += ? " > " ;
    17 ???????????? break ;
    18 ???????? case ?' " ':
    19 ????????????result?+=? """ ;
    20 ???????????? break ;
    21 ???????? case ?'\'':
    22 ????????????result? += ? " &apos; " ;
    23 ???????????? break ;
    24 ???????? default :
    25 ????????????result? += ?ch;
    26 ????????}

    27 ????}

    28 ???? return ?result;
    29 }

    30

    要使任務(wù)更為簡(jiǎn)單,還需要一些其他實(shí)用程序方法,例如:

    1?function?attribute(name,?value)?{
    2???return?"?"?+?name?+?"=\""?+?escapeXML(value)?+?"\"";
    3?}

    以下示例從一個(gè)具有以下三個(gè)屬性的對(duì)象的數(shù)組構(gòu)建一個(gè) XML 文檔:symbolsharespaidPrice

    ?1?function?buildPortfolioDoc(stocks)?{
    ?2?????var?xml?=?"<portfolio>";
    ?3?????for?(var?i?=?0;?i?<?stocks.length;?i++)?{
    ?4?????????var?stock?=?stocks[i];
    ?5?????????xml?+=?"<stock?";
    ?6?????????xml?+=?attribute("symbol",?stock.symbol);
    ?7?????????xml?+=?attribute("shares",?stock.shares);
    ?8?????????xml?+=?attribute("paidPrice",?stock.paidPrice);
    ?9?????????xml?+=?"";
    10?????}
    11?????xml?+=?"</portfolio>";
    12?????return?xml;
    13?}

    如果您喜好使用 DOM,則可使用 Web 瀏覽器的 API 分析 XML 和串行化 DOM 樹。通過 IE,您可以用新的 ActiveXObject("Microsoft.XMLDOM") 創(chuàng)建一個(gè)空文檔。然后,可以使用 loadXML() 或 load() 方法分別從字符串或 URL 分析該 XML。在使用 IE 的情況下,每個(gè)節(jié)點(diǎn)都有一個(gè)稱為 xml 的屬性,您可以利用它獲得該節(jié)點(diǎn)及其所有子節(jié)點(diǎn)的 XML 表示。因此,您可以分析 XML 字符串、修改 DOM 樹,然后將該 DOM 串行化回 XML。

    Firefox 和 Netscape 瀏覽器允許您使用 document.implementation.createDocument(...) 創(chuàng)建一個(gè)空文檔。然后,可以使用 createElement()、createTextNode()、createCDATASection() 等創(chuàng)建 DOM 節(jié)點(diǎn)。Mozilla 瀏覽器還提供了兩個(gè)分別名為 DOMParser 和 XMLSerializer 的 API。DOMParser API 包含 parseFromStream() 和 parseFromString() 方法。XMLSerializer 類具有串行化 DOM 樹的相應(yīng)方法:serializeToStream() 和 serializeToString()。

    以下函數(shù)分析一個(gè) XML 字符串并返回 DOM 文檔:

    ?1?function?parse(xml)?{
    ?2?????var?dom;
    ?3?????try{
    ?4?????????dom?=?new?ActiveXObject("Microsoft.XMLDOM");
    ?5?????????dom.async?=?false;
    ?6?????????dom.loadXML(xml);
    ?7?????}?catch?(error)?{
    ?8?????????try{
    ?9?????????????var?parser?=?new?DOMParser();
    10?????????????dom?=?parser.parseFromString(xml,?"text/xml");
    11?????????????delete?parser;
    12?????????}?catch?(error2)?{
    13?????????????if?(debug)
    14?????????????alert("XML?parsing?is?not?supported.");
    15?????????}
    16?????}
    17?????return?dom;
    18?}

    第二個(gè)函數(shù)串行化一個(gè) DOM 節(jié)點(diǎn)及其所有子節(jié)點(diǎn),將 XML 作為字符串返回:

    ?1?function?serialize(dom)?{
    ?2?????var?xml?=?dom.xml;
    ?3?????if?(xml?==?undefined)?{
    ?4?????????try{
    ?5?????????????var?serializer?=?new?XMLSerializer();
    ?6?????????????xml?=?serializer.serializeToString(dom);
    ?7?????????????delete?serializer;
    ?8?????????}?catch?(error)?{
    ?9?????????????if?(debug)
    10?????????????alert("DOM?serialization?is?not?supported.");
    11?????????}
    12?????}
    13?????return?xml;
    14?}

    還可以使用 XMLHttpRequest 作為分析程序或串行化程序。在從服務(wù)器接收到對(duì) Ajax 請(qǐng)求的響應(yīng)后,該響應(yīng)會(huì)自動(dòng)進(jìn)行分析。可通過 XMLHttpRequest 的 responseText 和 responseXML 屬性分別訪問文本版本和 DOM 樹。此外,在將 DOM 樹傳遞至 send() 方法時(shí)自動(dòng)將其串行化。

    發(fā)送請(qǐng)求。在先前的文章中,我介紹了 XMLHttpRequest API 和一個(gè)實(shí)用函數(shù) sendHttpRequest(),您可以在提供下載的示例中的 ajaxUtil.js 文件中找到。該函數(shù)有四個(gè)參數(shù)(HTTP 方法、URL、一個(gè)參數(shù)數(shù)組和一個(gè)回調(diào)),可創(chuàng)建 XMLHttpRequest 對(duì)象,設(shè)置其屬性并調(diào)用 send() 方法。如果提供了回調(diào)參數(shù),則異步發(fā)送請(qǐng)求,并在收到響應(yīng)后調(diào)用回調(diào)函數(shù)。否則,將同步發(fā)送請(qǐng)求,您可以在 sendHttpRequest() 返回后即刻處理響應(yīng)。

    如您所見,在使用 XMLHttpRequest 時(shí)必須進(jìn)行一些重要選擇

    • 將要使用的 HTTP 方法(GET 或 POST)
    • 用于編碼請(qǐng)求參數(shù)的格式(本文前面已探討了 XML 和 URL 編碼)
    • 是進(jìn)行同步(等待響應(yīng))調(diào)用還是異步(使用回調(diào))調(diào)用
    • 響應(yīng)的格式,如 XML、XHTML、HTML 或 JavaScript Object Notation (JSON)(本文稍后將對(duì)此進(jìn)行探討)。

    假設(shè)您希望從數(shù)據(jù)饋送了解一些股價(jià)信息,且無需用戶干預(yù)即可定期刷新信息。在本例中,應(yīng)異步發(fā)送 HTTP 請(qǐng)求,這是為了在檢索信息時(shí)不阻塞用戶界面。請(qǐng)求參數(shù)是一個(gè)符號(hào)數(shù)組,可在 URL 中進(jìn)行編碼。由于服務(wù)器可能超載,因此您不希望在進(jìn)行頻繁請(qǐng)求時(shí)發(fā)送 XML 文檔。由于您只對(duì)最新的股價(jià)感興趣,因此應(yīng)終止任何未完成的先前請(qǐng)求:

    ?1?var?ctrlURL?=?"ajaxCtrl.jsp";
    ?2?var?feedRequest?=?null;
    ?3?
    ?4?function?sendInfoRequest(symbols,?callback)?{
    ?5?????if?(feedRequest)?abortRequest(feedRequest);
    ?6?????var?params?=?new?Array();
    ?7?????for?(var?i?=?0;?i?<?symbols.length;?i++)
    ?8?????????params[i]?=?{?name:"symbol",?value:symbols[i]?};
    ?9?????feedRequest?=?sendHttpRequest("GET",?ctrlURL,?params,?callback);
    10?}
    11?

    在調(diào)用請(qǐng)求對(duì)象的 abort() 方法之前,abortRequest() 函數(shù)(可在 ajaxUtil.js 文件中找到)會(huì)將 onreadystatechange 屬性設(shè)置為不執(zhí)行任何操作的回調(diào)。此外,刪除該請(qǐng)求對(duì)象以避免內(nèi)存泄漏,這點(diǎn)至關(guān)重要:

    1?function?abortRequest(request)?{
    2?????function?doNothing()?{?}
    3?????request.onreadystatechange?=?doNothing;
    4?????request.abort();
    5?????delete?feedRequest;
    6?}

    我們來考慮另一種情況:在傳輸要保存在數(shù)據(jù)庫(kù)中的整個(gè)用戶數(shù)據(jù)時(shí),應(yīng)同步發(fā)送請(qǐng)求,因?yàn)槟赡懿幌M脩粼诒4孢@些數(shù)據(jù)進(jìn)行時(shí)對(duì)其進(jìn)行修改。在這種情況下,首選 XML 格式,這是因?yàn)樵谖臋n中進(jìn)行對(duì)象模型編碼通常要比使用很多字符串參數(shù)更簡(jiǎn)單。此外,保存數(shù)據(jù)的請(qǐng)求并不頻繁,服務(wù)器可以毫無問題地處理負(fù)載。可將 XML 文檔編碼為參數(shù),這樣您就可以使用 EL 語法 (${param.xml}) 在 JSP 頁(yè)面中訪問該文檔了。以下就是發(fā)送在 XML 文檔中編碼的模型數(shù)據(jù)的函數(shù):

    1?function?sendSaveRequest(xml)?{
    2?????var?params?=?[?{?name:"xml",?value:xml?}?];
    3?????var?saveRequest?=?sendHttpRequest("POST",?ctrlURL,?params);
    4?????if?(saveRequest)?delete?saveRequest;
    5?}

    如果需要恢復(fù)對(duì)象模型,則也可同步發(fā)送請(qǐng)求,從服務(wù)器檢索數(shù)據(jù)。在這種情況下,服務(wù)器應(yīng)當(dāng)返回一個(gè) JSON 響應(yīng),以便您可利用 eval(loadRequest.responseText) 輕松將其轉(zhuǎn)換為 JavaScript 對(duì)象樹:

    1?function?sendLoadRequest()?{
    2?????var?model?=?null;
    3?????var?loadRequest?=?sendHttpRequest("GET",?ctrlURL);
    4?????if?(loadRequest)?{
    5?????????model?=?eval(loadRequest.responseText);
    6?????????delete?loadRequest;
    7?????????}
    8?????return?model;
    9?}

    以下兩部分介紹了通常在服務(wù)器上對(duì) XML 文檔執(zhí)行的操作,以及如何響應(yīng) Ajax 請(qǐng)求。

    在服務(wù)器端處理請(qǐng)求

    Servlet/JSP 容器分析各個(gè) HTTP 請(qǐng)求并創(chuàng)建一個(gè) ServletRequest 實(shí)例,該實(shí)例使您可以通過 getParameter() / getParameterValues() 獲得請(qǐng)求參數(shù),或通過 getInputStream() 獲得請(qǐng)求正文。在 JSP 頁(yè)面中,也可以使用 EL 語法(${param...} 和 ${paramValues...})獲得這些參數(shù)。請(qǐng)注意,只有在 Ajax 客戶端使用了類似于 buildQueryString() 之類的實(shí)用函數(shù),通過 application/x-www-form-urlencoded 格式來編碼數(shù)據(jù)(本文前一部分有述)的情況下,才可通過 getParameter() 或 ${param...} 獲得請(qǐng)求參數(shù)。如果在客戶端上將 XML 文檔或 DOM 樹傳遞至 XMLHttpRequest 的 send() 方法,則必須在服務(wù)器端使用 ServletRequest 的 getInputStream() 方法。

    數(shù)據(jù)驗(yàn)證。典型的 Web 應(yīng)用程序會(huì)進(jìn)行許多數(shù)據(jù)驗(yàn)證操作。多數(shù)可能的錯(cuò)誤相當(dāng)簡(jiǎn)單,例如缺少請(qǐng)求參數(shù)、數(shù)字格式錯(cuò)誤等等。這些錯(cuò)誤通常是由于用戶忘記輸入表單元素的值或提供了無效值引起的。Web 框架(如 JSF 和 Oracle ADF Faces)非常善于處理這些用戶錯(cuò)誤。在 Ajax 應(yīng)用程序中,這些錯(cuò)誤可以在客戶端使用 JavaScript 來捕獲和處理。例如,您可使用 isNaN(new Number(value)) 驗(yàn)證數(shù)字值是否無效。

    出于安全和可靠性的考慮,應(yīng)當(dāng)在服務(wù)器端對(duì)數(shù)據(jù)進(jìn)行重新驗(yàn)證,而不應(yīng)想當(dāng)然地認(rèn)為 XML 請(qǐng)求格式設(shè)置正確。XML 模式是在服務(wù)器端驗(yàn)證復(fù)雜請(qǐng)求的有用工具。示例代碼下載中包含了一個(gè)名為 XMLUtil 的類,它提供用于加載和使用模式文檔的方法。以下代碼段顯示了如何初始化 SchemaFactory:

    ?1?import?javax.xml.*;
    ?2?import?javax.xml.validation.*;
    ?3?
    ?4?protected?static?SchemaFactory?schemaFactory;
    ?5?static?{
    ?6?????schemaFactory?=?SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    ?7?????schemaFactory.setErrorHandler(newErrorHandler());
    ?8?}
    ?9?
    10?The?newErrorHandler()?method?returns?a?SAX?error?handler:
    11?
    12?import?org.xml.sax.*;
    13?
    14?public?static?ErrorHandler?newErrorHandler()?{
    15?????return?new?ErrorHandler()?{
    16?????????public?void?warning(SAXParseException?e)?throws?SAXException?{
    17?????????????Logger.global.warning(e.getMessage());
    18?????????????}
    19?
    20?????????public?void?error(SAXParseException?e)?throws?SAXException?{
    21?????????????throw?e;
    22?????????????}
    23?
    24?????????public?void?fatalError(SAXParseException?e)?throws?SAXException?{
    25?????????????throw?e;
    26?????????????}
    27?????????};
    28?}
    29?

    可以使用 getResourceAsStream() 查找并加載某個(gè)目錄中的 XSD 文件或 CLASSPATH 中指定的 JAR:

    1?public?static?InputStream?getResourceAsStream(String?name)?throws?IOException?{
    2?????InputStream?in?=?XMLUtil.class.getResourceAsStream(name);
    3?????if?(in?==?null)?throw?new?FileNotFoundException(name);
    4?????return?in;
    5?}

    然后,使用 SchemaFactory 實(shí)例通過 newSchema() 方法獲取 Schema 對(duì)象:

    ?1?import?javax.xml.validation.*;
    ?2?
    ?3?public?static?Schema?newSchema(String?name)?throws?IOException,?SAXException?{
    ?4?????Schema?schema;
    ?5?????InputStream?in?=?getResourceAsStream(name);
    ?6?????try{
    ?7?????????schema?=?schemaFactory.newSchema(new?StreamSource(in));
    ?8?????}finally{
    ?9?????????in.close();
    10?????????}
    11?????return?schema;
    12?}

    您還可以使用以下方法創(chuàng)建 Oracle XMLSchema 對(duì)象:

    ?1?import?oracle.xml.parser.schema.XMLSchema;
    ?2?import?oracle.xml.parser.schema.XSDBuilder;
    ?3?
    ?4?public?static?XMLSchema?newOracleSchema(String?name)?throws?IOException,?SAXException?{
    ?5?????XMLSchema?schema;
    ?6?????InputStream?in?=?getResourceAsStream(name);
    ?7?????try{
    ?8?????????XSDBuilder?builder?=?new?XSDBuilder();
    ?9?????????schema?=?builder.build(new?InputSource(in));
    10?????}?catch?(Exception?e){
    11?????????throw?new?SAXException(e);
    12?????}finally{
    13?????????in.close();
    14?????}
    15?????return?schema;
    16?}

    接下來,您需要?jiǎng)?chuàng)建一個(gè) DocumentBuilderFactory。如果在 CLASSPATH 中找到的是 JAXP 1.1 實(shí)現(xiàn),則由 JAXP 1.2 定義的 setSchema() 方法可能會(huì)拋出 UnsupportedOperationException,此時(shí)需要將 JAXP 1.1 實(shí)現(xiàn)替換為 Java SE 5.0 的 JAXP 1.2 實(shí)現(xiàn)。在這種情況下,您仍可使用 newOracleSchema() 創(chuàng)建模式對(duì)象,并通過 setAttribute()方法對(duì)其進(jìn)行設(shè)置:

    ?1?import?javax.xml.parsers.*;
    ?2?import?oracle.xml.jaxp.JXDocumentBuilderFactory;
    ?3?
    ?4?public?static?DocumentBuilderFactory?newParserFactory(String?schemaName)?throws?IOException,?SAXException?{
    ?5?????DocumentBuilderFactory?parserFactory?=?DocumentBuilderFactory.newInstance();
    ?6?????try{
    ?7?????????parserFactory.setSchema(newSchema(schemaName));
    ?8?????}?catch?(UnsupportedOperationException?e)?{
    ?9?????????if?(parserFactory?instanceof?JXDocumentBuilderFactory)?{
    10?????????????parserFactory.setAttribute(JXDocumentBuilderFactory.SCHEMA_OBJECT,newOracleSchema(schemaName));
    11?????????????}
    12?????????}
    13?????return?parserFactory;
    14?}

    然后,創(chuàng)建一個(gè) DocumentBuilder 對(duì)象,并使用該對(duì)象驗(yàn)證和分析 XML 文檔:

    1?import?javax.xml.parsers.*;
    2?
    3?public?static?DocumentBuilder?newParser(DocumentBuilderFactory?parserFactory)?throws?ParserConfigurationException?{
    4?????DocumentBuilder?parser?=?parserFactory.newDocumentBuilder();
    5?????parser.setErrorHandler(newErrorHandler());
    6?????return?parser;
    7?};

    假設(shè)您要根據(jù) portfolio.xsd 模式示例驗(yàn)證 XML 文檔:

    <xsd:schema?xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    ????
    <xsd:complexType?name="portfolioType">
    ????????
    <xsd:sequence>
    ????????????
    <xsd:element?name="stock"?minOccurs="0"?maxOccurs="unbounded">
    ????????????????
    <xsd:complexType>
    ????????????????????
    <xsd:attribute?name="symbol"?type="xsd:string"?use="required"/>
    ????????????????????
    <xsd:attribute?name="shares"?type="xsd:positiveInteger"?use="required"/>
    ????????????????????
    <xsd:attribute?name="paidPrice"?type="xsd:decimal"?use="required"/>
    ????????????????
    </xsd:complexType>
    ????????????
    </xsd:element>
    ????????
    </xsd:sequence>
    ????
    </xsd:complexType>
    </xsd:schema>

    DataModel 類的 parsePortfolioDoc() 方法使用 XMLUtil 驗(yàn)證和分析 xml 參數(shù),并返回一個(gè) DOM 文檔:

    private?static?final?String?SCHEMA_NAME?=?"/ajaxapp/model/portfolio.xsd";
    private?static?DocumentBuilderFactory?parserFactory;

    private?static?Document?parsePortfolioDoc(String?xml)?throws?IOException,?SAXException,?ParserConfigurationException?{
    ????
    synchronized?(DataModel.class)?{
    ????????
    if?(parserFactory?==?null)
    ????????????parserFactory?
    =?XMLUtil.newParserFactory(SCHEMA_NAME);
    ????????}
    ????DocumentBuilder?parser?
    =?XMLUtil.newParser(parserFactory);
    ????InputSource?in?
    =?new?InputSource(new?StringReader(xml));
    ????
    return?parser.parse(in);
    }

    現(xiàn)在,您擁有了一個(gè) DOM 樹,接下來要獲取形成 DOM 節(jié)點(diǎn)所需的數(shù)據(jù)。

    提取所需信息。您可以使用 DOM API 或查詢語言(如 XQuery 或 XPath)來瀏覽 DOM 樹。Java 為 XPath 提供了標(biāo)準(zhǔn)的 API,后面會(huì)用到。XMLUtil 類創(chuàng)建一個(gè)具有 newXPath() 方法的 XPathFactory:

    import?javax.xml.xpath.*;

    protected?static?XPathFactory?xpathFactory;
    static?{
    ????xpathFactory?
    =?XPathFactory.newInstance();
    }
    ????
    public?static?XPath?newXPath()?{
    ????
    return?xpathFactory.newXPath();
    }

    以下方法在給定的上下文中求解 XPath 表達(dá)式,返回結(jié)果值:

    public?static?String?evalToString(String?expression,?Object?context)?throws?XPathExpressionException?{
    ????
    return?(String)?newXPath().evaluate(expression,?context,?XPathConstants.STRING);
    }
    ????
    public?static?boolean?evalToBoolean(String?expression,?Object?context)?throws?XPathExpressionException?{
    ????
    return?((Boolean)?newXPath().evaluate(expression,?context,?XPathConstants.BOOLEAN)).booleanValue();
    }
    ????
    public?static?double?evalToNumber(String?expression,?Object?context)?throws?XPathExpressionException?{
    ????
    return?((Double)?newXPath().evaluate(expression,?context,?XPathConstants.NUMBER)).doubleValue();
    }
    ????
    public?static?Node?evalToNode(String?expression,?Object?context)?throws?XPathExpressionException?{
    ????
    return?(Node)?newXPath().evaluate(expression,?context,?XPathConstants.NODE);
    }
    ????
    public?static?NodeList?evalToNodeList(String?expression,?Object?context)?throws?XPathExpressionException?{
    ????
    return?(NodeList)?newXPath().evaluate(expression,?context,?XPathConstants.NODESET);
    }

    DataModel 的 setData() 方法使用 XPath 求解方法從組合 XML 文檔提取信息:

    public?synchronized?void?setData(String?xml)?throws?IOException,?SAXException,?ParserConfigurationException,?XPathExpressionException?{
    ????
    try{
    ????????ArrayList?stockList?
    =?new?ArrayList();
    ????????Document?doc?
    =?parsePortfolioDoc(xml);
    ????????NodeList?nodeList?
    =?XMLUtil.evalToNodeList("/portfolio/stock",?doc);
    ????????
    for?(int?i?=?0;?i?<?nodeList.getLength();?i++)?{
    ????????????Node?node?
    =?nodeList.item(i);
    ????????????StockBean?stock?
    =?new?StockBean();
    ????????????stock.setSymbol(XMLUtil.evalToString(
    "@symbol",?node));
    ????????????stock.setShares((
    int)?XMLUtil.evalToNumber("@shares",?node));
    ????????????stock.setPaidPrice(XMLUtil.evalToNumber(
    "@paidPrice",?node));
    ????????????stockList.add(stock);
    ????????????}
    ????????
    this.stockList?=?stockList;
    ????}?
    catch?(Exception?e){
    ????????Logger.global.logp(Level.SEVERE,?
    "DataModel",?"setData",e.getMessage(),?e);
    ????}
    }

    一旦服務(wù)器端的數(shù)據(jù)模型中具備了數(shù)據(jù),就可根據(jù)應(yīng)用程序的要求對(duì)其進(jìn)行處理了。然后,您必須響應(yīng) Ajax 請(qǐng)求。

    在服務(wù)器端生成響應(yīng)

    將 HTML 作為 Ajax 請(qǐng)求的響應(yīng)而返回是一種最簡(jiǎn)單的解決方案,這是因?yàn)槟梢允褂?JSP 語法構(gòu)建標(biāo)記,而 Ajax 客戶端只需使用 <div> 或 <span> 元素的 innerHTML 屬性在頁(yè)面某處插入 HTML。但是,向 Ajax 客戶端返回不帶任何表示標(biāo)記的數(shù)據(jù)則更為有效。您可以使用 XML 格式或 JSON。

    生成 XML 響應(yīng)。Java EE 提供了很多創(chuàng)建 XML 文檔的選項(xiàng):可通過 JSP 生成、通過 JAXB 從對(duì)象樹創(chuàng)建、或利用 javax.xml.transform 生成。以下示例中的轉(zhuǎn)換程序?qū)⒋谢粋€(gè) DOM 樹:

    import?javax.xml.transform.*;
    import?javax.xml.transform.dom.*;
    import?javax.xml.transform.stream.*;

    public?static?TransformerFactory?serializerFctory;
    static?{
    ????serializerFctory?
    =?TransformerFactory.newInstance();
    }
    ????
    public?static?void?serialize(Node?node,?OutputStream?out)?throws?TransformerException?{
    ????Transformer?serializer?
    =?serializerFctory.newTransformer();
    ????Properties?serializerProps?
    =?new?Properties();
    ????serializerProps.put(OutputKeys.METHOD,?
    "xml");
    ????serializer.setOutputProperties(serializerProps);
    ????Source?source?
    =?new?DOMSource(node);
    ????Result?result?
    =?new?StreamResult(out);
    ????serializer.transform(source,?result);
    }

    有這么多可在服務(wù)器端生成 XML 的標(biāo)準(zhǔn)選項(xiàng)和開發(fā)源框架,您唯一所要做的就是選擇一個(gè)適合你的選項(xiàng)。但是,在客戶端上,由于只能使用 DOM 來分析 XML,因此情況非常不同。某些瀏覽器還支持 XPath 和 XSLT。

    在先前的 Ajax 文章中,您學(xué)習(xí)了如何通過 JSP 生成 XML,然后在客戶端上利用 JavaScript 和 DOM 對(duì)其進(jìn)行分析。另一個(gè)解決方案是使用 JSON 而非 XML 作為響應(yīng) Ajax 請(qǐng)求的數(shù)據(jù)格式。如前所述,JSON 字符串可通過 eval() 函數(shù)轉(zhuǎn)化為 JavaScript 對(duì)象樹。較之利用 JavaScript 從 DOM 樹提取信息而言,這更為簡(jiǎn)單些。您所需的就是一個(gè)在服務(wù)器端生成 JSON 的良好實(shí)用類。

    JSON 編碼。JSONEncoder 類提供了編碼文字、對(duì)象和數(shù)組的方法。結(jié)果存儲(chǔ)在 java.lang.StringBuilder 中:

    package?ajaxapp.util;

    public?class?JSONEncoder?{
    ????
    private?StringBuilder?buf;
    ????
    ????
    public?JSONEncoder()?{
    ????????buf?
    =?new?StringBuilder();
    ????}

    ????
    }

    character() 方法編碼單一字符:

    public?void?character(char?ch)?{
    ????
    switch?(ch)?{
    ????
    case?'\'':
    ????case?'\"':
    ????
    case?'\\':
    ????????buf.append(
    '\\');
    ????????buf.append(ch);
    ????????
    break;
    ????
    case?'\t':
    ????????buf.append(
    '\\');
    ????????buf.append(
    't');
    ????????
    break;
    ????
    case?'\r':
    ????????buf.append(
    '\\');
    ????????buf.append(
    'r');
    ????????
    break;
    ????
    case?'\n':
    ????????buf.append(
    '\\');
    ????????buf.append(
    'n');
    ????????
    break;
    ????
    default:
    ????????
    if?(ch?>=?32?&&?ch?<?128)?buf.append(ch);
    ????????
    else{
    ????????????buf.append(
    '\\');
    ????????????buf.append(
    'u');
    ????????????
    for?(int?j?=?12;?j?>=?0;?j-=4)?{
    ????????????????
    int?k?=?(((int)?ch)?>>?j)?&?0x0f;
    ????????????????
    int?c?=?k?<?10??'0'?+?k?:'a'?+?k?-?10;
    ????????????????buf.append((
    char)?c);
    ????????????}
    ????????}
    ????}
    }

    string() 方法編碼整個(gè)字符串:

    public?void?string(String?str)?{
    ????
    int?length?=?str.length();
    ????
    for?(int?i?=?0;?i?<?length;?i++)
    ????????character(str.charAt(i));
    }

    literal() 方法編碼 JavaScript 文字:

    public?void?literal(Object?value)?{
    ????
    if?(value?instanceof?String)?{
    ????????buf.append(
    '"');
    ????????string((String)?value);
    ????????buf.append(
    '"');
    ????}?
    else?if?(value?instanceof?Character)?{
    ????????buf.append(
    '\'');
    ????????character(((Character)?value).charValue());
    ????????buf.append(
    '\'');
    ????}?else
    ????????buf.append(value.toString());
    }

    comma() 方法附加一個(gè)逗號(hào)字符:

    private?void?comma()?{
    ??buf.append(
    ',');
    }

    deleteLastComma() 方法將移除緩沖區(qū)末尾最后一個(gè)逗號(hào)字符(如果有的話):

    private?void?deleteLastComma()?{
    ??
    if?(buf.length()?>?0)
    ????
    if?(buf.charAt(buf.length()-1)?==?',')
    ??????buf.deleteCharAt(buf.length()
    -1);
    }

    startObject() 方法附加一個(gè) { 字符,用于表示一個(gè) JavaScript 對(duì)象的開始:

    public?void?startObject()?{
    ??buf.append(
    '{');
    }

    property() 方法編碼 JavaScript 屬性:

    public?void?property(String?name,?Object?value)?{
    ??buf.append(name);
    ??buf.append(
    ':');
    ??literal(value);
    ??comma();
    }

    endObject() 方法附加一個(gè) } 字符,用于表示一個(gè) JavaScript 對(duì)象的結(jié)束:

    public?void?endObject()?{
    ??deleteLastComma();
    ??buf.append(
    '}');
    ??comma();
    }

    startArray() 方法附加一個(gè) [ 字符,用于表示一個(gè) JavaScript 數(shù)組的開始:

    public?void?startArray()?{
    ??buf.append(
    '[');
    }

    element() 方法編碼 JavaScript 數(shù)組的元素:

    public?void?element(Object?value)?{
    ??literal(value);
    ??comma();
    }

    endArray() 方法附加一個(gè) ] 字符,用于表示一個(gè) JavaScript 數(shù)組的結(jié)束:

    public?void?endArray()?{
    ??deleteLastComma();
    ??buf.append(
    ']');
    ??comma();
    }

    toString() 方法返回 JSON 字符串:

    public?String?toString()?{
    ??deleteLastComma();
    ??
    return?buf.toString();
    }

    clear() 方法清空緩沖區(qū):

    public?void?clear()?{
    ??buf.setLength(
    0);
    }

    DataModel 使用 JSONEncoder 類來編碼其維護(hù)的數(shù)據(jù):

    public?synchronized?String?getData()?{
    ????JSONEncoder?json?
    =?new?JSONEncoder();
    ????json.startArray();
    ????
    for?(int?i?=?0;?i?<?stockList.size();?i++)?{
    ????????StockBean?stock?
    =?stockList.get(i);
    ????????json.startObject();
    ????????json.property(
    "symbol",?stock.getSymbol());
    ????????json.property(
    "shares",?stock.getShares());
    ????????json.property(
    "paidPrice",?stock.getPaidPrice());
    ????????json.endObject();
    ????}
    ????json.endArray();
    ????
    return?json.toString();
    }

    如果提供了 xml 請(qǐng)求參數(shù),則 ajaxCtrl.jsp 頁(yè)面將設(shè)置模型的數(shù)據(jù)。否則,該頁(yè)面會(huì)使用 ${dataModel.data} EL 表達(dá)式輸出 getData() 返回的 JSON 字符串:

    <%@?taglib?prefix="c"?uri="http://java.sun.com/jsp/jstl/core"?%>

    <jsp:useBean?id="dataModel"?scope="session"?class="ajaxapp.model.DataModel"?/>

    <c:choose>
    ????
    ????
    <c:when?test="${!empty?param.xml}">
    ????????
    <c:set?target="${dataModel}"?property="data"?value="${param.xml}"?/>
    ????
    </c:when>
    ????
    <c:otherwise>
    ????????${dataModel.data}
    ????
    </c:otherwise>
    </c:choose>

    這個(gè)作業(yè)并未完成,因?yàn)?Ajax 客戶端必須處理 JSON 數(shù)據(jù)。

    在客戶端處理響應(yīng)

    在典型的 Web 應(yīng)用程序中,您使用 JSP、Web 框架和標(biāo)記庫(kù)在服務(wù)器端生成內(nèi)容。Ajax 應(yīng)用程序非常適合這種情況,因?yàn)?Web 框架(如 JavaServer Faces 和 Oracle ADF Faces)在構(gòu)建 Ajax 應(yīng)用程序時(shí)非常有用。然而,Ajax 和非 Ajax 應(yīng)用程序之間仍然存在著明顯不同。使用 Ajax 時(shí),您必須在客戶端處理數(shù)據(jù),并用 JavaScript 動(dòng)態(tài)生成內(nèi)容來向用戶提供數(shù)據(jù)。

    如果使用 JSON 格式進(jìn)行數(shù)據(jù)轉(zhuǎn)換,則使用 JavaScript 提供的 eval() 函數(shù)將文本轉(zhuǎn)換為對(duì)象樹非常容易。如果喜歡使用 XML,則還需要執(zhí)行許多其他操作,但這種格式也有其自身的優(yōu)勢(shì)。例如,許多類型的客戶端可以使用 XML,而 JSON 只在 JavaScript 環(huán)境中易于分析。此外,在使用 XML 的情況下,可以更快地發(fā)現(xiàn)并修正錯(cuò)誤,從而縮短了調(diào)試時(shí)間。

    使用 JavaScript 訪問 DOM 樹。JavaScript 的 DOM API 非常類似于 Java 的 org.w3c.dom 程序包。主要的區(qū)別在于對(duì)屬性的訪問。在 JavaScript 中可直接訪問屬性,而 Java 將屬性視為私有,您需要通過 get 和 set 方法進(jìn)行訪問。例如,您可通過 dom.documentElement 獲得文檔的根元素。

    DOM 是一種低級(jí)的 API,利用它可以訪問已分析文檔的結(jié)構(gòu)。例如,在大多數(shù)情況下您都想忽略注釋,并可能不愿意擁有相鄰的文本節(jié)點(diǎn)。來看下面這個(gè)簡(jiǎn)單示例:

    var?xml?=?"<element>da<!--comment-->ta&"?+?"<![CDATA[cdata</element>";

    您可以使用先前介紹的實(shí)用函數(shù)分析上述 XML 字符串:

    var?dom?=?parse(xml);

    您可以在 ajaxUtil.js 中找到 parse() 函數(shù)的代碼;本例中,該函數(shù)返回一個(gè) DOM 樹,其根元素包含一個(gè)文本節(jié)點(diǎn),其后是一條注釋、另一個(gè)文本節(jié)點(diǎn)和一個(gè)字符數(shù)據(jù)節(jié)點(diǎn)。如果希望包含的文本不帶注釋,則必須迭代元素的子元素,連接文本和字符數(shù)據(jù)節(jié)點(diǎn)的值(其類型分別為 3 和 4):

    var?element?=?dom.documentElement;
    var?childNodes?=?element.childNodes;
    var?text?=?"";
    for?(var?i?=?0;?i?<?childNodes.length;?i++)
    ????
    if?(childNodes[i].nodeValue)?{
    ????????
    var?type?=?childNodes[i].nodeType;
    ????????
    if?(type?==?3?||?type?==?4)
    ????????????text?
    +=?childNodes[i].nodeValue;
    ????????}

    使用 DOM 時(shí),應(yīng)當(dāng)構(gòu)建一個(gè)小的實(shí)用函數(shù)集,以避免處理上述這些低級(jí)細(xì)節(jié)。

    使用 JavaScript 生成動(dòng)態(tài)內(nèi)容。Web 瀏覽器使您可以通過文檔對(duì)象訪問 Web 頁(yè)面的 DOM 結(jié)構(gòu)。例如,您可以利用 document.getElementById(...) 非常輕松地找到一個(gè)元素。還可以創(chuàng)建可插入現(xiàn)有文檔的新元素和文本節(jié)點(diǎn)。然而,如下所示,通過連接字符串構(gòu)建 HTML 要更為簡(jiǎn)單:

    function?updateInfo(request)?{
    ????
    var?shares?=?eval(request.responseText);
    ????
    var?table?=?"<table?border=1?cellpadding=5>";
    ????table?
    +=?"<tr>";
    ????table?
    +=?"<th>Symbol</th>";
    ????table?
    +=?"<th>Trend</th>";
    ????table?
    +=?"<th>Last?Price</th>";
    ????table?
    +=?"</tr>";
    ????
    for?(var?i?=?0;?i?<?shares.length;?i++)?{
    ????????
    var?share?=?shares[i];
    ????????
    var?symbol?=?escapeXML(share.symbol)
    ????????
    var?trend?=?share.trend?>?0???"+"?:?"-";
    ????????
    var?lastPrice?=?new?Number(share.lastPrice).toFixed(2);
    ????????table?
    +=?"<tr>";
    ????????table?
    +=?"<td>"?+?symbol?+?"</td>";
    ????????table?
    +=?"<td>"?+?trend?+?"</td>";
    ????????table?
    +=?"<td>"?+?lastPrice?+?"</td>";
    ????????table?
    +=?"</tr>";
    ????????}
    ????table?
    +=?"</table>";
    ????document.getElementById(
    "table").innerHTML?=?table;
    }

    通過設(shè)置由 getElementById() 返回的對(duì)象的 innerHTML 屬性,可將生成的 HTML 插入空 <div> 元素中,例如:

    <div?id="table">
    </div>

    本文的示例將 updateInfo() 函數(shù)用作回調(diào)來處理對(duì) Ajax 請(qǐng)求的響應(yīng),該請(qǐng)求是通過 ajaxLogic.js 文件中的 sendInfoRequest 發(fā)送到服務(wù)器的。如果希望每 5 秒更新一次信息,可使用 JavaScript 的 setInterval() 函數(shù):

    var?symbols?=?[??];
    setInterval(
    "sendInfoRequest(symbols,?updateInfo)",?5000);

    一個(gè)名為 DataFeed 的類模擬服務(wù)器端的饋送。ajaxCtrl.jsp 頁(yè)面調(diào)用該饋送的 getData() 方法,將響應(yīng)作為 JSON 字符串返回。在客戶端,updateInfo() 函數(shù)利用 eval(request.responseText) 對(duì)該 JSON 字符串進(jìn)行分析,如上述代碼示例中所示。

    結(jié)論

    在本文中,首先介紹了如何使用 URL 編碼和 XML 格式從 Ajax 客戶端向服務(wù)器傳輸數(shù)據(jù)。然后,闡釋了如何通過 XML 模式驗(yàn)證數(shù)據(jù)、利用 XPath 提取所需信息以及在服務(wù)器端生成 XML 或 JSON 響應(yīng)。最后,介紹了如何利用 JavaScript 處理 XML 和 JSON 響應(yīng),以及如何生成用于更新瀏覽器中 Web 頁(yè)面的動(dòng)態(tài)內(nèi)容。

    posted on 2006-08-25 09:54 Mongoose 閱讀(770) 評(píng)論(0)  編輯  收藏


    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 成年美女黄网站色大免费视频| 成人看的午夜免费毛片| 在线a毛片免费视频观看| 亚洲美女在线国产| 亚洲精品国产情侣av在线| 亚洲av日韩综合一区二区三区| 天堂在线免费观看| 女性无套免费网站在线看| 亚洲日韩精品一区二区三区无码 | 69式互添免费视频| 亚洲日韩国产精品乱| 亚洲国产亚洲综合在线尤物| 又大又硬又粗又黄的视频免费看| 亚洲视频在线观看免费视频| www.91亚洲| 国产亚洲sss在线播放| 国产一级在线免费观看| 全免费a级毛片免费看不卡| 亚洲av之男人的天堂网站| 国产成人综合亚洲| 青娱乐免费在线视频| 国产亚洲一区二区三区在线| 亚洲精品成a人在线观看☆| 亚洲韩国精品无码一区二区三区| 中文字幕精品三区无码亚洲| 免费看成人AA片无码视频吃奶| 日韩视频在线免费观看| 日韩精品一区二区亚洲AV观看| 特级做a爰片毛片免费看| 性做久久久久久免费观看| 久久久久久亚洲精品中文字幕| 高h视频在线免费观看| 色se01短视频永久免费| 亚洲AV午夜成人片| 一区二区三区免费在线视频| 成人超污免费网站在线看| 亚洲日本在线免费观看| 青娱分类视频精品免费2| 精品国产日韩亚洲一区| 亚洲色成人四虎在线观看| 最近中文字幕高清免费中文字幕mv|