亚洲日韩精品A∨片无码,亚洲无成人网77777,亚洲成人中文字幕http://m.tkk7.com/liaojiyong/category/11431.htmlzh-cnTue, 15 May 2007 09:10:59 GMTTue, 15 May 2007 09:10:59 GMT60JSTL 入門(轉)http://m.tkk7.com/liaojiyong/archive/2007/03/21/105235.htmlliaojiyongliaojiyongWed, 21 Mar 2007 04:15:00 GMThttp://m.tkk7.com/liaojiyong/archive/2007/03/21/105235.htmlhttp://m.tkk7.com/liaojiyong/comments/105235.htmlhttp://m.tkk7.com/liaojiyong/archive/2007/03/21/105235.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/105235.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/105235.html

通過避免使用腳本編制元素來簡化對 JSP 應用程序的軟件維護

?

?


?


級別: 初級

Mark A. Kolb (mak@taglib.com), 軟件工程師

2003 年 5 月 27 日

JSP 標準標記庫(JSP Standard Tag Library,JSTL)是一個實現 Web 應用程序中常見的通用功能的定制標記庫集,這些功能包括迭代和條件判斷、數據管理格式化、XML 操作以及數據庫訪問。在 developerWorks 上其新系列的第一篇文章中,軟件工程師 Mark Kolb 向您展示了如何使用 JSTL 標記來避免在 JSP 頁面中使用腳本編制元素。您還將了解如何通過從表示層刪除源代碼來簡化軟件維護。最后,您將了解 JSTL 經過簡化的表達式語言,它允許在不必使用功能齊全的編程語言的情況下對 JSTL 操作指定動態屬性值。

JavaServer Pages(JSP)是用于 J2EE 平臺的標準表示層技術。JSP 技術提供了用于執行計算(這些計算用來動態地生成頁面內容)的腳本編制元素和操作。腳本編制元素允許在 JSP 頁面中包括程序源代碼,在為響應用戶請求而呈現頁面時可以執行這些源代碼。操作將計算操作封裝到很象 HTML 或 XML 標記的標記中,JSP 頁面的模板文本通常包含這些標記。JSP 規范只將幾種操作定義成了標準,但從 JSP 1.1 開始,開發人員已經能夠以定制標記庫的方式創建其自己的操作了。

JSP 標準標記庫(JSTL)是 JSP 1.2 定制標記庫集,這些標記庫實現大量服務器端 Java 應用程序常用的基本功能。通過為典型表示層任務(如數據格式化和迭代或條件內容)提供標準實現,JSTL 使 JSP 作者可以專注于特定于應用程序的開發需求,而不是為這些通用操作“另起爐灶”。

當然,您可以使用 JSP 腳本編制元素(scriptlet、表達式和聲明)來實現此類任務。例如,可以使用三個 scriptlet 實現條件內容,清單 1 中著重顯示了這三個 scriptlet。但是,因為腳本編制元素依賴于在頁面中嵌入程序源代碼(通常是 Java 代碼),所以對于使用這些腳本編制元素的 JSP 頁面,其軟件維護任務的復雜度大大增加了。例如,清單 1 中的 scriptlet 示例嚴格地依賴于花括號的正確匹配。如果不經意間引入了一個語法錯誤,則條件內容中的嵌套其它 scriptlet 可能會造成嚴重破壞,并且在 JSP 容器編譯該頁面時,要使所產生的錯誤信息有意義可能會很困難。



清單 1. 通過 scriptlet 實現條件內容
<% if (user.getRole() == "member")) { %>
    <p>Welcome, member!</p>
<% } else { %>
    <p>Welcome, guest!</p>
<% } %>

修正此類問題通常需要相當豐富的編程經驗。盡管通常會由十分精通頁面布局和圖形設計的設計人員來開發和維護 JSP,但是同一頁面中的腳本編制元素出現問題時,需要程序員的介入。這種狀況將單個文件中代碼的責任分擔給多人,因而使得開發、調試和增強此類 JSP 頁面成為很麻煩的任務。通過將常用功能包裝到定制標記庫的標準集合中,JSTL 使 JSP 作者可以減少對編制腳本元素的需求,甚至可以不需要它們,并避免了相關的維護成本。





JSTL 1.0

JSTL 1.0 發布于 2002 年 6 月,由四個定制標記庫( coreformatxmlsql )和一對通用標記庫驗證器( ScriptFreeTLVPermittedTaglibsTLV )組成。 core 標記庫提供了定制操作,通過限制了作用域的變量管理數據,以及執行頁面內容的迭代和條件操作。它還提供了用來生成和操作 URL 的標記。顧名思義, format 標記庫定義了用來格式化數據(尤其是數字和日期)的操作。它還支持使用本地化資源束進行 JSP 頁面的國際化。 xml 庫包含一些標記,這些標記用來操作通過 XML 表示的數據,而 sql 庫定義了用來查詢關系數據庫的操作。

兩個 JSTL 標記庫驗證器允許開發人員在其 JSP 應用程序中強制使用編碼標準。可以配置 ScriptFreeTLV 驗證器以在 JSP 頁面中禁用各種類型的 JSP 腳本元素 ― scriptlet、表達式和聲明。類似地, PermittedTaglibsTLV 驗證器可以用來限制可能由應用程序的 JSP 頁面訪問的定制標記庫集(包括 JSTL 標記庫)。

盡管 JSTL 最終將會成為 J2EE 平臺的必需組件,但目前只有少數應用程序服務器包括它。JSTL 1.0 的參考實現可作為 Apache 軟件基金會(Apache Software Foundation)的 Jakarta Taglibs 項目(請參閱 參考資料)的一部分而獲得。可以將該參考實現中的定制標記庫合并到任何支持 JSP 1.2 和 Servlet 2.3 規范的服務器,以添加對 JSTL 的支持。





表達式語言

在 JSP 1.2 中,可以使用靜態字符串或表達式(如果允許的話)指定 JSP 操作的屬性。例如,在清單 2 中,對 <jsp:setProperty> 操作的 nameproperty 屬性指定了靜態值,而用表達式指定了其 value 屬性。這個操作的效果是將請求參數的當前值賦予命名的 bean 特性。以這種形式使用的表達式被稱為 請求時屬性值(request-time attribute value),這是構建到 JSP 規范中的用于動態指定屬性值的唯一機制。



清單 2. 合并請求時屬性值的 JSP 操作
<jsp:setProperty name="user" property="timezonePref"
                 value='<%= request.getParameter("timezone") %>'/>

因為請求時屬性值是用表達式指定的,所以它們往往有和其它腳本元素一樣的軟件維護問題。因此,JSTL 定制標記支持另一種用于指定動態屬性值的機制。可以用簡化的 表達式語言(EL)而不使用完整的 JSP 表達式來指定 JSTL 操作的屬性值。EL 提供了一些標識符、存取器和運算符,用來檢索和操作駐留在 JSP 容器中的數據。EL 在某種程度上以 EcmaScript(請參閱 參考資料)和 XML 路徑語言(XML Path Language,XPath)為基礎,因此頁面設計人員和程序員都應該熟悉它的語法。EL 擅長尋找對象及其特性,然后對它們執行簡單操作;它不是編程語言,甚至不是腳本編制語言。但是,與 JSTL 標記一起使用時,它就能使用簡單而又方便的符號來表示復雜的行為。EL 表達式的格式是這樣的:用美元符號($)定界,內容包括在花括號({})中,如清單 3 所示。



清單 3. 說明 EL 表達式定界符的 JSTL 操作
<c:out value="${user.firstName}"/>

此外,您可以將多個表達式與靜態文本組合在一起以通過字符串并置來構造動態屬性值,如清單 4 所示。單獨的表達式由標識符、存取器、文字和運算符組成。標識符用來引用存儲在數據中心中的數據對象。EL 有 11 個保留標識符,對應于 11 個 EL 隱式對象。假定所有其它標識符都引用 限制了作用域的變量。存取器用來檢索對象的特性或集合的元素。文字表示固定的值 ― 數字、字符、字符串、布爾型或空值。運算符允許對數據和文字進行組合以及比較。



清單 4. 組合靜態文本和多個 EL 表達式以指定動態屬性值
<c:out value="Hello ${user.firstName} ${user.lastName}"/>

限制了作用域的變量

JSP API 通過 <jsp:useBean> 操作允許從 JSP 容器內的四個不同作用域中存儲和檢索數據。JSTL 通過提供用于指定和除去這些作用域中的對象的附加操作來擴展這一能力。此外,EL 提供將這些對象作為限制了作用域的變量進行檢索的內置支持。特別地,任何出現在 EL 表達式中但不對應于任何 EL 隱式對象的標識符,都被自動假定為引用存儲在四個 JSP 作用域的其中某個中的對象,這四個作用域是:

  • 頁面作用域
  • 請求作用域
  • 會話作用域
  • 應用程序作用域

您可能還記得,只有在為特定請求處理頁面期間才能檢索存儲在該頁面作用域中的對象。如果對象是存儲在請求作用域中的,可以在處理所有參與處理某請求的頁面期間檢索這些對象(譬如在對某個請求的處理中遇到了一個或多個 <jsp:include><jsp:forward> 操作)。如果對象是存儲在會話作用域中的,則在與 Web 應用程序的交互式會話期間,可以由用戶訪問的任何頁面檢索它(即,直到與該用戶交互相關聯的 HttpSession 對象無效為止)。可以由任何用戶從任何頁面訪問存儲在應用程序作用域中的對象,直到卸載 Web 應用程序本身為止(通常是由于關閉 JSP 容器所致)。

通過將字符串映射為期望作用域中的對象來將對象存儲到該作用域。然后,就可以通過提供相同字符串來從該作用域檢索該對象。在作用域的映射中查找字符串,并返回被映射的對象。在 Servlet API 中,將此類對象稱為相應作用域的 屬性。但是,在 EL 的上下文中,也將與屬性相關聯的字符串看作變量的名稱,該變量通過屬性映射的方式獲得特定的值。

在 EL 中,與隱式對象無關聯的標識符被認為是存儲在四個 JSP 作用域中的名稱對象。首先對頁面作用域檢查是否存在這樣的標識符,其次對請求作用域、然后對會話作用域、最后對應用程序作用域依次進行這樣的檢查,然后測試該標識符的名稱是否與存儲在該作用域中的某個對象的名稱匹配。第一個這樣的匹配作為 EL 標識符的值被返回。通過這種方法,可以將 EL 標識符看作引用限制了作用域的變量。

從更技術的方面來說,沒有映射到隱式對象的標識符是用 PageContext 實例的 findAttribute() 方法求值的,該實例表示對頁面的處理,在該頁面上,當前正在處理用于請求的表達式。標識符的名稱作為參數傳遞給這個方法,然后該方法依次在四個作用域中搜索具有相同名稱的屬性。并將所找到的第一個匹配項作為 findAttribute() 方法的值返回。如果未在這四個作用域中找到這樣的屬性,則返回 null

最終,限制了作用域的變量是四個 JSP 作用域的屬性,這些屬性具有可以用作 EL 標識符的名稱。只要對限制了作用域的變量賦予由字母數字組成的名稱,就可以通過 JSP 中提供的用于設置屬性的任何機制來創建它們。這包括內置的 <jsp:useBean> 操作,以及由 Servlet API 中的幾個類定義的 setAttribute() 方法。此外,四個 JSTL 庫中定義的許多定制標記本身就能夠設置作為限制了作用域的變量使用的屬性值。

隱式對象

表 1 中列出了 11 個 EL 隱式對象的標識符。不要將這些對象與 JSP 隱式對象(一共只有九個)混淆,其中只有一個對象是它們所共有的。

表 1. EL 隱式對象

類別 標識符 描述
JSP pageContext PageContext 實例對應于當前頁面的處理
作用域 pageScope 與頁面作用域屬性的名稱和值相關聯的 Map
requestScope 與請求作用域屬性的名稱和值相關聯的 Map
sessionScope 與會話作用域屬性的名稱和值相關聯的 Map
applicationScope 與應用程序作用域屬性的名稱和值相關聯的 Map
請求參數 param 按名稱存儲請求參數的主要值的 Map
paramValues 將請求參數的所有值作為 String 數組存儲的 Map
請求頭 header 按名稱存儲請求頭主要值的 Map
headerValues 將請求頭的所有值作為 String 數組存儲的 Map
Cookie cookie 按名稱存儲請求附帶的 cookie 的 Map
初始化參數 initParam 按名稱存儲 Web 應用程序上下文初始化參數的 Map

盡管 JSP 和 EL 隱式對象中只有一個公共對象( pageContext ),但通過 EL 也可以訪問其它 JSP 隱式對象。原因是 pageContext 擁有訪問所有其它八個 JSP 隱式對象的特性。實際上,這是將它包括在 EL 隱式對象中的主要理由。

其余所有 EL 隱式對象都是映射,可以用來查找對應于名稱的對象。前四個映射表示先前討論的各種屬性作用域。可以用它們來查找特定作用域中的標識符,而不用依賴于 EL 在缺省情況下使用的順序查找過程。

接下來的四個映射用來獲取請求參數和請求頭的值。因為 HTTP 協議允許請求參數和請求頭具有多個值,所以它們各有一對映射。每對中的第一個映射返回請求參數或頭的主要值,通常是恰巧在實際請求中首先指定的那個值。每對中第二個映射允許檢索參數或頭的所有值。這些映射中的鍵是參數或頭的名稱,但這些值是 String 對象的數組,其中的每個元素都是單一參數值或頭值。

cookie 隱式對象提供了對由請求設置的 cookie 名稱的訪問。這個對象將所有與請求相關聯的 cookie 名稱映射到表示那些 cookie 特性的 Cookie 對象。

最后一個 EL 隱式對象 initParam 是一個映射,它儲存與 Web 應用程序相關聯的所有上下文的初始化參數的名稱和值。初始化參數是通過 web.xml 部署描述符文件指定的,該文件位于應用程序的 WEB-INF 目錄中。


存取器

因為 EL 標識符是作為隱式對象或限制了作用域的變量(通過屬性來實現)解析的,因此有必要將它們轉換成 Java 對象。EL 可以自動包裝和解包其相應的 Java 類中的基本類型(例如,可以在后臺將 int 強制轉換成 Integer 類,反之亦可),但大多數的標識符將成為指向完整的 Java 對象的指針。

結果是,對這些對象的特性或(在對象是數組和集合的情況下)對其元素的訪問通常是令人滿意的。就為了實現這種用途,EL 提供了兩種不同的存取器(點運算符( . )和方括號運算符( [] )),也支持通過 EL 操作特性和元素。

點運算符通常用于訪問對象的特性。例如,在表達式 ${user.firstName} 中,使用點運算符來訪問 user 標識符所引用對象的名為 firstName 的特性。EL 使用 Java bean 約定訪問對象特性,因此必須定義這個特性的 getter 方法(通常是名為 getFirstName() 的方法),以便表達式正確求值。當被訪問的特性本身是對象時,可以遞歸地應用點運算符。例如,如果我們虛構的 user 對象有一個實現為 Java 對象的 address 特性,那么也可以用點運算符來訪問這個對象的特性。例如,表達式 ${user.address.city} 將會返回這個地址對象嵌套的 city 特性。

方括號運算符用來檢索數組和集合的元素。在數組和有序集合(也即,實現了 java.util.List 接口的集合)的情況下,把要檢索的元素的下標放在方括號中。例如,表達式 ${urls[3]} 返回 urls 標識符所引用的數組或集合的第四個元素(和 Java 語言以及 JavaScript 中一樣,EL 中的下標是從零開始的)。

對于實現 java.util.Map 接口的集合,方括號運算符使用關聯的鍵查找存儲在映射中的值。在方括號中指定鍵,并將相應的值作為表達式的值返回。例如,表達式 ${commands["dir"]} 返回與 commands 標識符所引用的 Map 中的 "dir" 鍵相關聯的值。

對于上述兩種情況,都可允許表達式出現在方括號中。對嵌套表達式求值的結果將被作為下標或鍵,用來檢索集合或數組的適當元素。和點運算符一樣,方括號運算符也可以遞歸應用。這使得 EL 能夠從多維數組、嵌套集合或兩者的任意組合中檢索元素。此外,點運算符和方括號運算符還可以互操作。例如,如果數組的元素本身是對象,則可以使用方括號運算符來檢索該數組的元素,并結合點運算符來檢索該元素的一個特性(例如 ${urls[3].protocol} )。

假定 EL 充當指定動態屬性值的簡化語言,EL 存取器有一個有趣的功能(與 Java 語言的存取器不同),那就是它們在應用于 null 時不拋出異常。如果應用 EL 存取器的對象(例如, ${foo.bar}${foo["bar"]} 中的 foo 標識符)是 null ,那么應用存取器的結果也是 null 。事實證明,在大多數情況下,這是一個相當有用的行為,不久您就會了解這一點。

最后,點運算符和方括號運算符可能實現某種程度的互換。例如,也可以使用 ${user["firstName"]} 來檢索 user 對象的 firstName 特性,正如可以用 ${commands.dir} 獲取與 commands 映射中的 "dir" 鍵相關聯的值一樣。


運算符

EL 還可以通過使用標識符和存取器,遍歷包含應用程序數據(通過限制了作用域的變量公開)或關于環境的信息(通過 EL 隱式對象)的對象層次結構。但是,只是訪問這些數據,通常不足以實現許多 JSP 應用程序所需的表示邏輯。

最終,EL 還包括了幾個用來操作和比較 EL 表達式所訪問數據的運算符。表 2 中匯總了這些運算符。

表 2. EL 運算符

類別 運算符
算術運算符 +-*/ (或 div )和 % (或 mod
關系運算符 == (或 eq )、 != (或 ne )、 < (或 lt )、 > (或 gt )、 <= (或 le )和 >= (或 ge
邏輯運算符 && (或 and )、 || (或 or )和 ! (或 not
驗證運算符 empty

算術運算符支持數值的加法、減法、乘法和除法。還提供了一個求余運算符。注:除法和求余運算符都有替代的、非符號的名稱(為的是與 XPath 保持一致)。清單 5 中顯示了一個演示算術運算符用法的示例表達式。對幾個 EL 表達式應用算術運算符的結果是將該算術運算符應用于這些表達式返回的數值所得的結果。



清單 5. 利用算術運算符的 EL 表達式
${item.price * (1 + taxRate[user.address.zipcode])}

關系運算符允許比較數字或文本數據。比較的結果作為布爾值返回。邏輯運算符允許合并布爾值,返回新的布爾值。因此,可以將 EL 邏輯運算符應用于嵌套的關系或邏輯運算符的結果,如清單 6 所示。



清單 6. 利用關系和邏輯運算符的 EL 表達式
${(x >= min) && (x <= max)}

最后一種 EL 運算符是 empty ,它對于驗證數據特別有用。 empty 運算符采用單個表達式作為其變量(也即, ${empty input} ),并返回一個布爾值,該布爾值表示對表達式求值的結果是不是“空”值。求值結果為 null 的表達式被認為是空,即無元素的集合或數組。如果參數是對長度為零的 String 求值所得的結果,則 empty 運算符也將返回 true

表 3 顯示了 EL 運算符的優先級。正如清單 5 和 6 所示,可以用圓括號對表達式分組,高于普通的優先級規則。

表 3. EL 運算符優先級(自頂到底,從左到右)

[] , .
()
unary -not!empty
*/div%mod
+ 、binary -
() <</code>><=>=ltgtlege
==!=eqne
&&and
||or

文字

在 EL 表達式中,數字、字符串、布爾值和 null 都可以被指定為文字值。字符串可以用單引號或雙引號定界。布爾值被指定為 truefalse


Taglib 偽指令

正如我們先前討論的,JSTL 1.0 包括四個定制標記庫。為了演示 JSTL 標記和表達式語言的交互,我們將研究幾個來自 JSTL core 庫的標記。和使用任何 JSP 定制標記庫一樣,必須在您想要使用這個庫標記的任何頁面中包括 taglib 偽指令。清單 7 顯示了用于這個特定庫的偽指令。



清單 7. 用于 JSTL core 庫 EL 版本的 taglib 偽指令
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>

實際上,對應于 JSTL core 庫的 taglib 偽指令有兩種,因為在 JSTL 1.0 中,EL 是可選的。所有四個 JSTL 1.0 定制標記庫都有使用 JSP 表達式(而不是 EL)指定動態屬性值的備用版本。因為這些備用庫依賴于 JSP 的更傳統的請求時屬性值,所以它們被稱為 RT庫,而那些使用表達式語言的則被稱為 EL 庫。開發人員用不同的 taglib 偽指令來區分每個庫的這兩個版本。清單 8 顯示了使用 core 庫的 RT 版本的偽指令。但是,由于現在我們討論的重點是 EL,所以首先需要這些偽指令。



清單 8. 用于 JSTL core 庫 RT 版本的 taglib 偽指令
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c_rt" %>

變量標記

我們首先要考慮的 JSTL 定制標記是 <c:set> 操作。正如已經說明的,限制了作用域的變量在 JSTL 中起關鍵作用, <c:set> 操作提供基于標記的機制來創建和設置限制了作用域的變量。清單 9 中顯示了該操作的語法,其中 var 屬性指定了限制了作用域的變量的名稱, scope 屬性表明了該變量駐留在哪個作用域中, value 屬性指定了分配給該變量的值。如果指定變量已經存在,則簡單地將所指明的值賦給它。如果不存在,則創建新的限制了作用域的變量,并用該值初始化這個變量。



清單 9. <c:set> 操作的語法
<c:set var="
        name" scope="
        scope" value="
        expression"/>
      

scope 屬性是可選的,其缺省值是 page

清單 10 中顯示了 <c:set> 的兩個示例。在第一個示例中,將會話作用域變量設置成 String 值。在第二個示例中,用表達式來設置數值:將頁面作用域內名為 square 的變量賦值為名為 x 的請求參數的值的平方。



清單 10. <c:set> 操作示例
<c:set var="timezone" scope="session" value="CST"/>
<c:set var="square" value="${param['x'] * param['x']}"/>

您還可以將限制了作用域的變量的值指定為 <c:set> 操作的主體內容,而不是使用屬性。使用這種方法,您可以重新編寫清單 10 中的第一個示例,如清單 11 所示。此外,正如我們馬上可以看到的, <c:set> 標記的主體內容本身也可以使用定制標記。 <c:set> 主體內生成的所有內容都將作為一個 String 值賦給指定變量。



清單 11. 通過主體內容指定 <c:set> 操作的值
<c:set var="timezone" scope="session">CST</c:set>

JSTL core 庫包含第二個用于管理限制了作用域的變量的標記 ― <c:remove> 。顧名思義, <c:remove> 操作是用來刪除限制了作用域的變量的,它獲取兩個屬性。 var 屬性指定待刪除變量的名稱, scope 屬性是可選的,它表示待刪除變量來自哪個作用域,缺省為 page ,如清單 12 所示。

清單 12. <c:remove> 操作示例
<c:remove var="timezone" scope="session"/>

輸出

盡管 <c:set> 操作允許將表達式結果賦給限制了作用域的變量,但開發人員通常會希望只顯示表達式的值,而不存儲它。JSTL <c:out> 定制標記承擔這一任務,其語法如清單 13 所示。該標記對由其 value 屬性指定的表達式進行求值,然后打印結果。如果指定了可選屬性 default ,那么,在對 value 屬性的表達式求值所得結果為 null 或空 String 的情況下, <c:out> 將打印其值。



清單 13. <c:out> 操作的語法
<c:out value="
        expression" default="
        expression" escapeXml="
        boolean"/>
      

escapeXml 屬性也是可選的。它控制當用 <c:out> 標記輸出諸如“<”、“>”和“&”之類的字符(在 HTML 和 XML 中具有特殊意義)時是否應該進行轉義。如果將 escapeXml 設置為 true,則會自動將這些字符轉換成相應的 XML 實體(此處提到的字符分別轉換成 <>& )。

例如,假定有一個名為 user 的會話作用域變量,它是一個類的實例,該類為用戶定義了兩個特性: usernamecompany 。每當用戶訪問站點時,這個對象被自動分配給會話,但直到用戶實際登錄后,才會設置這兩個特性。假定是這種方案,請考慮清單 14 中的 JSP 片段。在用戶登錄之后,這個片段將顯示單詞“Hello”,其后是他/她的用戶名和一個驚嘆號。但是,在用戶登錄之前,由這個片段生成的內容則是短語“Hello Guest!”。在這種情況下,因為 username 特性還有待初始化,所以 <c:out> 標記將轉而打印出 default 屬性的值(即字符串“Guest”)。



清單 14. 帶缺省內容的 <c:out> 操作示例
Hello <c:out value="${user.username}" default=="Guest"/>!

接下來,考慮清單 15,它使用了 <c:out> 標記的 escapeXml 屬性。如果在這種情況下已經將 company 特性設置成 Java String"Flynn & Sons" ,那么,實際上該操作生成的內容將是 Flynn & Sons 。如果這個操作是生成 HTML 或 XML 內容的 JSP 頁面的一部分,那么,這個字符串中間的“&”符號最終可能被解釋為 HTML 或 XML 控制字符,從而妨礙了對該內容的顯示或解析。但是,如果將 escapeXml 屬性值設置成 true ,則所生成的內容將是 Flynn & Sons 。瀏覽器或解析器不會因在解釋時遇到這種內容而出問題。假定 HTML 和 XML 是 JSP 應用程序中最常見的內容類型,所以 escapeXml 屬性的缺省值是 true 就不足為奇了。



清單 15. 禁用轉義的 <c:out> 操作示例
<c:out value="${user.company}" escapeXml=="false"/>




用缺省值設置變量

除了簡化動態數據的顯示之外,當通過 <c:set> 設置變量值時, <c:out> 指定缺省值的能力也很有用。正如 清單 11 所示,用來賦給限制了作用域的變量的值可以指定為 <c:set> 標記的主體內容,也可以通過其值屬性來指定。通過將 <c:out> 操作嵌套在 <c:set> 標記的主體內容中,變量賦值就可以利用其缺省值能力。

清單 16 中說明了這種方法。外部 <c:set> 標記的行為非常簡單:它根據其主體內容設置會話作用域 timezone 變量的值。但是,在這種情況下,主體內容是通過 <c:out> 操作生成的。這個嵌套操作的值屬性是表達式 ${cookie['tzPref'].value} ,它嘗試通過 cookie 隱式對象返回名為 tzPref 的 cookie 值。( cookie 隱式對象將 cookie 名稱映射到相應的 Cookie 實例,這意味著必須通過對象的 value 特性使用點運算符來檢索儲存在 cookie 中的實際數據。)



清單 16. 合并 <c:set> 和 <c:out> 以提供缺省變量值
<c:set var="timezone" scope=="session">
   <c:out value="${cookie['tzPref'].value}" default=="CST"/>
</c:set>

但是,請考慮以下情況,用戶是第一次嘗試使用這段代碼的 Web 應用程序。結果是,請求中沒有提供名為 tzPref 的 cookie。這意味著使用隱式對象的查找將返回 null ,在這種情況下整個表達式將返回 null 。因為對 <c:out> 標記的 value 屬性求值的結果是 null ,所以 <c:out> 標記會轉而輸出對其 default 屬性求值的結果。在這里是字符串 CST 。因此,實際的結果是將 timezone 限制了作用域的變量設置成用戶的 tzPref cookie 中存儲的時區,或者,如果沒有,則使用缺省時區 CST

EL 和 JSP 2.0

目前,表達式語言僅可用于指定 JSTL 定制標記中的動態屬性值。但 JSTL 1.0 表達式語言的一個擴展已經被提出,會把它包括到 JSP 2.0 中去,眼下正在進行最后評審。這個擴展將允許開發人員通過自己的定制標記來使用 EL。頁面作者將可以在目前允許使用 JSP 表達式的任何地方使用 EL 表達式,譬如將動態值插入模板文本中: <p>Your preferred time zone is ${timezone}</p>

這個 JSP 2.0 功能(就象 JSTL 本身一樣)將支持頁面作者進一步減少對 JSP 編制腳本元素的依賴,從而改進 JSP 應用程序的可維護性。


結束語

EL(與四個 JSTL 定制標記庫提供的操作結合起來)允許頁面作者不使用腳本元素即可實現表示層邏輯。例如,對比本文開頭 清單 1 中的 JSP 代碼和清單 17 中顯示的通過 JSTL 實現的同樣功能。(JSTL core 庫中其余的標記,包括 <c:choose> 及其子標記,將在本系列的下一篇文章中討論。)盡管顯然執行了條件邏輯,但是 JSTL 版本中沒有 Java 語言源代碼,并且標記之間的關系(尤其是關于嵌套需求)對于任何精通 HTML 語法的人都應該是熟悉的。



清單 17. 合并 <c:set> 和 <c:out> 以提供缺省變量值
<c:choose><c:when test="${user.role == 'member'}">
    <p>Welcome, member!</p>
  </c:when><c:otherwise>
    <p>Welcome, guest!</p>
  </c:otherwise></c:choose>

通過提供大多數 Web 應用程序常用功能的標準實現,JSTL 有助于加速開發周期。與 EL 結合起來,JSTL 可以不需要對表示層程序編寫代碼,這極大地簡化了 JSP 應用程序的維護。



參考資料



關于作者

Mark Kolb 是一名在德克薩斯州奧斯汀工作的軟件工程師。他經常就服務器端 Java 主題在業界發表演講,并且與人合著了 Web Development with JavaServer Pages,第二版一書。可通過 mak@taglib.com與 Mark 聯系。




liaojiyong 2007-03-21 12:15 發表評論
]]>
編碼問題(轉)http://m.tkk7.com/liaojiyong/archive/2006/12/10/86656.htmlliaojiyongliaojiyongSun, 10 Dec 2006 03:46:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/12/10/86656.htmlhttp://m.tkk7.com/liaojiyong/comments/86656.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/12/10/86656.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/86656.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/86656.html??? 情況是這樣的:我們現在使用的一個名為Notepad++的編輯器,它大體上和UltraEdit類似,是我從sourceforge上下載下載使用的。它有個格式的功能,簡單而言就是設置使用ascii編碼編輯文檔還是使用utf-8編碼,其實這里主要是對應非英文方面的字符才發生差異。
??? 如前那篇隨筆所述utf-8只是用于在數據傳輸時將字符串分割成字節傳輸出去,它對于unicode編碼有著嚴格的一一對應關系,而對于gb2312、gbk、gb18030之類的中文編碼是沒有對應關系的。gb2312、gbk、gb18030是在ascii編碼之上進行的中文編碼開發,所以它們是與ascii相兼容的,可以這么理解在我們的文本編輯器中ascii方式編輯的文本是以ascii碼保存,而以utf-8方式編輯的文本是以unicode編碼保存。
??? 現在,編碼的差異就在這里發生了,unicode中的英文部分編碼是與ascii相兼容的,具體而言是iso8859-1兼容,所以我們無論用什么編碼保存方式保存jsp文件,在網頁上顯示的都是正確的英文字符。但是對于中文這個保存的格式卻非常重要。在了解了jsp頁面的顯示過程我們就會明白其中的原因。
??? 現在回頭來看看jsp文件是經過了哪些步驟才在網頁上顯示出來的。
??? 1、首先我們用編輯器編輯jsp頁面代碼保存;
??? 2、web服務器對其進行編譯,將jsp頁面編譯成為一個java的class文件;
??? 3、web服務器將其class文件用傳輸格式在網絡上傳輸;
??? 4、客戶端瀏覽器解析代碼生成頁面。
??? 過程大致如上,能夠影響編碼方面的基本上在1、2、4方面,所以對其進行詳細講解。
??? 我們的編輯器是有一種默認的編碼保存方式的。這會到后面java編譯器是否將其中的中文進行轉換的問題。大家都知道java是以十六位的unicode編碼編譯、存儲java文件的。因為同一個漢字在unicode編碼和gb2312編碼中的位置是不一樣的,(具體而言就是這兩種編碼沒有兼容性,也不能通過公式換算出來,只能通過字符對應表進行轉換)
??? 上面指出的保存格式的重要性就在這里體現出來了,這也是很多人容易遺漏之處,當我們用默認格式位gbk的編輯器編輯jsp頁面,而在其頭部聲明charset=utf-8,那么返回的頁面會出現亂碼,而charset=gb2312則正常顯示。本人分析原因在于,在charset=utf-8的聲明有誤時,java編譯器會錯誤的認為現在保存的文件就是以unicode編碼格式保存的文件,所以在編譯中就不對其中的中文字符進行gb2312--unicode的轉化,而直接將其gb2312指向的編碼地址保存為unicode的編碼地址。反之,當編輯器的默認編輯保存格式為utf-8時,在jsp中聲明為gb2312,就會讓編譯器畫蛇添足的多進行一次中文編碼轉換,從而亂碼出現。
??? 綜上所述,編輯器的默認編碼方式是在解決中文亂碼方面是很重要的一個因素,當然我們可以用pageEncoding參數來說明文本編碼形式。弄清中文顯示后面的具體調用過程是徹底解決該類問題的終極之道!

posted on 2006-12-07 14:42 stme 閱讀(490) 評論(7) ?編輯?收藏引用收藏至365Key

評論

#?re: 編碼問題 2006-12-07 16:22 BeanSoft

不錯, 必須在頭部加入下列聲明:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="GBK"%>

要不然你就得用編輯器另存為的時候(例如記事本就有這個功能, 但是貌似有問題)轉換編碼為 UTF-8.

服務器看到 contentType="text/html; charset=UTF-8" 的頁面聲明的時候, 就認為下面的內容都是 UTF-8, 如果這時候不加上 pageEncoding, 出來的內容自然就是亂碼.

因此要么都用 GBK, 要么都用 UTF-8. 可以在 Eclipse 里進行設置讓所有的源文件的默認字符集都是 UTF-8. 如果要做開源項目, 強烈建議的是不要用 GBK 格式的編碼, 用 UTF-8 是上上策. 還有就是鼓勵英文注釋, 網頁內容國際化, 呵呵.??回復??

#?re: 編碼問題 2006-12-07 17:00 hahahehe

1、首先我們用編輯器編輯jsp頁面代碼保存;
2、web服務器對其進行編譯,將jsp頁面編譯成為一個java的class文件;
3、web服務器將其class文件用傳輸格式在網絡上傳輸;
4、客戶端瀏覽器解析代碼生成頁面。

非applet的情況下,服務器怎么可能把class傳輸到網絡上去,瀏覽器又怎么可能解析class?服務器傳輸的是html,class生成html的過程早在服務器端就已經完成。

亂碼的直接原因就在于服務器在頭里告訴瀏覽器的編碼 charset=UTF-8 和事實上生成html的編碼是不一致的??回復??

#?re: 編碼問題 2006-12-07 17:13 BeanSoft[匿名]

呵呵, 這段話的確是有點問題...

應該是:
1. 我們用代碼編輯器編輯了一個頁面(encoding=GBK)
2. Web 服務器調用編譯器將其編譯為 servlet(編譯器也會有個 encoding 選項, 一般都是跟著系統的 encoding 走, 頁面有 charset 的話就跟頁面的 charset 或者 pageEncoding 一致, 所以有時候不指定JSP編譯默認編碼, 不同的服務器出來的結果就不一樣), (encoding=GBK);
3. 生成 HTML, 這沒啥, 直接 write string;
4. 瀏覽器根據 head 里的 contentType (UTF-8)來判斷編碼, 或者根據頁面里的 meta 來判斷編碼
<meta http-equiv="content-type" content="text/html; charset=UTF-8">, 什么都沒的時候就當成西歐字符集, 具體哪個優先? 印象中是頁面里的 meta 優先, 這時候客戶需要手工選一個編碼才能看到正確的網頁.

所以只有這些條件都一致的時候, 才不會亂碼, 否則都會亂碼. 一般推薦全用 UTF-8(英文服務器環境) 或者 GBK(中文服務器).??回復??

#?re: 編碼問題 2006-12-08 00:27 weidagang2046

上次看你另一篇文章,對編碼分析得很詳細,很有幫助。再接再厲!??回復??

#?re: 編碼問題 2006-12-08 09:44 stme[匿名]

謝謝大家的點評,大家一起進步吧!??回復??

#?re: 編碼問題 2006-12-08 13:44 laoshi

>>utf-8只是用于在數據傳輸時將字符串分割成字節傳輸出去,它對于unicode編碼有著嚴格的一一對應關系,而對于gb2312、gbk、gb18030之類的中文編碼是沒有對應關系的。gb2312、gbk、gb18030是在ascii編碼之上進行的中文編碼開發,所以它們是與ascii相兼容的
gb18030也和unicode有著嚴格的一一對應關系,gb2312、gbk和unicode的子集有著嚴格的一一對應關系
UTF-8和gb18030都是所謂的外碼標準
“gb2312、gbk、gb18030是在ascii編碼之上進行的中文編碼開發,所以它們是與ascii相兼容的”是錯誤的,只能說UTF-8以及國標編碼的單字節編碼區和ascii的0~7F區的編碼是一致的。
Notepad++和UltraEdit的ascii其實并不是ascii,而是指Windows平臺的本地多字節字符集,一般咱們用的中文Windows上就是gbk??回復??

#?re: 編碼問題2006-12-08 16:13 luguoren

”gb18030也和unicode有著嚴格的一一對應關系,gb2312、gbk和unicode的子集有著嚴格的一一對應關系“
這個是對的嗎?本人好像沒有看到這樣的資料,gb18030只是一部分與unicode對應,不會是全部一一對應吧??回復??



liaojiyong 2006-12-10 11:46 發表評論
]]>
代碼生成工具MySQL2JSP 0.3 版本發布了http://m.tkk7.com/liaojiyong/archive/2006/08/24/65499.htmlliaojiyongliaojiyongThu, 24 Aug 2006 05:19:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/08/24/65499.htmlhttp://m.tkk7.com/liaojiyong/comments/65499.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/08/24/65499.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/65499.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/65499.html代碼生成工具MySQL2JSP?0.3 版本發布了。

MySQL2JSP是一個快速開發工具,可以讓你從Mysql中直接生成JSP、servlets,以及JavaBeans。并且會生成增加、刪除、修改、查看記錄等JSP頁面。使用JDBC。某種程度上,有點像是簡化的RAILS。
http://sourceforge.net/projects/db2jspgen/



liaojiyong 2006-08-24 13:19 發表評論
]]>
使用SiteMesh簡化網頁布局(轉)http://m.tkk7.com/liaojiyong/archive/2006/07/24/59819.htmlliaojiyongliaojiyongMon, 24 Jul 2006 07:25:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/07/24/59819.htmlhttp://m.tkk7.com/liaojiyong/comments/59819.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/07/24/59819.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/59819.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/59819.html閱讀全文

liaojiyong 2006-07-24 15:25 發表評論
]]>
JSP實現留言板的JavaBean類和分頁顯示的源碼http://m.tkk7.com/liaojiyong/archive/2006/07/21/59319.htmlliaojiyongliaojiyongThu, 20 Jul 2006 17:43:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/07/21/59319.htmlhttp://m.tkk7.com/liaojiyong/comments/59319.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/07/21/59319.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/59319.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/59319.html

//留言bean
package userinfobean;

/**
?* <p>Title: </p>
?* <p>Description: </p>
?* <p>Copyright: Copyright (c) 2004</p>
?* <p>Company: </p>
?* @author not attributable
?* @version 1.0
?*/

public class memoBean {

? private String id;//1.用戶名
? private String email;//2.密碼
? private String content;//3.留言?

? public memoBean() {
? }
? //1.用戶名
? public void setId(String s){
??? this.id=s;
? }
? //2.密碼
? public void setEmail(String s){
??? this.email=s;
? }
? //3.留言
?? public void setContent(String s){
??? this.content=s;
? }
?

? //1.用戶名
?? public String getId( ){
???? return this.id;
?? }
?? //2.密碼
?? public String getEmail(){
???? return this.email;
?? }
?? //3.留言?
?? public String getContent(){
???? return this.content;
?? }
}
//// 用于數據庫操作的bean??????????
package userinfobean;

import userinfobean.*;
import java.io.*;
import java.util.*;
import java.sql.*;


public class databaseBean{
? private String dbName;
? private String dbUser;
? private String dbPass;
???private Vector memoVector;

?? Connection connection;

? public databaseBean(){
??? dbName=new String("×××××××");
??? dbUser=new String("××××××");
??? dbPass=new String("×××××××");
??? String connectionUrl="jdbc:mysql://localhost/"+dbName;
??? try{
????? Class.forName("org.gjt.mm.mysql.Driver");
????? connection=DriverManager.getConnection(connectionUrl,dbUser,dbPass);
??? }
??? catch(Exception e){
????? System.out.println(e.toString());
????? }
? }

? public void dbQueryMemos(){
??? try{
?????? memoVector=new Vector();
????? Statement stmt=connection.createStatement();
????? ResultSet rs=stmt.executeQuery("select * from memo;");
????? while(rs.next()){
??????? memoBean temp=new memoBean();
??????? temp.setId(rs.getString(2));
??????? temp.setEmail(rs.getString(3));
??????? temp.setContent(rs.getString(4));
??????? memoVector.add(temp);
????? }

?? }
?? catch(SQLException e){
???? System.out.println(e.toString());
?? }

?

? }

??? public void setDbName(String s){
??? this.dbName=s;
? }
? public void setDbUser(String s){
??? this.dbUser=s;
? }
? public void setDbPass(String s){
??? this.dbPass=s;
? }

? public String getDbName(){
??? return this.dbName;
? }
? public String getDbUser(){
??? return this.dbUser;
? }
? public String getDbPass(){
??? return this.dbPass;
? }
??? public Vector getMemoVector(){
??? return this.memoVector;

? }

}

///以下是網頁源碼

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page language="java" import="java.util.*" %>
<%@ page? import="java.text.*" %>
<%@ page? import="userinfobean.*" %>
<jsp:useBean id="memoInfo" scope="page" class="userinfobean.memoBean"/>
<jsp:useBean id="memoQuery" scope="page" class="userinfobean.databaseBean"/>
<%!
int totalPages;
int countPerPage=3;
int totalCount;
int currentPage=0;
int currentStart;
%>
<%
memoQuery.dbQueryMemos();
Vector vt=memoQuery.getMemoVector();
totalCount=vt.size();
totalPages=totalCount/countPerPage;
if(totalCount%countPerPage>0)totalPages=totalPages+1;
if(request.getParameter("page")==null)
{
currentPage=1;
}
else
{
currentPage=Integer.parseInt(request.getParameter("page"));
if(currentPage>totalPages)currentPage=totalPages;
if(currentPage<1)currentPage=1;
}
currentStart=totalCount-1-countPerPage * (currentPage-1);
%>
<html>
<head>
<title>NetGreen</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
? <link rel="stylesheet" href="main.css">????????????????????????????
</head>
<script language="JavaScript" >
function page_check()
{
if(document.pageForm.page.value=="")
{alert("請輸入頁數");
document.pageForm.page.focus();
return false;
}
return true;
}</script>
<body bgcolor="ffffff"? >

<center>
? <table width="800" border="0" height="622">
??? <tr>
????? <td height="71" align="center" valign="middle"> <table width="100%" border="1" cellpadding="0" cellspacing="0" bordercolor="#0099CC">
????????? <tr>
??????????? <td width="19%" height="60" align="left" valign="bottom" bgcolor="#FFFFFF"><b><font size="4" color="#00cc66" ><img src="image/logo.gif" width="156" height="60"></font></b></td>
??????????? <td width="57%" align="left" valign="bottom" bgcolor="#FFFFFF"><img src="image/banner.gif" width="100%" height="60"></td>
??????????? <td width="24%" align="left" valign="bottom"> <table width="102%" height="60" border="1" cellpadding="0" cellspacing="0" bordercolor="#0099CC" bgcolor="#FFFFFF">
??????????????? <tr>
????????????????? <td width="50%" height="27" align="center">上 傳</td>
????????????????? <td width="50%" align="center">收藏本頁</td>
??????????????? </tr>
??????????????? <tr>
????????????????? <td height="21" align="center" bgcolor="#FFFFFF" >退 出 </td>
????????????????? <td align="center"><a href="memoForm.htm" target="_self">我要留言</a></td>
??????????????? </tr>
????????????? </table></td>
????????? </tr>
??????? </table></td>
??? </tr>
??? <tr>
????? <td height="31"><table width="100%" border="1" bordercolor="#0099CC">
????????? <tr bgcolor="#00CC66">
??????????? <td width="12%" height="23" align="center" bgcolor="#CCFFFF"><a href="index.htm" >首頁</a></td>
??????????? <td width="12%" align="center" bgcolor="#CCFFFF"><a href="loginForm.htm" >登陸</a></td>
??????????? <td width="12%" align="center" bgcolor="#CCFFFF">&nbsp;</td>
??????????? <td width="12%" align="center" bgcolor="#CCFFFF">&nbsp;</td>
??????????? <td width="12%" align="center" bgcolor="#CCFFFF" >&nbsp; </td>
??????????? <td width="12%" align="center" bgcolor="#CCFFFF">&nbsp;</td>
??????????? <td width="12%" align="center" bgcolor="#CCFFFF"><a href="memoDisplay.jsp" >留言板</a></td>
??????????? <td width="12%" align="center"? bgcolor="#CCFFFF"><a href="registForm.htm">注冊</a></td>
????????? </tr>
??????? </table></td>
??? </tr>
??? <tr>
????? <td height="435" align="center" valign="top" nowrap>
??????? <table width="100%"? border="1" cellpadding="0" cellspacing="0" bordercolor="#0099CC">
????????? <tr>
??????????? <td width="18%" height="431" align="center" valign="middle" bgcolor="#CCFFFF">
??????????? </td>
??????????? <td width="83%" align="center" valign="top" bgcolor="#FFFFFF"><table width="100%" border="1"><form name="pageForm" method="post"? onSubmit="return page_check();"action="memoDisplay.jsp">
??????????????? <tr>
????????????????? <td width="36%">共有留言<%out.print(totalPages);%> 頁,現在是第<%out.print(currentPage);%>? 頁</td>
??????????????????? <td width="64%">
?????<a href="memoDisplay.jsp?page=<%=currentPage-1%>" >上一頁 </a>
?????<a href="memoDisplay.jsp?page=<%=currentPage+1%>" >下一頁? </a>
?????<a href="memoDisplay.jsp?page=1" >首頁 </a>
????? <a href="memoDisplay.jsp?page=<%=totalPages%>" >尾頁? </a>
????? <input type="submit" name="Submit" value="G0">
????????????????????? <input name="page" type="text" size="10">
????????????????????? 頁</td>
??????????????? </tr>
????????????? </form></table>
????????????? <%
????
????for(int i=0;i<countPerPage&&currentStart-i>=0;i++)
????{
?????memoInfo.setId(((memoBean)vt.get(currentStart-i)).getId());
?????memoInfo.setEmail(((memoBean)vt.get(currentStart-i)).getEmail());
?????memoInfo.setContent(((memoBean)vt.get(currentStart-i)).getContent());
?????
?????
?????
???out.print("<table width='100%' border='1' ><tr><td width='19%'>");
???out.print(memoInfo.getId());
???out.print("</td><td width='81%'>");
???out.print(memoInfo.getEmail());
???out.print("</td></tr><tr><td height='44' colspan='2'>");
???out.print(memoInfo.getContent());
???out.print("</td></tr></table> ");
???out.print("<br>");
?????
????}
??
???%>
???</td>
????????? </tr>
??????? </table>
????? </td>
??? </tr>
??? <tr>
????? <td height="73" align="center" valign="top" nowrap>
??????? <table width="100%" border="0">
????????? <tr>
??????????? <td height="21" align="center" bgcolor="#FFFFFF"><hr width="85%" noshade? color="#00CC66"></td>
????????? </tr>
????????? <tr>
??????????? <td height="12" align="center">Copyright @ HomeLee. All rights reserved.
??????????? </td>
????????? </tr>
????????? <tr>
??????????? <td height="12" align="center" bgcolor="#FFFFFF">&nbsp;</td>
????????? </tr>
??????? </table></td>
??? </tr>
? </table>
? </center></body>
</html>



liaojiyong 2006-07-21 01:43 發表評論
]]>
上傳下載全攻略jspSmartUpload(轉)http://m.tkk7.com/liaojiyong/archive/2006/07/03/56388.htmlliaojiyongliaojiyongMon, 03 Jul 2006 09:24:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/07/03/56388.htmlhttp://m.tkk7.com/liaojiyong/comments/56388.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/07/03/56388.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/56388.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/56388.html一、安裝篇?

  jspSmartUpload是由www.jspsmart.com網站開發的一個可免費使用的全功能的文件上傳下載組件,適于嵌入執行上傳下載操作的JSP文件中。該組件有以下幾個特點:?

1、使用簡單。在JSP文件中僅僅書寫三五行JAVA代碼就可以搞定文件的上傳或下載,方便。?

2、能全程控制上傳。利用jspSmartUpload組件提供的對象及其操作方法,可以獲得全部上傳文件的信息(包括文件名,大小,類型,擴展名,文件數據等),方便存取。?

3、能對上傳的文件在大小、類型等方面做出限制。如此可以濾掉不符合要求的文件。?

4、下載靈活。僅寫兩行代碼,就能把Web服務器變成文件服務器。不管文件在Web服務器的目錄下或在其它任何目錄下,都可以利用jspSmartUpload進行下載。?

5、能將文件上傳到數據庫中,也能將數據庫中的數據下載下來。這種功能針對的是MYSQL數據庫,因為不具有通用性,所以本文不準備舉例介紹這種用法。?

  jspSmartUpload組件可以從www.jspsmart.com網站上自由下載,壓縮包的名字是jspSmartUpload.zip。下載后,用WinZip或WinRAR將其解壓到Tomcat的webapps目錄下(本文以Tomcat服務器為例進行介紹)。解壓后,將webapps/jspsmartupload目錄下的子目錄Web-inf名字改為全大寫的WEB-INF,這樣一改jspSmartUpload類才能使用。因為Tomcat對文件名大小寫敏感,它要求Web應用程序相關的類所在目錄為WEB-INF,且必須是大寫。接著重新啟動Tomcat,這樣就可以在JSP文件中使用jspSmartUpload組件了。?

  注意,按上述方法安裝后,只有webapps/jspsmartupload目錄下的程序可以使用jspSmartUpload組件,如果想讓Tomcat服務器的所有Web應用程序都能用它,必須做如下工作:?

1.進入命令行狀態,將目錄切換到Tomcat的webapps/jspsmartupload/WEB-INF目錄下。?

2.運行JAR打包命令:jar?cvf?jspSmartUpload.jar?com?

(也可以打開資源管理器,切換到當前目錄,用WinZip將com目錄下的所有文件壓縮成jspSmartUpload.zip,然后將jspSmartUpload.zip換名為jspSmartUpload.jar文件即可。)?

3.將jspSmartUpload.jar拷貝到Tomcat的shared/lib目錄下。?

二、相關類說明篇?

㈠?File類?

  這個類包裝了一個上傳文件的所有信息。通過它,可以得到上傳文件的文件名、文件大小、擴展名、文件數據等信息。?

  File類主要提供以下方法:?

1、saveAs作用:將文件換名另存。?

原型:?

public?void?saveAs(java.lang.String?destFilePathName)?

或?

public?void?saveAs(java.lang.String?destFilePathName,?int?optionSaveAs)?

其中,destFilePathName是另存的文件名,optionSaveAs是另存的選項,該選項有三個值,分別是SAVEAS_PHYSICAL,SAVEAS_VIRTUAL,SAVEAS_AUTO。SAVEAS_PHYSICAL表明以操作系統的根目錄為文件根目錄另存文件,SAVEAS_VIRTUAL表明以Web應用程序的根目錄為文件根目錄另存文件,SAVEAS_AUTO則表示讓組件決定,當Web應用程序的根目錄存在另存文件的目錄時,它會選擇SAVEAS_VIRTUAL,否則會選擇SAVEAS_PHYSICAL。?

例如,saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)執行后若Web服務器安裝在C盤,則另存的文件名實際是c:\upload\sample.zip。而saveAs("/upload/sample.zip",SAVEAS_VIRTUAL)執行后若Web應用程序的根目錄是webapps/jspsmartupload,則另存的文件名實際是webapps/
建議:對于Web程序的開發來說,最好使用SAVEAS_VIRTUAL,以便移植。?

2、isMissing?

作用:這個方法用于判斷用戶是否選擇了文件,也即對應的表單項是否有值。選擇了文件時,它返回false。未選文件時,它返回true。?

原型:public?boolean?isMissing()?

3、getFieldName?

作用:取HTML表單中對應于此上傳文件的表單項的名字。?

原型:public?String?getFieldName()?

4、getFileName?

作用:取文件名(不含目錄信息)?

原型:public?String?getFileName()?

5、getFilePathName?

作用:取文件全名(帶目錄)?

原型:public?String?getFilePathName?

6、getFileExt?

作用:取文件擴展名(后綴)?

原型:public?String?getFileExt()?

7、getSize?

作用:取文件長度(以字節計)?

原型:public?int?getSize()?

8、getBinaryData?

作用:取文件數據中指定位移處的一個字節,用于檢測文件等處理。?

原型:public?byte?getBinaryData(int?index)。其中,index表示位移,其值在0到getSize()-1之間。?

㈡?Files類?

  這個類表示所有上傳文件的集合,通過它可以得到上傳文件的數目、大小等信息。有以下方法:?

1、getCount?

作用:取得上傳文件的數目。?

原型:public?int?getCount()?

2、getFile?

作用:取得指定位移處的文件對象File(這是com.
jspsmart.upload.File,不是java.io.File,注意區分)。?

原型:public?File?getFile(int?index)。其中,index為指定位移,其值在0到getCount()-1之間。?

3、getSize?

作用:取得上傳文件的總長度,可用于限制一次性上傳的數據量大小。?

原型:public?long?getSize()?

4、getCollection?

作用:將所有上傳文件對象以Collection的形式返回,以便其它應用程序引用,瀏覽上傳文件信息。?

原型:public?Collection?getCollection()?

5、getEnumeration?

作用:將所有上傳文件對象以Enumeration(枚舉)的形式返回,以便其它應用程序瀏覽上傳文件信息。?

原型:public?Enumeration?getEnumeration()?

㈢?Request類?

  這個類的功能等同于JSP內置的對象request。只所以提供這個類,是因為對于文件上傳表單,通過request對象無法獲得表單項的值,必須通過jspSmartUpload組件提供的Request對象來獲取。該類提供如下方法:?

1、getParameter?

作用:獲取指定參數之值。當參數不存在時,返回值為null。?

原型:public?String?getParameter(String?name)。其中,name為參數的名字。?

2、getParameterValues?

作用:當一個參數可以有多個值時,用此方法來取其值。它返回的是一個字符串數組。當參數不存在時,返回值為null。?

原型:public?String[]?getParameterValues(String?name)。其中,name為參數的名字。?

3、getParameterNames?

作用:取得Request對象中所有參數的名字,用于遍歷所有參數。它返回的是一個枚舉型的對象。?

原型:public?Enumeration?getParameterNames()?

㈣?SmartUpload類這個類完成上傳下載工作。?

A.上傳與下載共用的方法:?

只有一個:initialize。?

作用:執行上傳下載的初始化工作,必須第一個執行。?

原型:有多個,主要使用下面這個:?

public?final?void?initialize(javax.servlet.jsp.PageContext?pageContext)?

其中,pageContext為JSP頁面內置對象(頁面上下文)。?

B.上傳文件使用的方法:?

1、upload?

作用:上傳文件數據。對于上傳操作,第一步執行initialize方法,第二步就要執行這個方法。?

原型:public?void?upload()?

2、save?

作用:將全部上傳文件保存到指定目錄下,并返回保存的文件個數。?

原型:public?int?save(String?destPathName)?

和public?int?save(String?destPathName,int?option)?

其中,destPathName為文件保存目錄,option為保存選項,它有三個值,分別是SAVE_PHYSICAL,SAVE_VIRTUAL和SAVE_AUTO。(同File類的saveAs方法的選項之值類似)SAVE_PHYSICAL指示組件將文件保存到以操作系統根目錄為文件根目錄的目錄下,SAVE_VIRTUAL指示組件將文件保存到以Web應用程序根目錄為文件根目錄的目錄下,而SAVE_AUTO則表示由組件自動選擇。?

注:save(destPathName)作用等同于save(destPathName,SAVE_AUTO)。?

3、getSize?

作用:取上傳文件數據的總長度?

原型:public?int?getSize()?

4、getFiles?

作用:取全部上傳文件,以Files對象形式返回,可以利用Files類的操作方法來獲得上傳文件的數目等信息。?

原型:public?Files?getFiles()?

5、getRequest?

作用:取得Request對象,以便由此對象獲得上傳表單參數之值。?

原型:public?Request?getRequest()?

6、setAllowedFilesList?

作用:設定允許上傳帶有指定擴展名的文件,當上傳過程中有文件名不允許時,組件將拋出異常。?

原型:public?void?setAllowedFilesList(String?allowedFilesList)?

其中,allowedFilesList為允許上傳的文件擴展名列表,各個擴展名之間以逗號分隔。如果想允許上傳那些沒有擴展名的文件,可以用兩個逗號表示。例如:setAllowedFilesList("doc,txt,,")將允許上傳帶doc和txt擴展名的文件以及沒有擴展名的文件。?

7、setDeniedFilesList?

作用:用于限制上傳那些帶有指定擴展名的文件。若有文件擴展名被限制,則上傳時組件將拋出異常。?

原型:public?void?setDeniedFilesList(String?deniedFilesList)?

其中,deniedFilesList為禁止上傳的文件擴展名列表,各個擴展名之間以逗號分隔。如果想禁止上傳那些沒有擴展名的文件,可以用兩個逗號來表示。例如:setDeniedFilesList("exe,bat,,")將禁止上傳帶exe和bat擴展名的文件以及沒有擴展名的文件。?

8、setMaxFileSize?

作用:設定每個文件允許上傳的最大長度。?

原型:public?void?setMaxFileSize(long?maxFileSize)?

其中,maxFileSize為為每個文件允許上傳的最大長度,當文件超出此長度時,將不被上傳。?

9、setTotalMaxFileSize?

作用:設定允許上傳的文件的總長度,用于限制一次性上傳的數據量大小。?

原型:public?void?setTotalMaxFileSize(long?totalMaxFileSize)?

其中,totalMaxFileSize為允許上傳的文件的總長度。



1、setContentDisposition?

作用:將數據追加到MIME文件頭的CONTENT-DISPOSITION域。jspSmartUpload組件會在返回下載的信息時自動填寫MIME文件頭的CONTENT-DISPOSITION域,如果用戶需要添加額外信息,請用此方法。?

原型:public?void?setContentDisposition(String?contentDisposition)?

其中,contentDisposition為要添加的數據。如果contentDisposition為null,則組件將自動添加"attachment;",以表明將下載的文件作為附件,結果是IE瀏覽器將會提示另存文件,而不是自動打開這個文件(IE瀏覽器一般根據下載的文件擴展名決定執行什么操作,擴展名為doc的將用word程序打開,擴展名為pdf的將用acrobat程序打開,等等)。?

2、downloadFile?

作用:下載文件。?

原型:共有以下三個原型可用,第一個最常用,后兩個用于特殊情況下的文件下載(如更改內容類型,更改另存的文件名)。?

①?public?void?downloadFile(String?sourceFilePathName)?

其中,sourceFilePathName為要下載的文件名(帶目錄的文件全名)?

②?public?void?downloadFile(String?sourceFilePathName,String?contentType)?

其中,sourceFilePathName為要下載的文件名(帶目錄的文件全名),contentType為內容類型(MIME格式的文件類型信息,可被瀏覽器識別)。?

③?public?void?downloadFile(String?sourceFilePathName,String?contentType,String?destFileName)?

其中,sourceFilePathName為要下載的文件名(帶目錄的文件全名),contentType為內容類型(MIME格式的文件類型信息,可被瀏覽器識別),destFileName為下載后默認的另存文件名。?

三、文件上傳篇?

㈠?表單要求?

對于上傳文件的FORM表單,有兩個要求:?

1、METHOD應用POST,即METHOD="POST"。?

2、增加屬性:ENCTYPE="multipart/form-data"?

下面是一個用于上傳文件的FORM表單的例子:?



<FORM?METHOD="POST"?ENCTYPE="multipart/form-data"?
ACTION="/jspSmartUpload/upload.<INPUT?TYPE="FILE"?NAME="MYFILE">
<INPUT?TYPE="SUBMIT">
</FORM>
?


㈡?上傳的例子?

1、上傳頁面upload.html?

本頁面提供表單,讓用戶選擇要上傳的文件,點擊"上傳"按鈕執行上傳操作。?

頁面源碼如下:?

<!--
????文件名:upload.html
作??者:縱橫軟件制作中心雨亦奇(zhsoft88@sohu.com)
-->
<!DOCTYPE?HTML?PUBLIC?"-//W3C//DTD?HTML?4.01?Transitional//EN">
<html>
<head>
<title>文件上傳</title>
<meta?http-equiv="Content-Type"?content="text/html;?charset=gb2312">
</head>

<body>
<p>?</p>
<p?align="center">上傳文件選擇</p>
<FORM?METHOD="POST"?ACTION="
jsp/do_upload.ENCTYPE="multipart/form-data">
<input?type="hidden"?name="TEST"?value="good">
??<table?width="75%"?border="1"?align="center">
????<tr>?
??????<td><div?align="center">1、?
??????????<input?type="FILE"?name="FILE1"?size="30">
????????</div></td>
????</tr>
????<tr>?
??????<td><div?align="center">2、?
??????????<input?type="FILE"?name="FILE2"?size="30">
????????</div></td>
????</tr>
????<tr>?
??????<td><div?align="center">3、?
??????????<input?type="FILE"?name="FILE3"?size="30">
????????</div></td>
????</tr>
????<tr>?
??????<td><div?align="center">4、?
??????????<input?type="FILE"?name="FILE4"?size="30">
????????</div></td>
????</tr>
????<tr>?
??????<td><div?align="center">
??????????<input?type="submit"?name="Submit"?value="上傳它!">
????????</div></td>
????</tr>
??</table>
</FORM>
</body>
</html>
?


2、上傳處理頁面do_upload.
jsp?

本頁面執行文件上傳操作。頁面源碼中詳細介紹了上傳方法的用法,在此不贅述了。?

頁面源碼如下:?

<%--
文件名:do_upload.jsp
作??者:縱橫軟件制作中心雨亦奇(zhsoft88@sohu.com)
--%>
<%@?page?contentType="text/html;?charset=gb2312"?language="java"?
import="java.util.*,com.<html>
<head>
<title>文件上傳處理頁面</title>
<meta?http-equiv="Content-Type"?content="text/html;?charset=gb2312">
</head>

<body>
<%
//?新建一個SmartUpload對象
SmartUpload?su?=?new?SmartUpload();
//?上傳初始化
su.initialize(pageContext);
//?設定上傳限制
//?1.限制每個上傳文件的最大長度。
//?su.setMaxFileSize(10000);
//?2.限制總上傳數據的長度。
//?su.setTotalMaxFileSize(20000);
//?3.設定允許上傳的文件(通過擴展名限制),僅允許doc,txt文件。
//?su.setAllowedFilesList("doc,txt");
//?4.設定禁止上傳的文件(通過擴展名限制),禁止上傳帶有exe,bat,
jsp,htm,html擴展名的文件和沒有擴展名的文件。
//?su.setDeniedFilesList("exe,bat,//?上傳文件
su.upload();
//?將上傳文件全部保存到指定目錄
int?count?=?su.save("/upload");
out.println(count+"個文件上傳成功!<br>");

//?利用Request對象獲取參數之值
out.println("TEST="+su.getRequest().getParameter("TEST")
+"<BR><BR>");

//?逐一提取上傳文件信息,同時可保存文件。
for?(int?i=0;i<su.getFiles().getCount();i++)
{
com.
jspsmart.upload.File?file?=?su.getFiles().getFile(i);

//?若文件不存在則繼續
if?(file.isMissing())?continue;

//?顯示當前文件信息
out.println("<TABLE?BORDER=1>");
out.println("<TR><TD>表單項名(FieldName)</TD><TD>"
+?file.getFieldName()?+?"</TD></TR>");
out.println("<TR><TD>文件長度(Size)</TD><TD>"?+?
file.getSize()?+?"</TD></TR>");
out.println("<TR><TD>文件名(FileName)</TD><TD>"?
+?file.getFileName()?+?"</TD></TR>");
out.println("<TR><TD>文件擴展名(FileExt)</TD><TD>"?
+?file.getFileExt()?+?"</TD></TR>");
out.println("<TR><TD>文件全名(FilePathName)</TD><TD>"
+?file.getFilePathName()?+?"</TD></TR>");
out.println("</TABLE><BR>");

//?將文件另存
//?file.saveAs("/upload/"?+?myFile.getFileName());
//?另存到以WEB應用程序的根目錄為文件根目錄的目錄下
//?file.saveAs("/upload/"?+?myFile.getFileName(),?
su.SAVE_VIRTUAL);
//?另存到操作系統的根目錄為文件根目錄的目錄下
//?file.saveAs("c:\\temp\\"?+?myFile.getFileName(),?
su.SAVE_PHYSICAL);

}
%>
</body>
</html>
?


四、文件下載篇?

1、下載鏈接頁面download.html?

頁面源碼如下:?

<!--
文件名:download.html
作??者:縱橫軟件制作中心雨亦奇(zhsoft88@sohu.com)
-->
<!DOCTYPE?HTML?PUBLIC?"-//W3C//DTD?HTML?4.01?Transitional//EN">
<html>
<head>
<title>下載</title>
<meta?http-equiv="Content-Type"?content="text/html;?charset=gb2312">
</head>
<body>
<a?href="jsp/do_download.</body>
</html>
?


2、下載處理頁面do_download.
jsp?do_download.jsp展示了如何利用jspSmartUpload組件來下載文件,從下面的源碼中就可以看到,下載何其簡單。?

源碼如下:?

<%@?page?contentType="text/html;charset=gb2312"?
import="com.//?新建一個SmartUpload對象
SmartUpload?su?=?new?SmartUpload();
//?初始化
su.initialize(pageContext);
//?設定contentDisposition為null以禁止瀏覽器自動打開文件,
//保證點擊鏈接后是下載文件。若不設定,則下載的文件擴展名為
//doc時,瀏覽器將自動用word打開它。擴展名為pdf時,
//瀏覽器將用acrobat打開。
su.setContentDisposition(null);
//?下載文件
su.downloadFile("/upload/如何賺取我的第一桶金.doc");
%>
?


注意,執行下載的頁面,在Java腳本范圍外(即<%?...?%>之外),不要包含HTML代碼、空格、回車或換行等字符,有的話將不能正確下載。不信的話,可以在上述源碼中%><%之間加入一個換行符,再下載一下,保證出錯。因為它影響了返回給瀏覽器的數據流,導致解析出錯。?

3、如何下載中文文件?

jspSmartUpload雖然能下載文件,但對中文支持不足。若下載的文件名中有漢字,則瀏覽器在提示另存的文件名時,顯示的是一堆亂碼,很掃人興。上面的例子就是這樣。(這個問題也是眾多下載組件所存在的問題,很少有人解決,搜索不到相關資料,可嘆!)?

為了給jspSmartUpload組件增加下載中文文件的支持,我對該組件進行了研究,發現對返回給瀏覽器的另存文件名進行UTF-8編碼后,瀏覽器便能正確顯示中文名字了。這是一個令人高興的發現。于是我對jspSmartUpload組件的SmartUpload類做了升級處理,增加了toUtf8String這個方法,改動部分源碼如下:?

public?void?downloadFile(String?s,?String?s1,?String?s2,?int?i)
throws?ServletException,?IOException,?SmartUploadException
????{
if(s?==?null)
????throw?new?IllegalArgumentException("File?''"?+?s?+
????"''?not?found?(1040).");
if(s.equals(""))
????throw?new?IllegalArgumentException("File?''"?+?s?+
????"''?not?found?(1040).");
if(!isVirtual(s)?&&?m_denyPhysicalPath)
????throw?new?SecurityException("Physical?path?is
????denied?(1035).");
if(isVirtual(s))
????s?=?m_application.getRealPath(s);
java.io.File?file?=?new?java.io.File(s);
FileInputStream?fileinputstream?=?new?FileInputStream(file);
long?l?=?file.length();
boolean?flag?=?false;
int?k?=?0;
byte?abyte0[]?=?new?byte[i];
if(s1?==?null)
????m_response.setContentType("application/x-msdownload");
else
if(s1.length()?==?0)
????m_response.setContentType("application/x-msdownload");
else
????m_response.setContentType(s1);
m_response.setContentLength((int)l);
m_contentDisposition?=?m_contentDisposition?!=?null??
m_contentDisposition?:?"attachment;";
if(s2?==?null)
????m_response.setHeader("Content-Disposition",?
????m_contentDisposition?+?"?filename="?+?
????toUtf8String(getFileName(s)));
else
if(s2.length()?==?0)
????m_response.setHeader("Content-Disposition",?
????m_contentDisposition);
else
????m_response.setHeader("Content-Disposition",?
????m_contentDisposition?+?"?filename="?+?toUtf8String(s2));
while((long)k?<?l)
{
????int?j?=?fileinputstream.read(abyte0,?0,?i);
????k?+=?j;
????m_response.getOutputStream().write(abyte0,?0,?j);
}
fileinputstream.close();
????}

????/**
?????*?將文件名中的漢字轉為UTF8編碼的串,以便下載時能正確顯示另存的文件名.
?????*?縱橫軟件制作中心雨亦奇2003.08.01
?????*?@param?s?原文件名
?????*?@return?重新編碼后的文件名
?????*/
????public?static?String?toUtf8String(String?s)?{
StringBuffer?sb?=?new?StringBuffer();
for?(int?i=0;i<s.length();i++)?{
????char?c?=?s.charAt(i);
????if?(c?>=?0?&&?c?<=?255)?{
sb.append(c);
????}?else?{
byte[]?b;
try?{
????b?=?Character.toString(c).getBytes("utf-8");
}?catch?(Exception?ex)?{
????System.out.println(ex);
????b?=?new?byte[0];
}
for?(int?j?=?0;?j?<?b.length;?j++)?{
????int?k?=?b[j];
????if?(k?<?0)?k?+=?256;
????sb.append("%"?+?Integer.toHexString(k).
????toUpperCase());
}
????}
}
return?sb.toString();
????}
?


注意源碼中粗體部分,原jspSmartUpload組件對返回的文件未作任何處理,現在做了編碼的轉換工作,將文件名轉換為UTF-8形式的編碼形式。UTF-8編碼對英文未作任何處理,對中文則需要轉換為%XX的形式。toUtf8String方法中,直接利用Java語言提供的編碼轉換方法獲得漢字字符的UTF-8編碼,之后將其轉換為%XX的形式。?

將源碼編譯后打包成jspSmartUpload.jar,拷貝到Tomcat的shared/lib目錄下(可為所有WEB應用程序所共享),然后重啟Tomcat服務器就可以正常下載含有中文名字的文件了。另,toUtf8String方法也可用于轉換含有中文的超級鏈接,以保證鏈接的有效,因為有的WEB服務器不支持中文鏈接。?

小結:jspSmartUpload組件是應用JSP進行B/S程序開發過程中經常使用的上傳下載組件,它使用簡單,方便。現在我又為其加上了下載中文名字的文件的支持,真個是如虎添翼,必將贏得更多開發者的青睞。




smartupload中文亂碼文題
smartupload存在字符編碼的問題,下面是處理下載時使用中文文件名的處理方法(轉貼),上傳可以采用類似的方法:
jspSmartUpload雖然能下載文件,但對中文支持不足。若下載的文件名中有漢字,則瀏覽器在提示另存的文件名時,顯示的是一堆亂碼,很掃人興。上面的例子就是這樣。(這個問題也是眾多下載組件所存在的問題,很少有人解決,搜索不到相關資料,可嘆!)

為了給jspSmartUpload組件增加下載中文文件的支持,我對該組件進行了研究,發現對返回給瀏覽器的另存文件名進行UTF-8編碼后,瀏覽器便能正確顯示中文名字了。這是一個令人高興的發現。于是我對jspSmartUpload組件的SmartUpload類做了升級處理,增加了toUtf8String這個方法,改動部分源碼如下:

public void downloadFile(String s, String s1, String s2, int i)
throws ServletException, IOException, SmartUploadException
??? {
if(s == null)
??? throw new IllegalArgumentException("File ''''" + s +
??? "'''' not found (1040).");
if(s.equals(""))
??? throw new IllegalArgumentException("File ''''" + s +
??? "'''' not found (1040).");
if(!isVirtual(s) && m_denyPhysicalPath)
??? throw new SecurityException("Physical path is
??? denied (1035).");
if(isVirtual(s))
??? s = m_application.getRealPath(s);
java.io.File file = new java.io.File(s);
FileInputStream fileinputstream = new FileInputStream(file);
long l = file.length();
boolean flag = false;
int k = 0;
byte abyte0[] = new byte;
if(s1 == null)
??? m_response.setContentType("application/x-msdownload");
else
if(s1.length() == 0)
??? m_response.setContentType("application/x-msdownload");
else
??? m_response.setContentType(s1);
m_response.setContentLength((int)l);
m_contentDisposition = m_contentDisposition != null ?
m_contentDisposition : "attachment;";
if(s2 == null)
??? m_response.setHeader("Content-Disposition",
??? m_contentDisposition + " filename=" +
??? toUtf8String(getFileName(s)));
else
if(s2.length() == 0)
??? m_response.setHeader("Content-Disposition",
??? m_contentDisposition);
else
??? m_response.setHeader("Content-Disposition",
??? m_contentDisposition + " filename=" + toUtf8String(s2));
while((long)k < l)
{
??? int j = fileinputstream.read(abyte0, 0, i);
??? k += j;
??? m_response.getOutputStream().write(abyte0, 0, j);
}
fileinputstream.close();
??? }

??? /**
???? * 將文件名中的漢字轉為UTF8編碼的串,以便下載時能正確顯示另存的文件名.
???? * 縱橫軟件制作中心雨亦奇2003.08.01
???? * @param s 原文件名
???? * @return 重新編碼后的文件名
???? */
??? public static String toUtf8String(String s) {
StringBuffer sb = new StringBuffer();
for (int i=0;i<s.length();i++) {
??? char c = s.charAt(i);
??? if (c >= 0 && c <= 255) {
sb.append(c);
??? } else {
byte[] b;
try {
??? b = Character.toString(c).getBytes("utf-8");
} catch (Exception ex) {
??? System.out.println(ex);
??? b = new byte[0];
}
for (int j = 0; j < b.length; j++) {
??? int k = b[j];
??? if (k < 0) k += 256;
??? sb.append("%" + Integer.toHexString(k).
??? toUpperCase());
}
??? }
}
return sb.toString();
??? }



liaojiyong 2006-07-03 17:24 發表評論
]]>
介紹一篇關于session的好文章,寫的很詳細(jsp-servlet 技術) (轉)http://m.tkk7.com/liaojiyong/archive/2006/06/29/55750.htmlliaojiyongliaojiyongThu, 29 Jun 2006 06:54:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/06/29/55750.htmlhttp://m.tkk7.com/liaojiyong/comments/55750.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/06/29/55750.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/55750.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/55750.html

摘要:雖然session機制在web應用程序中被采用已經很長時間了,但是仍然有很多人不清楚session機制的本質,以至不能正確的應用這一技術。本文將詳細討論session的工作機制并且對在Java?web?application中應用session機制時常見的問題作出解答。

目錄:
一、術語session
二、HTTP協議與狀態保持
三、理解cookie機制
四、理解session機制
五、理解javax.servlet.http.HttpSession
六、HttpSession常見問題
七、跨應用程序的session共享
八、總結
參考文檔

一、術語session
在我的經驗里,session這個詞被濫用的程度大概僅次于transaction,更加有趣的是transaction與session在某些語境下的含義是相同的。

session,中文經常翻譯為會話,其本來的含義是指有始有終的一系列動作/消息,比如打電話時從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之為一個?session。有時候我們可以看到這樣的話“在一個瀏覽器會話期間,...”,這里的會話一詞用的就是其本義,是指從一個瀏覽器窗口打開到關閉這個期間?①。最混亂的是“用戶(客戶端)在一次會話期間”這樣一句話,它可能指用戶的一系列動作(一般情況下是同某個具體目的相關的一系列動作,比如從登錄到選購商品到結賬登出這樣一個網上購物的過程,有時候也被稱為一個transaction),然而有時候也可能僅僅是指一次連接,也有可能是指含義①,其中的差別只能靠上下文來推斷②。

然而當session一詞與網絡協議相關聯時,它又往往隱含了“面向連接”和/或“保持狀態”這樣兩個含義,?“面向連接”指的是在通信雙方在通信之前要先建立一個通信的渠道,比如打電話,直到對方接了電話通信才能開始,與此相對的是寫信,在你把信發出去的時候你并不能確認對方的地址是否正確,通信渠道不一定能建立,但對發信人來說,通信已經開始了。“保持狀態”則是指通信的一方能夠把一系列的消息關聯起來,使得消息之間可以互相依賴,比如一個服務員能夠認出再次光臨的老顧客并且記得上次這個顧客還欠店里一塊錢。這一類的例子有“一個TCP?session”或者?“一個POP3?session”③。

而到了web服務器蓬勃發展的時代,session在web開發語境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務器之間保持狀態的解決方案④。有時候session也用來指這種解決方案的存儲結構,如“把xxx保存在session?里”⑤。由于各種用于web開發的語言在一定程度上都提供了對這種解決方案的支持,所以在某種特定語言的語境下,session也被用來指代該語言的解決方案,比如經常把Java里提供的javax.servlet.http.HttpSession簡稱為session⑥。

鑒于這種混亂已不可改變,本文中session一詞的運用也會根據上下文有不同的含義,請大家注意分辨。
在本文中,使用中文“瀏覽器會話期間”來表達含義①,使用“session機制”來表達含義④,使用“session”表達含義⑤,使用具體的“HttpSession”來表達含義⑥

二、HTTP協議與狀態保持
HTTP?協議本身是無狀態的,這與HTTP協議本來的目的是相符的,客戶端只需要簡單的向服務器請求下載某些文件,無論是客戶端還是服務器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關系一樣。

然而聰明(或者貪心?)的人們很快發現如果能夠提供一些按需生成的動態信息會使web變得更加有用,就像給有線電視加上點播功能一樣。這種需求一方面迫使HTML逐步添加了表單、腳本、DOM等客戶端行為,另一方面在服務器端則出現了CGI規范以響應客戶端的動態請求,作為傳輸載體的HTTP協議也添加了文件上載、?cookie這些特性。其中cookie的作用就是為了解決HTTP協議無狀態的缺陷所作出的努力。至于后來出現的session機制則是又一種在客戶端與服務器之間保持狀態的解決方案。

讓我們用幾個例子來描述一下cookie和session機制之間的區別與聯系。筆者曾經常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數量。想象一下其實也無外乎下面的幾種方案:
1、該店的店員很厲害,能記住每位顧客的消費數量,只要顧客一走進咖啡店,店員就知道該怎么對待了。這種做法就是協議本身支持狀態。
2、發給顧客一張卡片,上面記錄著消費的數量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以后的消費相聯系起來。這種做法就是在客戶端保持狀態。
3、發給顧客一張會員卡,除了卡號之外什么信息也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店里的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在服務器端保持狀態。

由于HTTP協議是無狀態的,而出于種種考慮也不希望使之成為有狀態的,因此,后面兩種方案就成為現實的選擇。具體來說cookie機制采用的是在客戶端保持狀態的方案,而session機制采用的是在服務器端保持狀態的方案。同時我們也看到,由于采用服務器端保持狀態的方案在客戶端也需要保存一個標識,所以session機制可能需要借助于cookie機制來達到保存標識的目的,但實際上它還有其他選擇。

三、理解cookie機制?
cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發;“會員卡”的內容;以及客戶如何使用“會員卡”。

正統的cookie分發是通過擴展HTTP協議來實現的,服務器通過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。

而cookie?的使用是由瀏覽器按照一定的原則在后臺自動發送給服務器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發送給服務器。意思是麥當勞的會員卡只能在麥當勞的店里出示,如果某家分店還發行了自己的會員卡,那么進這家店的時候除了要出示麥當勞的會員卡,還要出示這家店的會員卡。

cookie的內容主要包括:名字,值,過期時間,路徑和域。
其中域可以指定某一個域比如.google.com,相當于總店招牌,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com,可以用飄柔來做比。
路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。
路徑與域合在一起就構成了cookie的作用范圍。
如果不設置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,只要關閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會話期的?cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內存里,當然這種行為并不是規范規定的。如果設置了過期時間,瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。

存儲在硬盤上的cookie?可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內存里的cookie,不同的瀏覽器有不同的處理方式。對于IE,在一個打開的窗口上按?Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進程則不能共享已經打開的窗口的內存cookie;對于?Mozilla?Firefox0.8,所有的進程和標簽頁都可以共享同樣的cookie。一般來說是用javascript的window.open打開的窗口會與原窗口共享內存cookie。瀏覽器對于會話cookie的這種只認cookie不認人的處理方式經常給采用session機制的web應用程序開發者造成很大的困擾。

下面就是一個goolge設置cookie的響應頭的例子
HTTP/1.1?302?Found
Location:?
http://www.google.com/intl/zh-CN/
Set-Cookie:?PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8;?expires=Sun,?17-Jan-2038?19:14:07?GMT;?path=/;?domain=.google.com
Content-Type:?text/html




這是使用HTTPLook這個HTTP?Sniffer軟件來俘獲的HTTP通訊紀錄的一部分




瀏覽器在再次訪問goolge的資源時自動向外發送cookie




使用Firefox可以很容易的觀察現有的cookie的值
使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。




IE也可以設置在接受cookie前詢問




這是一個詢問接受cookie的對話框。

四、理解session機制
session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構(也可能就是使用散列表)來保存信息。

當程序需要為某個客戶端的請求創建一個session的時候,服務器首先檢查這個客戶端的請求里是否已包含了一個session標識?-?稱為?session?id,如果已包含一個session?id則說明以前已經為此客戶端創建過session,服務器就按照session?id把這個?session檢索出來使用(如果檢索不到,可能會新建一個),如果客戶端請求不包含session?id,則為此客戶端創建一個session并且生成一個與此session相關聯的session?id,session?id的值應該是一個既不會重復,又不容易被找到規律以仿造的字符串,這個?session?id將被在本次響應中返回給客戶端保存。

保存這個session?id的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規則把這個標識發揮給服務器。一般這個cookie的名字都是類似于SEEESIONID,而。比如weblogic對于web應用程序生成的cookie,JSESSIONID=?ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是?JSESSIONID。

由于cookie可以被人為的禁止,必須有其他機制以便在cookie被禁止時仍然能夠把session?id傳遞回服務器。經常被使用的一種技術叫做URL重寫,就是把session?id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現形式為
http://...../xxx;jsessionid= ?ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
另一種是作為查詢字符串附加在URL后面,表現形式為
http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
這兩種方式對于用戶來說是沒有區別的,只是服務器在解析的時候處理的方式不同,采用第一種方式也有利于把session?id的信息和正常程序參數區分開來。
為了在整個交互過程中始終保持狀態,就必須在每個客戶端可能請求的路徑后面都包含這個session?id。

另一種技術叫做表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把session?id傳遞回服務器。比如下面的表單
<form?name="testform"?action="/xxx">
<input?type="text">
</form>
在被傳遞給客戶端之前將被改寫成
<form?name="testform"?action="/xxx">
<input?type="hidden"?name="jsessionid"?value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input?type="text">
</form>
這種技術現在已較少應用,筆者接觸過的很古老的iPlanet6(SunONE應用服務器的前身)就使用了這種技術。
實際上這種技術可以簡單的用對action應用URL重寫來代替。

在談論session機制的時候,常常聽到這樣一種誤解“只要關閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務器刪除一個session,否則服務器會一直保留,程序一般都是在用戶做log?off的時候發個指令去刪除session。然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經關閉,之所以會有這種錯覺,是大部分session機制都使用會話cookie來保存session?id,而關閉瀏覽器后這個?session?id就消失了,再次連接服務器時也就無法找到原來的session。如果服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發出的HTTP請求頭,把原來的session?id發送給服務器,則再次打開瀏覽器仍然能夠找到原來的session。

恰恰是由于關閉瀏覽器不會導致session被刪除,迫使服務器為seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就可以認為客戶端已經停止了活動,才會把session刪除以節省存儲空間。

五、理解javax.servlet.http.HttpSession
HttpSession是Java平臺對session機制的實現規范,因為它僅僅是個接口,具體到每個web應用服務器的提供商,除了對規范支持之外,仍然會有一些規范里沒有規定的細微差異。這里我們以BEA的Weblogic?Server8.1作為例子來演示。

首先,Weblogic?Server提供了一系列的參數來控制它的HttpSession的實現,包括使用cookie的開關選項,使用URL重寫的開關選項,session持久化的設置,session失效時間的設置,以及針對cookie的各種設置,比如設置cookie的名字、路徑、域,?cookie的生存時間等。

一般情況下,session都是存儲在內存里,當服務器進程被停止或者重啟的時候,內存里的session也會被清空,如果設置了session的持久化特性,服務器就會把session保存到硬盤上,當服務器進程重新啟動或這些信息將能夠被再次使用,?Weblogic?Server支持的持久性方式包括文件、數據庫、客戶端cookie保存和復制。

復制嚴格說來不算持久化保存,因為session實際上還是保存在內存里,不過同樣的信息被復制到各個cluster內的服務器進程中,這樣即使某個服務器進程停止工作也仍然可以從其他進程中取得session。

cookie生存時間的設置則會影響瀏覽器生成的cookie是否是一個會話cookie。默認是使用會話cookie。有興趣的可以用它來試驗我們在第四節里提到的那個誤解。

cookie的路徑對于web應用程序來說是一個非常重要的選項,Weblogic?Server對這個選項的默認處理方式使得它與其他服務器有明顯的區別。后面我們會專題討論。

關于session的設置參考[5]?
http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

六、HttpSession常見問題
(在本小節中session的含義為⑤和⑥的混合)


1、session在何時被創建
一個常見的誤解是以為session在有客戶端訪問時就被創建,然而事實是直到某server端程序調用?HttpServletRequest.getSession(true)這樣的語句時才被創建,注意如果JSP沒有顯示的使用?<%?@page?session="false"%>?關閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句?HttpSession?session?=?HttpServletRequest.getSession(true);這也是JSP中隱含的?session對象的來歷。

由于session會消耗內存資源,因此,如果不打算使用session,應該在所有的JSP中關閉它。

2、session何時被刪除
綜合前面的討論,session在下列情況下被刪除a.程序調用HttpSession.invalidate();或b.距離上一次收到客戶端發送的session?id時間間隔超過了session的超時設置;或c.服務器進程被停止(非持久session)

3、如何做到在瀏覽器關閉時刪除session
嚴格的講,做不到這一點。可以做一點努力的辦法是在所有的客戶端頁面里使用javascript代碼window.oncolose來監視瀏覽器的關閉動作,然后向服務器發送一個請求來刪除session。但是對于瀏覽器崩潰或者強行殺死進程這些非常規手段仍然無能為力。

4、有個HttpSessionListener是怎么回事
你可以創建這樣的listener去監控session的創建和銷毀事件,使得在發生這樣的事件時你可以做一些相應的工作。注意是session的創建和銷毀動作觸發listener,而不是相反。類似的與HttpSession有關的listener還有?HttpSessionBindingListener,HttpSessionActivationListener和?HttpSessionAttributeListener。

5、存放在session中的對象必須是可序列化的嗎
不是必需的。要求對象可序列化只是為了session能夠在集群中被復制或者能夠持久保存或者在必要時server能夠暫時把session交換出內存。在?Weblogic?Server的session中放置一個不可序列化的對象在控制臺上會收到一個警告。我所用過的某個iPlanet版本如果?session中有不可序列化的對象,在session銷毀時會有一個Exception,很奇怪。

6、如何才能正確的應付客戶端禁止cookie的可能性
對所有的URL使用URL重寫,包括超鏈接,form的action,和重定向的URL,具體做法參見[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

7、開兩個瀏覽器窗口訪問應用程序會使用同一個session還是不同的session
參見第三小節對cookie的討論,對session來說是只認id不認人,因此不同的瀏覽器,不同的窗口打開方式以及不同的cookie存儲方式都會對這個問題的答案有影響。

8、如何防止用戶打開兩個瀏覽器窗口操作導致的session混亂
這個問題與防止表單多次提交是類似的,可以通過設置客戶端的令牌來解決。就是在服務器每次生成一個不同的id返回給客戶端,同時保存在session里,客戶端提交表單時必須把這個id也返回服務器,程序首先比較返回的id與保存在session里的值是否一致,如果不一致則說明本次操作已經被提交過了。可以參看《J2EE核心模式》關于表示層模式的部分。需要注意的是對于使用javascript?window.open打開的窗口,一般不設置這個id,或者使用單獨的id,以防主窗口無法操作,建議不要再window.open打開的窗口里做修改操作,這樣就可以不用設置。

9、為什么在Weblogic?Server中改變session的值后要重新調用一次session.setValue
做這個動作主要是為了在集群環境中提示Weblogic?Server?session中的值發生了改變,需要向其他服務器進程復制新的session值。

10、為什么session不見了
排除session正常失效的因素之外,服務器本身的可能性應該是微乎其微的,雖然筆者在iPlanet6SP1加若干補丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件造成的問題;理論上防火墻或者代理服務器在cookie處理上也有可能會出現問題。
出現這一問題的大部分原因都是程序的錯誤,最常見的就是在一個應用程序中去訪問另外一個應用程序。我們在下一節討論這個問題。

七、跨應用程序的session共享

常常有這樣的情況,一個大項目被分割成若干小項目開發,為了能夠互不干擾,要求每個小項目作為一個單獨的web應用程序開發,可是到了最后突然發現某幾個小項目之間需要共享一些信息,或者想使用session來實現SSO(single?sign?on),在session中保存login的用戶信息,最自然的要求是應用程序間能夠訪問彼此的session。

然而按照Servlet規范,session的作用范圍應該僅僅限于當前應用程序下,不同的應用程序之間是不能夠互相訪問對方的session的。各個應用服務器從實際效果上都遵守了這一規范,但是實現的細節卻可能各有不同,因此解決跨應用程序session共享的方法也各不相同。

首先來看一下Tomcat是如何實現web應用程序之間session的隔離的,從?Tomcat設置的cookie路徑來看,它對不同的應用程序設置的cookie路徑是不同的,這樣不同的應用程序所用的session?id是不同的,因此即使在同一個瀏覽器窗口里訪問不同的應用程序,發送給服務器的session?id也可以是不同的。


??

根據這個特性,我們可以推測Tomcat中session的內存結構大致如下。




筆者以前用過的iPlanet也采用的是同樣的方式,估計SunONE與iPlanet之間不會有太大的差別。對于這種方式的服務器,解決的思路很簡單,實際實行起來也不難。要么讓所有的應用程序共享一個session?id,要么讓應用程序能夠獲得其他應用程序的session?id。

iPlanet中有一種很簡單的方法來實現共享一個session?id,那就是把各個應用程序的cookie路徑都設為/(實際上應該是/NASApp,對于應用程序來講它的作用相當于根)。
<session-info>
<path>/NASApp</path>
</session-info>

需要注意的是,操作共享的session應該遵循一些編程約定,比如在session?attribute名字的前面加上應用程序的前綴,使得?setAttribute("name",?"neo")變成setAttribute("app1.name",?"neo"),以防止命名空間沖突,導致互相覆蓋。


在Tomcat中則沒有這么方便的選擇。在Tomcat版本3上,我們還可以有一些手段來共享session。對于版本4以上的Tomcat,目前筆者尚未發現簡單的辦法。只能借助于第三方的力量,比如使用文件、數據庫、JMS或者客戶端cookie,URL參數或者隱藏字段等手段。

我們再看一下Weblogic?Server是如何處理session的。


??

從截屏畫面上可以看到Weblogic?Server對所有的應用程序設置的cookie的路徑都是/,這是不是意味著在Weblogic?Server中默認的就可以共享session了呢?然而一個小實驗即可證明即使不同的應用程序使用的是同一個session,各個應用程序仍然只能訪問自己所設置的那些屬性。這說明Weblogic?Server中的session的內存結構可能如下




對于這樣一種結構,在?session機制本身上來解決session共享的問題應該是不可能的了。除了借助于第三方的力量,比如使用文件、數據庫、JMS或者客戶端?cookie,URL參數或者隱藏字段等手段,還有一種較為方便的做法,就是把一個應用程序的session放到ServletContext中,這樣另外一個應用程序就可以從ServletContext中取得前一個應用程序的引用。示例代碼如下,

應用程序A
context.setAttribute("appA",?session);?

應用程序B
contextA?=?context.getContext("/appA");
HttpSession?sessionA?=?(HttpSession)contextA.getAttribute("appA");?

值得注意的是這種用法不可移植,因為根據ServletContext的JavaDoc,應用服務器可以處于安全的原因對于context.getContext("/appA");返回空值,以上做法在Weblogic?Server?8.1中通過。

那么Weblogic?Server為什么要把所有的應用程序的cookie路徑都設為/呢?原來是為了SSO,凡是共享這個session的應用程序都可以共享認證的信息。一個簡單的實驗就可以證明這一點,修改首先登錄的那個應用程序的描述符weblogic.xml,把cookie路徑修改為/appA?訪問另外一個應用程序會重新要求登錄,即使是反過來,先訪問cookie路徑為/的應用程序,再訪問修改過路徑的這個,雖然不再提示登錄,但是登錄的用戶信息也會丟失。注意做這個實驗時認證方式應該使用FORM,因為瀏覽器和web服務器對basic認證方式有其他的處理方式,第二次請求的認證不是通過?session來實現的。具體請參看[7]?secion?14.8?Authorization,你可以修改所附的示例程序來做這些試驗。

八、總結
session機制本身并不復雜,然而其實現和配置上的靈活性卻使得具體情況復雜多變。這也要求我們不能把僅僅某一次的經驗或者某一個瀏覽器,服務器的經驗當作普遍適用的經驗,而是始終需要具體情況具體分析。
摘要:雖然session機制在web應用程序中被采用已經很長時間了,但是仍然有很多人不清楚session機制的本質,以至不能正確的應用這一技術。本文將詳細討論session的工作機制并且對在Java?web?application中應用session機制時常見的問題作出解答。


liaojiyong 2006-06-29 14:54 發表評論
]]>
JSP實用篇(轉)http://m.tkk7.com/liaojiyong/archive/2006/06/29/55749.htmlliaojiyongliaojiyongThu, 29 Jun 2006 06:52:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/06/29/55749.htmlhttp://m.tkk7.com/liaojiyong/comments/55749.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/06/29/55749.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/55749.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/55749.html一,重定向頁面

1,response.sendRedirect("url");
2,response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location",newLocation);

二,HTML Encoder和URL Encoder

1,HTML Encoder自定義,原則:''不輸出,'&'-"&amp;",'<'-"&lt;",'>'-"gt;",'"'-"&quot;"
2,URLEncoder 在java.net包中有定義
原型:public static String encode(String s)
例如:URLEncoder.encode("http://wwww.aaa.com/sss.jsp?name=小鬼")

三,在JSP中讀寫文件

1,用FileOutputStream初始化PrintWriter,然后用print或者println方法寫文件
PrintWriter pw=new PrintWriter(new FileOutputStream("file1.txt"));
pw.println("Hello world!");
pw.close();//若有錯誤thow IOException

用FileWriter初始化PrintWriter,然后用print或者println方法寫文件
File f=new File("file1.txt");
PrintWriter pw=new PrintWriter(new FileWriter(f));
pw.print("Hello world!\n");
pw.close();
2,用InputStreamReader或者FileReader初始化BufferedReader,然后用readLine()方法讀取文件
BufferedReader br=new BufferedReader(new FileReader("file1.txt"));
String rt=br.readLine();//結尾為null
br.close();
3,用FileWriter初始化PrintWriter,然后用pint或者println方法添加文件
PrintWriter pw=new PrintWriter(new FileWriter("file1.txt"),true);
4,import java.io.*;
File f=new File(request.getRealPath(""),"file1.txt");
boolean f.exists();
f.delete();f.createNewFile();

File d=new File(request.getRealPath(""));
boolean d.exists();
d.delete();d.mkdir();

request.getRealPath("url");//虛擬目錄映射為實際目錄
request.getRealPath("./");//網頁所在的目錄
request.getRealPath("../");//網頁所在目錄的上一層目錄

File f=new File("path","file1.txt");
f.getName();
f.isFile();
f.isDirectory();
f.canRead();
f.canWrite();
f.isHidden();
f.lastModified;
f.createNewFile();
f.length();

File d=new File("path");
File list[]=d.listFiles();//list是一個File數組
for(int i=0;i<list.length;i++)out.println(list[i].getName());

FileReader fr=new FileReader("path"+"\\file1.txt");
if(fr.read()==-1)//空文件
fr.close();
fr.read(int i)//讀取i個字符,-1如果不再有數據
//用BufferedReader可以一次讀取一行數據
fr.skip(int i);//略過i個字符


在引用parseInt等函數的時候,出錯是NumberFormatException等
Random獲得隨機數,
Random rd=new Random((new Date()).getTime());
int p=Math.abs(rd.nextInt())%s;//s為0到的范圍

四,URL重組、表單隱藏域Cookie

1,這些是用來彌補HTTP協議無狀態特征的技術(Sessions技術)的一部分
2,URL重組是用Get方法向服務器發送的信息“?param1=value1&param2=value2&...&paramn=valuen”
如果服務器已經在超鏈接上面作了session標記,那么客戶端通過這個走超鏈接發送請來時就會包含此標記
3,form中的<input type=hidden name="key1" value="value1" />也可以像URL重組那樣使用。
4,Cookie對象
Cookie c=new Cookie("key", "value");
response.addCookie(c);

Cookie[] c=request.getCookies();
c.setMaxAge(int k);//k以秒為單位
一般瀏覽器能放20個Cookie

五,session對象

1,session對象不僅僅能放String數據,還能放復雜的對象。
2,session.putValue("key1",Object1);
Object o=session.getValue("key1");

六,處理JSP中的中文問題

1,ASCII碼
8bit存儲,0~31和127是控制字符,32~126是可見字符。
2,GB2312
兩個8bit表示。前一個127~255,以區分ASCII碼。
3,Unicode
可以將世界上幾十種文字編碼統一在同一種編碼機制下。以16bit為單位存儲。0x0000~0xffff
4,ISO-8859-1 或稱為Latin-1,8859-1。在Unicode所占的值域為0~255,低位為ASCII擴展到0~255,然后在高位補上0x00,組成16bit(此處不太懂)。
5,字節和unicode Java內核是unicode,class文件也是。但是流卻是采用的byte方式。char為unicode方式,byte是字節方式。轉換函數:sun.io里面:
public static ByteToCharConverter getDefault();//獲取系統使用的編碼方式。
public static ByteToCharConverter getConverter(String encoding);
ByteToCharConverter c=New ByteToCharConverter(["encoding"]);
Byte[] s=c.convertAll(Char[] d);
也可以 Char[] d=c.converterAll(Byte[] s);
6,一些函數:
Integer.toHexString(int i);
String s;s.getBytes();
String(byte[]);String(byte[],encoding);//constructors
//關于Unicode編碼打算單獨寫一篇

七,獲取JVM屬性值

Properties props=System.getProperties();
Enumeration enum=props.propertyNames(); //key枚舉
key=(String)enum.nextElement();
String s=(String)props.getProperty(key);

八,JSP錯誤處理

1,所有可被throw和catch的Exception對象都繼承自Throwable。Exception應該被catch才對;Error對象也是繼承自Throwable,只是不應該catch,而的結束程序。
2,catch序列針對的Exception應該從低級到高級才對。
3,轉譯錯誤和客戶端端請求錯誤。jsp源程序錯誤、import路徑不正確等會在生成Servlet Class文檔時產生轉譯錯誤(500)。在執行Servlet Class時客戶端請求錯誤會被catch。
4,錯誤產生時,可以jsp:forward來控制,但更好是用errorPage來處理。也可以throw new Exception("errMsg")。

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=601253



liaojiyong 2006-06-29 14:52 發表評論
]]>
關于JSP文件路徑問題 (轉)http://m.tkk7.com/liaojiyong/archive/2006/06/16/53301.htmlliaojiyongliaojiyongFri, 16 Jun 2006 06:45:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/06/16/53301.htmlhttp://m.tkk7.com/liaojiyong/comments/53301.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/06/16/53301.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/53301.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/53301.html<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>文件的建立、檢查與刪除</title>
</head>
<body>
<%
String path=request.getRealPath("");
//out.println(path);
File f=new File(path,"File.txt");
//out.println(f);
//out.println(f.exists());

if(f.exists()){//檢查File.txt是否存在
f.delete();//刪除File.txt文件
out.println(path + "\\File.txt 存在,已刪除。");
}else{
f.createNewFile();//在當前目錄下建立一個名為File.txt的文件
out.println(path + "\\File.txt 不存在,已建立。");//輸出目前所在的目錄路徑
}
%>

目錄的建立/檢查與刪除
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>目錄的建立/檢查與刪除</title>
</head>
<body>
<%
String path=request.getRealPath("");
path=path + "\\Sub";//將要建立的目錄路徑
File d=new File(path);//建立代表Sub目錄的File對象,并得到它的一個引用
if(d.exists()){//檢查Sub目錄是否存在
d.delete();
out.println("Sub目錄存在,已刪除");
}else{
d.mkdir();//建立Sub目錄
out.println("Sub目錄不存在,已建立");
}
%>
</body>
</html>


如何在JSP中處理虛擬目錄
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>JSP中如何處理虛擬目錄</title>
</head>
<body>
取得虛擬目錄對應的磁盤路徑<br>
Web站點主目錄的位置為<font color=#ff0000><%=request.getRealPath("/")%></font><br>
JSP網頁所在的目錄位置<font color=#ff0000><%=request.getRealPath("./")%></font><br>
JSP網頁所在目錄上一層目錄的位置<font color=#ff0000><%=request.getRealPath("../")%></font><br>
</body>
</html>


文件屬性的取得
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.util.Date,java.io.*"%>
<html>
<head>
<title>文件屬性的取得</title>
</head>
<body>
<%
String path=request.getRealPath("/");
File f=new File(path,"ReadData.txt");
if(f.exists()){
%>
<%=f.getName()%>的屬性如下:<br><br>
文件長度為:<%=f.length()%>
<%=f.isFile()?"是文件":"不是文件"%><br>
<%=f.isDirectory()?"是目錄":"不是目錄"%><br>
<%=f.canRead()?"可讀取":"不可讀取"%><br>
<%=f.canWrite()?"可寫入":"不可寫入"%><br>
<%=f.isHidden()?"是隱藏文件":"不是隱藏文件"%><br>
文件的最后修改日期為:<%=new Date(f.lastModified())%><br>
<%
}else{
f.createNewFile();//在當前目錄下建立一個名為ReaData.txt的文件
%>
<%=f.getName()%>的屬性如下:<br><br>
文件長度為:<%=f.length()%>
<%=f.isFile()?"是文件":"不是文件"%><br>
<%=f.isDirectory()?"是目錄":"不是目錄"%><br>
<%=f.canRead()?"可讀取":"不可讀取"%><br>
<%=f.canWrite()?"可寫入":"不可寫入"%><br>
<%=f.isHidden()?"是隱藏文件":"不是隱藏文件"%><br>
文件的最后修改日期為:<%=new Date(f.lastModified())%><br>
<%
}
%>
</body>
</html>


取出目錄中文件的方法
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>取出目錄中文件的方法--列出目錄中的文件</title>
</head>
<body>
<%
String path=request.getRealPath("/");
File d=new File(path);//建立當前目錄中文件的File對象
File list[]=d.listFiles();//取得代表目錄中所有文件的File對象數組
out.println("<font color=#ff0000>" + path + "目錄下的文件:</font><br>");
for(int i=0;i<list.length;i++){
if(list<I>.isFile()){
out.println(list<I>.getName() + "<br>");
}
}
out.println("<br><font color=#ff0000>" + path + "目錄下的目錄:</font><br>");
for(int i=0;i<list.length;i++){
if(list<I>.isDirectory()){
out.println(list<I>.getName() + "<br>");
}
}
%>
</body>
</html>


判斷是否為空白文件
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>判斷是否為空白文件</title>
</head>
<body>
<%
String path=request.getRealPath("/");
out.println(path);
FileReader fr=new FileReader(path + "\\AtEnd.txt");//建立FileReader對象,并實例化為fr
//對FileReader類生成的對象使用read()方法,可以從字符流中讀取下一個字符。
if(fr.read()==-1)//判斷是否已讀到文件的結尾
{
out.print("AtEnd.txt文件中沒有數據<br>");
}else{
out.println("AtEnd.txt文件中有數據");
}
fr.close();
%>
</body>
</html>


讀取所有的文件數據
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*,java.lang.*"%>
<html>
<head>
<title>讀取所有的文件數據</title>
</head>
<body>
<%
String path=request.getRealPath(".");
FileReader fr=new FileReader(path + "\\ReadData.txt");
//關鍵在于讀取過程中,要判斷所讀取的字符是否已經到了文件的末尾,并且這個字符是不是文件中的斷行符,即判斷該字符值是否為13。
int c=fr.read();//從文件中讀取一個字符
//判斷是否已讀到文件結尾
while(c!=-1){
out.print((char)c);//輸出讀到的數據
c=fr.read();//從文件中繼續讀取數據
if(c==13){//判斷是否為斷行字符
out.print("<br>");//輸出分行標簽
fr.skip(1);//略過一個字符
//c=fr.read();//讀取一個字符
}
}
fr.close();
%>
</body>
</html> 一行一行讀取數據
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>文件讀取</title>
</head>
<body>
<%
String path=request.getRealPath("");//取得當前目錄的路徑
FileReader fr=new FileReader(path + "\\file\\inc\\t.txt");//建立FileReader對象,并實例化為fr
BufferedReader br=new BufferedReader(fr);//建立BufferedReader對象,并實例化為br
String Line=br.readLine();//從文件讀取一行字符串
//判斷讀取到的字符串是否不為空
while(Line!=null){
out.println(Line + "<br>");//輸出從文件中讀取的數據
Line=br.readLine();//從文件中繼續讀取一行數據
}
br.close();//關閉BufferedReader對象
fr.close();//關閉文件
%>
</body>
</html>


略過文件中的字符不讀取
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>略過字節不讀取</title>
</head>
<body>
<%
String path=request.getRealPath(".");
FileReader fr=new FileReader(path + "\\ReadData.txt");
fr.skip(2);//跳過2個字節
int c=fr.read();//讀取一個字節
while(c!=-1){
out.print((char)c);
c=fr.read();
}
fr.close();
%>
</body>
</html>


將數據寫入文件
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>將數據寫入文件</title>
</head>
<body>
<%
String path=request.getRealPath(".");
FileWriter fw=new FileWriter(path + "\\WriteData.txt");//建立FileWriter對象,并實例化fw
//將字符串寫入文件
fw.write("大家好!");
fw.write("本書是《JSP編程技巧》");
fw.write("請多多指教!");
fw.write("email:stride@sina.com");
fw.close();

FileReader fr=new FileReader(path + "\\WriteData.txt");
BufferedReader br=new BufferedReader(fr);//建立BufferedReader對象,并實例化為br
String Line=br.readLine();
//讀取一行數據
out.println(Line + "<br>");
br.close();//關閉BufferedReader對象
fr.close();
%>
</body>
</html>


將寫入文件的數據分行
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>將寫入文件的數據分行</title>
</head>
<body>
<%
String path=request.getRealPath(".");
FileWriter fw=new FileWriter(path + "\\WriteData.txt");
BufferedWriter bw=new BufferedWriter(fw);
bw.write("大家好!");
bw.write("本書是《JSP編程技巧》。");
bw.newLine();//斷行
bw.write("請多多指教!");
bw.newLine();//斷行
bw.write("email: stride@sina.com");
bw.flush();//將數據更新至文件
fw.close();//關閉文件流
out.println("寫入文件內容為:<br>");
FileReader fr=new FileReader(path + "\\WriteData.txt");
BufferedReader br=new BufferedReader(fr);
String Line=br.readLine();//讀取一行數據
while(Line!=null){
out.println(Line + "<br>");
Line=br.readLine();
}
fr.close();
%>
</body>
</html>
如何將數據追加寫入到文件
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.*"%>
<html>
<head>
<title>將寫入文件的數據分行</title>
</head>
<body>
<%
String path=request.getRealPath(".");
RandomAccessFile rf=new RandomAccessFile(path + "\\WriteData.txt","rw");//定義一個類RandomAccessFile的對象,并實例化
rf.seek(rf.length());//將指針移動到文件末尾
rf.writeBytes("\nAppend a line to the file!");
rf.close();//關閉文件流
out.println("寫入文件內容為:<br>");
FileReader fr=new FileReader(path + "\\WriteData.txt");
BufferedReader br=new BufferedReader(fr);//讀取文件的BufferedRead對象
String Line=br.readLine();
while(Line!=null){
out.println(Line + "<br>");
Line=br.readLine();
}
fr.close();//關閉文件
%>
</body>
</html></I></I></I></I>



liaojiyong 2006-06-16 14:45 發表評論
]]>
JSP內建對象http://m.tkk7.com/liaojiyong/archive/2006/05/24/47876.htmlliaojiyongliaojiyongWed, 24 May 2006 11:24:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/05/24/47876.htmlhttp://m.tkk7.com/liaojiyong/comments/47876.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/05/24/47876.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/47876.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/47876.html① out - javax.servlet.jsp.jspWriter
   out對象用于把結果輸出到網頁上。

方法:
1. void clear() ;
   清除輸出緩沖區的內容,但是不輸出到客戶端。

2. void clearBuffer() ;
   清除輸出緩沖區的內容,并輸出到客戶端。

3. void close() ;
   關閉輸出流,清除所有內容。

4. void flush() ;
   輸出緩沖區里面的數據。

5. int getBufferSize() ;
   獲取以kb為單位的目前緩沖區大小。

6. int getRemaining() ;
   獲取以kb為單位的緩沖區中未被占用的空間大小。

7. boolean isAutoFlush() ;
   是否自動刷新緩沖區。

8. void newLine() ;
   輸出一個換行字符。

9. void print( boolean b ) ;
   void print( char c ) ;
   void print( char[] s ) ;
   void print( double d ) ;
   void print( float f ) ;
   void print( int i ) ;
   void print( long l ) ;
   void print( Object obj ) ;
   void print( String s ) ;
   將指定類型的數據輸出到Http流,不換行。

10. void println( boolean b ) ;
    void println( char c ) ;
    void println( char[] s ) ;
    void println( double d ) ;
    void println( float f ) ;
    void println( int i ) ;
    void println( long l ) ;
    void println( Object obj ) ;
    void println( String s ) ;
    將指定類型的數據輸出到Http流,并輸出一個換行符。
   
11. Appendable append( char c ) ;
    Appendable append( CharSequence cxq, int start, int end ) ;
    Appendable append( CharSequence cxq ) ;
    將一個字符或者實現了CharSequence接口的對象添加到輸出流的后面。

成員:
int DEFAULT_BUFFER = 0    - 缺省緩沖區大小
int NO_BUFFER = -1        - writer是否處于緩沖輸出狀態
int UNBOUNDED_BUFFER = -2 - 是否限制緩沖區大小


② request - javax.servlet.http.HttpServletRequest
   request對象包含所有請求的信息,如請求的來源、標頭、cookies和請求相關的參數值等。

方法:
1. Object getAttribute( String name ) ;
   返回由name指定的屬性值,該屬性不存在時返回null。

2. Enumeration getAttributeNames() ;
   返回request對象的所有屬性名稱的集合。

3. String getAuthType() ;
   返回用來保護servlet的認證方法的名稱,未受保護時返回null。

4. String getCharacterEncoding() ;
   返回請求中的字符編碼方法,可以在response對象中設置。

5. int getContentLength() ;
   返回請求的BODY的長度,不能確定長度時返回-1。可以在response中設置。

6. String getContentType() ;
   返回在response中定義的內容類型。

7. String getContentPath() ;
   返回請求的路徑。

8. Cookie[] getCookies() ;
   返回客戶端所有的Cookie的數組。

9. Enumeration getHeaderNames() ;
   返回所有HTTP頭的名稱的集合。

10. Enumeration getHeaders( String name ) ;
    返回指定HTTP頭的所有值的集合。

11. String getHeader( String name ) ;
    返回指定名稱的HTTP頭的信息。

12. long getDateHeader( String name ) ;
    返回指定名稱的Data類型的HTTP頭的信息。

13. int getIntHeader( String name ) ;
    返回指定名稱的Int類型的HTTP頭的信息。

14. ServletInputStream getInputStream() ;
    返回請求的輸入流。

15. Locale getLocale() ;
    返回當前頁的Locale對象,可以在response中設定。

16. Enumeration getLocales() ;
    返回請求中所有的Locale對象的集合。

17. String getLocalName() ;
    獲取響應請求的服務器端主機名。

18. String getLocalAddr() ;
    獲取響應請求的服務器端地址。

19. int getLocalPort() ;
    獲取響應請求的服務器端端口

20. String getMethod() ;
    獲取客戶端向服務器端發送請求的方法(GET、POST)。

21. String getParameter( String name ) ;
    獲取客戶端發送給服務器端的參數值。

22. Map getParameterMap() ;
    該方法返回包含請求中所有參數的一個Map對象。

23. Enumeration getParameterNames() ;
    返回請求中所有參數的集合。

24. String[] getParameterValues( String name ) ;
    獲得請求中指定參數的所有值。

25. String getQueryString() ;
    返回get方法傳遞的參數字符串,該方法不分解出單獨的參數。

26. String getPathInfo() ;
    取出請求中處于ServletPath和QueryString之間的額外信息。

27. String getPathTranslated() ;
    返回用getPathInfo()方法取得的路徑信息的實際路徑。

28. String getProtocol() ;
    返回請求使用的協議。可以是HTTP1.1或者HTTP1.0。

29. BufferedReader getReader() ;
    返回請求的輸入流對應的Reader對象,該方法和getInputStream()方法在一個頁面中只能調用一個。

30. String getRemoteAddr() ;
    獲取發出請求的客戶端IP地址。

31. String getRemoteHost() ;
    獲取發出請求的客戶端主機名

32. String getRemoteUser() ;
    返回經過客戶端驗證的用戶名,未經驗證返回null。

33. int getRemotePort() ;
    返回發出請求的客戶端主機端口。

34. String getRealPath( String path ) ;
    返回給定虛擬路徑的物理路徑。

35. RequestDispatcher getRequestDispatcher( String path ) ;
    按給定的路徑生成資源轉向處理適配器對象。

36. String getRequestedSessionId() ;
    返回請求的session的標識。

37. String RequestURI() ;
    返回發出請求的客戶端地址,但是不包括請求的參數字符串。

38. StringBuffer getRequestURI() ;
    返回響應請求的服務器端地址

39. String getScheme() ;
    獲取協議名稱,缺省值為HTTP協議。

40. String getServerName() ;
    返回響應請求的服務器名稱。

41. String getServletPath() ;
    獲取客戶端所請求的腳本文件的文件路徑。

42. int getServerPort() ;
    獲取響應請求的服務器端主機端口號。

43. void removeAttribute( String name ) ;
    在屬性列表中刪除指定名稱的屬性。

44. void setAttribute( String name, Object value ) ;
    在屬性列表中添加/刪除指定的屬性。

45. void setCharacterEncoding( String name ) ;
    設置請求的字符編碼格式。

46. HttpSession getSession() ;
    HttpSession getSession( boolean create ) ;
    獲取session,如果create為true,在無session的情況下創建一個。
   
47. boolean isRequestedSessionIdFromCookie() ;
    檢查請求的會話ID是否為通過Cookie傳入。

48. boolean isRequestedSessionIdFromURL() ;
    檢查請求的會話ID是否為通過URL傳入。

49. boolean isRequestedSessionIdValid() ;
    檢查請求的會話ID是否仍然有效。

50. boolean isSecure() ;
    檢查請求是否使用安全鏈接,如果HTTPS等。

51. boolean isUserInRole( String role ) ;
    檢查已經通過驗證的用戶是否在是role所指定的角色。

52. Principal getUserPrincipal() ;
    返回包含用戶登陸名的一個java.security.Principal對象。

成員:
String BASIC_AUTH = "BASIC"             -
String CLIENT_CERT_AUTH = "CLIENT_CERT" -
String DIGEST_AUTH = "DIGEST"           -
String FORM_AUTH = "FORM"               -


③ response - javax.servlet.http.HttpServletResponse
   response對象主要將JSP容器處理后的結果傳回到客戶端。

方法:
1. void addCookie( Cookie cookie ) ;
   添加一個Cookie對象,保存客戶端信息。

2. void addDateHeader( String name, long value ) ;
   添加一個日期類型的HTTP頭信息,覆蓋同名的HTTP頭信息。

3. void addHeader( String name, String value ) ;
   添加一個HTTP頭,覆蓋同名的舊HTTP頭。

4. void addIntHeader( String name, int value ) ;
   添加一個整型的HTTP頭,覆蓋同名的舊HTTP頭。

5. boolean containsHeader( String name ) ;
   判斷指定的HTTP頭是否存在。

6. String encodeRedirectURL( String url ) ;
   對sendRedirect()方法使用的URL進行編碼。

7. String encodeURL( String url ) ;
   將URL予以編碼,回傳包含session ID的URL。
  
8. void flushBuffer() ;
   強制把當前緩沖區的內容發送到客戶端。

9. int getBufferSize() ;
   取得以kb為單位的緩沖區大小。

10. String getCharacterEncoding() ;
    獲取響應的字符編碼格式。

11. String getContentType() ;
    獲取響應的類型。

12. Locale getLocale() ;
    獲取響應的Locale對象。

13. ServletOutputStream getOutputStream() ;
    返回客戶端的輸出流對象。

14. PrintWriter getWriter() ;
    獲取輸出流對應的writer對象。

15. boolean isCommitted() ;
    判斷服務器端是否已經將數據輸出到客戶端。

16. void reset() ;
    清空buffer中的所有內容。

17. void resetBuffer() ;
    情況buffer中所有的內容,但是保留HTTP頭和狀態信息。

18. void sendError( int xc, String msg ) ;
    void sendError( int xc ) ;
    發送錯誤,包括狀態碼和錯誤信息。

19. void sendRedirect( String locationg ) ;
    把響應發送到另外一個位置進行處理。

20. void setBufferSize( int size ) ;
    設置以kb為單位的緩沖區大小。

21. void setCharacterEncoding( String charset ) ;
    設置響應使用的字符編碼格式。

22. void setContentLength( int length ) ;
    設置響應的BODY長度。

23. void setContentType( String type ) ;
    設置響應的類型。

24. void setDateHeader( String name, long value ) ;
    設置指定名稱的Data類型的HTTP頭的值。

25. void setHeader( String name, String value ) ;
    設置指定名稱的HTTP頭的值。

26. void setIntHeader( String name, int value ) ;
    設置指定名稱的int類型的HTTP頭的值。

27. void setStatus( int xc ) ;
    設置響應狀態碼,新值會覆蓋當前值。

成員(HTTP狀態碼):
int SC_CONTINUE = 100                      int SC_SWITCHING_PROTOCOLS = 101
int SC_OK = 200                            int SC_NON_AUTHORITATIVE_INFORMATION = 203
int SC_ACCEPTED = 202                      int SC_CREATED = 201
int SC_NO_CONTENT = 204                    int SC_RESET_CONTENT = 205
int SC_PARTIAL_CONTENT = 206               int SC_MULTIPLE_CHOICES = 300
int SC_MOVED_PERMANENTLY = 301             int SC_MOVED_TEMPORARILY = 302
int SC_FOUND = 302                         int SC_SEE_OTHER = 303
int SC_NOT_MODIFIED = 304                  int SC_USE_PROXY = 305
int SC_TEMPORARY_REDIRECT = 307            int SC_BAD_REQUEST = 400
int SC_UNAUTHORIZED = 401                  int SC_PAYMENT_REQUIRED = 402
int SC_FORBIDDEN = 403                     int SC_NOT_FOUND = 404
int SC_METHOD_NOT_ALLOWED = 405            int SC_NOT_ACCEPTABLE = 406
int SC_PROXY_AUTHENTICATION_REQUIRED = 407 int SC_REQUEST_TIMEOUT = 408
int SC_CONFLICT = 409                      int SC_GONE = 410
int SC_LENGTH_REQUIRED = 411               int SC_PRECONDITION_FAILED = 412
int SC_REQUEST_ENTITY_TOO_LARGE = 413      int SC_REQUEST_URI_TOO_LONG = 414
int SC_UNSUPPORTED_MEDIA_TYPE = 415        int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416
int SC_EXPECTATION_FAILED = 417            int SC_INTERNAL_SERVER_ERROR = 500
int SC_NOT_IMPLEMENTED = 501               int SC_BAD_GATEWAY = 502
int SC_SERVICE_UNAVAILABLE = 503           int SC_GATEWAY_TIMEOUT = 504
int SC_HTTP_VERSION_NOT_SUPPORTED = 505


④ session - javax.servlet.http.HttpSession
   session對象表示目前個別用戶的會話狀態,用來識別每個用戶。

方法:
1. Object getAttribute( String name ) ;
   獲取與指定名字相關聯的session屬性值。

2. Enumeration getAttributeNames() ;
   取得session內所有屬性的集合。

3. long getCreationTime() ;
   返回session的創建時間,最小單位千分之一秒。

4. String getId() ;
   取得session標識。

5. long getLastAccessedTime() ;
   返回與當前session相關的客戶端最后一次訪問的時間,由1970-01-01算起,單位毫秒。

6. int getMaxInactiveInterval( int interval ) ;
   返回總時間,以秒為單位,表示session的有效時間(session不活動時間)。-1為永不過期。

7. ServletContext getServletContext() ;
   返回一個該JSP頁面對應的ServletContext對象實例。

8. HttpSessionContext getSessionContext() ;
  

9. Object getValue( String name ) ;
   取得指定名稱的session變量值,不推薦使用。

10. String[] getValueNames() ;
    取得所有session變量的名稱的集合,不推薦使用。

11. void invalidate() ;
    銷毀這個session對象。

12. boolean isNew() ;
    判斷一個session是否由服務器產生,但是客戶端并沒有使用。

13. void pubValue( String name, Object value ) ;
    添加一個session變量,不推薦使用。

14. void removeValue( String name ) ;
    移除一個session變量的值,不推薦使用。

15. void setAttribute( String name, String value ) ;
    設置指定名稱的session屬性值。

16. void setMaxInactiveInterval( int interval ) ;
    設置session的有效期。

17. void removeAttribute( String name ) ;
    移除指定名稱的session屬性。


⑤ pageContext - javax.servlet.jsp.PageContext
   pageContext對象存儲本JSP頁面相關信息,如屬性、內建對象等。

方法:
1. void setAttribute( String name, Object value, int scope ) ;
   void setAttribute( String name, Object value ) ;
   在指定的共享范圍內設置屬性。

2. Object getAttribute( String name, int scope ) ;
   Object getAttribute( String name ) ;
   取得指定共享范圍內以name為名字的屬性值。

3. Object findAttribute( String name ) ;
   按頁面、請求、會話和應用程序共享范圍搜索已命名的屬性。

4. void removeAttribute( String name, int scope ) ;
   void removeAttribute( String name ) ;
   移除指定名稱和共享范圍的屬性。

5. void forward( String url ) ;
   將頁面導航到指定的URL。

6. Enumeration getAttributeNamesScope( int scope ) ;
   取得指定共享范圍內的所有屬性名稱的集合。

7. int getAttributeScope( String name ) ;
   取得指定屬性的共享范圍。

8. ErrorData getErrorDate() ;
   取得頁面的errorData對象。

9. Exception getException() ;
   取得頁面的exception對象。

10. ExpressionEvaluator getExpressionEvaluator() ;
    取得頁面的expressionEvaluator對象。

11. JspWriter getOut() ;
    取得頁面的out對象。

12. Object getPage() ;
    取得頁面的page對象。

13. ServletRequest getRequest() ;
    取得頁面的request對象。

14. ServletResponse getResponse() ;
    取得頁面的response對象。

15. ServletConfig getConfig() ;
    取得頁面的config對象。

16. ServletContext getServletContext() ;
    取得頁面的servletContext對象。

17. HttpSession getSession() ;
    取得頁面的session對象。

18. VariableResolver getVariableResolver() ;
    取得頁面的variableResolver對象。

19. void include( String url, boolean flush ) ;
    void include( String url ) ;
    包含其他的資源,并指定是否自動刷新。

20. void release() ;
    重置pageContext內部狀態,釋放所有內部引用。

21. void initialize( Servlet servlet, ServletRequest request, ServletResponse response,
                     String errorPageURL, boolean needSession, int bufferSize, boolean autoFlush ) ;
    初始化未經初始化的pageContext對象。

22. BodyContext pushBody() ;
    BodyContext pushBody( Writer writer ) ;
    保存當前的out對象,并更新pageContext中page范圍內的out對象。

23. JspWrite popBody() ;
    取出由pushBody()方法保存的out對象。

24. void handlePageException( Exception e ) ;
    void handlePageException( Thrwoable t ) ;
   

成員:
int PAGE_SCOPE = 1        - 頁面共享范圍
int REQUEST_SCOPE = 2     - 請求共享范圍
int SESSION_SCOPE = 3     - 會話共享范圍
int APPLICATION_SCOPE = 4 - 應用程序共享范圍
String PAGE = "javax.servlet.jsp.jspPage"
String PAGECONTEXT = "javax.servlet.jsp.jspPageContext"
String REQUEST = "javax.servlet.jsp.jspRequest"
String RESPONSE = "javax.servlet.jsp.jspResponse"
String CONFIG = "javax.servlet.jsp.jspConfig"
String SESSION = "javax.servlet.jsp.jspSession"
String OUT = "javax.servlet.jsp.jspOut"
String APPLICATION = "javax.servlet.jsp.jspApplication"
String EXCEPTION = "javax.servlet.jsp.jspException"


⑥ application - javax.servlet.ServletContext
   application主要功用在于取得或更改Servlet的設定。

方法:
1. Object getAttribute( String name ) ;
   返回由name指定的application屬性。

2. Enumeration getAttributes() ;
   返回所有的application屬性。

3. ServletContext getContext( String uripath ) ;
   取得當前應用的ServletContext對象。

4. String getInitParameter( String name ) ;
   返回由name指定的application屬性的初始值。

5. Enumeration getInitParameters() ;
   返回所有的application屬性的初始值的集合。

6. int getMajorVersion() ;
   返回servlet容器支持的Servlet API的版本號。

7. String getMimeType( String file ) ;
   返回指定文件的類型,未知類型返回null。一般為"text/html"和"image/gif"。

8. int getMinorVersion() ;
   返回servlet容器支持的Servlet API的副版本號。

9. String getRealPath( String path ) ;
   返回給定虛擬路徑所對應物理路徑。

10. RequestDispatcher getNamedDispatcher( String name ) ;
    為指定名字的Servlet對象返回一個RequestDispatcher對象的實例。

11. RequestDispatcher getRequestDispatcher( String path ) ;
    返回一個RequestDispatcher對象的實例。

12. URL getResource( String path ) ;
    返回指定的資源路徑對應的一個URL對象實例,參數要以"/"開頭。

13. InputStream getResourceAsStream( String path ) ;
    返回一個由path指定位置的資源的InputStream對象實例。

14. Set getResourcePaths( String path ) ;
    返回存儲在web-app中所有資源路徑的集合。

15. String getServerInfo() ;
    取得應用服務器版本信息。

16. Servlet getServlet( String name ) ;
    在ServletContext中檢索指定名稱的servlet。

17. Enumeration getServlets() ;
    返回ServletContext中所有servlet的集合。

18. String getServletContextName() ;
    返回本web應用的名稱。

19. Enumeration getServletContextNames() ;
    返回ServletContext中所有servlet的名稱集合。

20. void log( Exception ex, String msg ) ;
    void log( String msg, Throwable t ) ;
    void log( String msg ) ;
    把指定的信息寫入servlet log文件。

21. void removeAttribute( String name ) ;
    移除指定名稱的application屬性。

22. void setAttribute( String name, Object value ) ;
    設定指定的application屬性的值。


⑦ config - javax.servlet.ServletConfig
   config對象用來存放Servlet初始的數據結構。

方法:
1. String getInitParameter( String name ) ;
   返回名稱為name的促使參數的值。

2. Enumeration getInitParameters() ;
   返回這個JSP所有的促使參數的名稱集合。

3. ServletContext getContext() ;
   返回執行者的servlet上下文。

4. String getServletName() ;
   返回servlet的名稱。


⑧ exception - java.lang.Throwable
   錯誤對象,只有在JSP頁面的page指令中指定isErrorPage="true"后,才可以在本頁面使用exception對象。

方法:
1. Throwable fillInStackTrace() ;
   將當前stack信息記錄到exception對象中。

2. String getLocalizedMessage() ;
   取得本地語系的錯誤提示信息。

3. String getMessage()
   取得錯誤提示信息。

4. StackTrackElement[] getStackTrace() ;
   返回對象中記錄的call stack track信息。

5. Throwable initCause( Throwable cause ) ;
   將另外一個異常對象嵌套進當前異常對象中。
  
6. Throwable getCause() ;
   取出嵌套在當前異常對象中的異常。

7. void printStackTrace() ;
   void printStackTrace( printStream s ) ;
   void printStackTrace( printWriter s ) ;
   打印出Throwable及其call stack trace信息。

8. void setStackTrace( StackTraceElement[] stackTrace )
   設置對象的call stack trace信息。


⑨ page - javax.servlet.jsp.HttpJspPage
   page對象代表JSP對象本身,或者說代表編譯后的servlet對象,
   可以用( (javax.servlet.jsp.HttpJspPage)page )來取用它的方法和屬性。



liaojiyong 2006-05-24 19:24 發表評論
]]>
在struts1.1框架下,利用smartupload實現文件的上傳(可以是多個文件)(轉)http://m.tkk7.com/liaojiyong/archive/2006/05/23/47686.htmlliaojiyongliaojiyongTue, 23 May 2006 09:49:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/05/23/47686.htmlhttp://m.tkk7.com/liaojiyong/comments/47686.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/05/23/47686.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/47686.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/47686.html1、前端頁面upload.jsp,后臺處理程序UplodAction.java
2、struts.config的配置參數如下(沒有設置ActionForm):
<action input="/upload.jsp"? path="/save" scope="request" type="yhp.test.web.UploadAction" validate="false">
????? <forward name="success" path="/list.do" />
??? </action>
3、upload.jsp頁面中主要部分代碼
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html:html>
<head>
<title>?測試Struts利用SmartUpload上傳文件 </title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<html:form action="/save.do" styleId="formItem" method="post"? enctype="multipart/form-data">
<html:hidden property="id"/>
<html:file property="serverpath" styleClass="input-text"></html:file>
</html:form>
</body>
</html:html>
4、UploadAction.java
import com.jspsmart.upload.File;
import com.jspsmart.upload.Files;
import com.jspsmart.upload.SmartUpload;

public ActionForward doBatchsave(ActionMapping actionMapping,
???ActionForm actionForm, HttpServletRequest httpServletRequest,
???HttpServletResponse httpServletResponse) throws Exception {
SmartUpload mySmartUpload = new SmartUpload();
??mySmartUpload.initialize(getServlet().getServletConfig(),httpServletRequest, httpServletResponse);??
??mySmartUpload.upload();
??//獲取除文件以外的相關信息,例如upload.jsp中隱藏控件id的值
??String strId=(String)mySmartUpload.getRequest().getParameter("id");
??Files files=mySmartUpload.getFiles();
??Collection col=files.getCollection();
??Iterator it=col.iterator();
??while(it.hasNext()){
??? File file=(File)it.next();????
????String oldFileName=file.getFileName();?????
????String extname=file.getFileExt();
????String fileName=Sequence.getSequence()+"."+extname;//產生一個唯一的文件名
????file.saveAs("c:\\temp\"+fileName);????
??}
??return (actionMapping.findForward("success"));


liaojiyong 2006-05-23 17:49 發表評論
]]>
forward,include,redirect 區別(轉)http://m.tkk7.com/liaojiyong/archive/2006/05/23/47673.htmlliaojiyongliaojiyongTue, 23 May 2006 09:03:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/05/23/47673.htmlhttp://m.tkk7.com/liaojiyong/comments/47673.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/05/23/47673.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/47673.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/47673.htmlforward,include,redirect是jsp中web頁面(jsp或servelt)的三種不同的路由關系,三個貌似相同,其實還是有許多差別的,而了解這些細微的差別,在web開發中是很重要的。
說明:
一、forward、include由javax.servlet.RequestDispatcher來封裝,由包容器container提供RequestDispatcher接口的實現,其中聲明如下:
void forward(ServletRequest req,ServletResponse res)
void include(ServeltRequest req,ServletResponse res)
可以通過兩種方式得到RequestDispatcher:
1、ServletContext.getRequestDispatcher(String path);
其中這里的path必須開始為"/",即這里的path必須相對于context的root.
2、ServeltRequest.getRequestDispatcher(String path)
這里的path可以為相對路徑,如果path開始為"/",則也認為是從context的root開始的。
二、Redirect由HttpServletResponse.sendRedirect(String location)來支持
差別:
三個都可以對用戶的request進行轉發,但是還是有許多的不同,差別最主要集中在如下幾個方面:
1、forward與include共亨Request范圍內的對象,而redirect則不行,即:如果一個javabean被聲明為request范圍的話,則被forward到的資源也可以訪問這個javabean,而redriect則不行。
2、forward與include基本上都是轉發到context內部的資源,而redirect可以重定向到外部的資源,如: req.sendRedriect("http://www.mocuai.com");

Forward功能的實現,這樣
private void setForward(String url,ServletRequest request, ServletResponse response)throws Exception {??
??????? HttpServletRequest hreq = (HttpServletRequest) request;
??????? RequestDispatcher dispatcher =? hreq.getSession().getServletContext().getRequestDispatcher(url);
??????? dispatcher.forward(request,response);
}

liaojiyong 2006-05-23 17:03 發表評論
]]>
jsp頁面,以excel的方式輸出http://m.tkk7.com/liaojiyong/archive/2006/05/23/47667.htmlliaojiyongliaojiyongTue, 23 May 2006 08:36:00 GMThttp://m.tkk7.com/liaojiyong/archive/2006/05/23/47667.htmlhttp://m.tkk7.com/liaojiyong/comments/47667.htmlhttp://m.tkk7.com/liaojiyong/archive/2006/05/23/47667.html#Feedback0http://m.tkk7.com/liaojiyong/comments/commentRss/47667.htmlhttp://m.tkk7.com/liaojiyong/services/trackbacks/47667.html在jsp頁面中加入以下的語句,就可以使jsp以excel的形式輸出。
response.setHeader("Content-Type", "application/vnd.ms-excel;charset=GBK");
response.setContentType("application/vnd.ms-excel;charset=GBK");

liaojiyong 2006-05-23 16:36 發表評論
]]>
主站蜘蛛池模板: 四虎成人免费观看在线网址 | 成人av片无码免费天天看| 成年女人视频网站免费m| 亚洲六月丁香婷婷综合| 四虎永久精品免费观看| 久久免费动漫品精老司机| 国产AV无码专区亚洲AV琪琪| 国产AV无码专区亚洲AWWW| 亚洲AV成人噜噜无码网站| 精品国产亚洲AV麻豆| 免费大香伊蕉在人线国产| 黄色免费网站在线看| 一本色道久久综合亚洲精品| 久久99热精品免费观看动漫 | 又粗又黄又猛又爽大片免费| 四虎一区二区成人免费影院网址| 亚洲精品高清一二区久久| 国内精品免费久久影院| 亚洲AV无码成人精品区在线观看 | 亚洲?V乱码久久精品蜜桃| 国产乱妇高清无乱码免费| 亚洲va无码va在线va天堂| 免费v片在线观看视频网站| 亚洲色偷偷色噜噜狠狠99网| www.xxxx.com日本免费| 欧洲乱码伦视频免费国产| 亚洲字幕在线观看| 亚洲国产第一页www| 亚洲国产精品一区二区久久hs| 亚洲日本香蕉视频观看视频| 成人爽A毛片免费看| 九九九国产精品成人免费视频| 亚洲欧洲日产国码无码网站| 亚洲综合免费视频| 曰批全过程免费视频免费看| 亚洲国产精品久久久天堂| 欧美a级在线现免费观看| 久久av免费天堂小草播放| 亚洲av无码一区二区三区观看| 免费大片黄手机在线观看| 麻豆高清免费国产一区|